配置 Gitlab 和 Elasticsearch/Zoekt 并使用 Docker Metadata 数据库、Camo 代理服务
本文章首发于:连接 Gitlab 和 Elasticsearch/Zoekt 并使用 Docker Metadata 数据库、Camo 代理服务 - Ayaka 的小站
为确保更好阅读格式和阅读体验,更建议前往个人博客阅读 ~,另外本文章部分内部 Issue 链接为个人 Gitlab,不保证链接可用性。
一、简介
1)前提
大家好啊我是 Musicminion。今天这期博客来点小众一点话题——把 Gitlab 接入 ElasticSearch 还有 Zoekt 搜索引擎,以及把 Docker 镜像仓库的 Metadata 迁移到 PostgreSQL 数据库里面。
如果你想要学习 Gitlab 部署,网上还是有不少文章的,这篇文章可能对你不是很适用,因为这个算是比较进阶的内容,前提是需要有一个你部署好的 Gitlab EE (企业版) 实例。关于获取企业版许可证,请参考 Activate GitLab Enterprise Edition (EE) | GitLab Docs。
我知道大部分人部署 Gitlab 可能纯纯的当做内部协作的代码仓库来使用的,但是其实对于热爱折腾朋友来说,探索应该不止于此。Gitlab 作为一个非常优秀的代码存储工具,功能经过数年的迭代还是非常全的,几乎可以当做一个 All in One 的开发工具,比如下面的内容,网上已经有不少教程:
Gitlab Pages:个人静态网站部署工具
Gitlab Docker Registry:容器镜像库
配置 Github 集成的单点登录
CI/CD:流水线和 Runner 的配置
其他一些 Bug 的修复等
但是有些比较小众的功能,比如搜索等,这些功能中文教程很少。得对着说明文档一点一点的配置。因此这篇文章介绍一下 Gitlab 的小众功能。
2)搜索功能简介
Gitlab 的高级搜索还有精确代码搜索都是付费功能,需要企业版许可证。
Gitlab 支持不同类型的 Gitlab 搜索,比如搜索代码,搜索 Issue、项目名字,这些功能统称搜索。
社区 CE 版搜索:只支持最基本的搜索:项目名字等
Gitlab 高级搜索:可以搜索具体代码、评论、Issue 内容、里程碑
Zoekt 精确代码:高级搜索的代码不一定准确,Zoekt 可以更精准的搜索到代码(beta 版)
因为我平时个人搜索代码还是很频繁的,你要说为什么不本地 vscode 里面直接搜,那肯定是最快的嘛,但是有时候开发很可能就是想起来了,在浏览器里面顺手一搜索的事,能简化肯定是希望更简化的。
这是不同搜索支持的功能,看完之后是不是很心动:
基本搜索 (Basic search) | 高级搜索 (Advanced search) | 精确代码搜索 (Exact code search) | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
范围 | 全局 | 群组 | 项目 | 范围 | 全局 | 群组 | 项目 | 范围 | 全局 | 群组 | 项目 |
Code | No | No | Yes | Code | Yes | Yes | Yes | Code | No | Yes | Yes |
Comments | No | No | Yes | Comments | Yes | Yes | Yes | Comments | No | ||
Commits | No | No | Yes | Commits | Yes | Yes | Yes | Commits | |||
Epics | No | Yes | No | Epics | Yes | Yes | No | Epics | |||
Issues | Yes | Yes | Yes | Issues | Yes | Yes | Yes | Issues | |||
Merge requests | Yes | Yes | Yes | Merge requests | Yes | Yes | Yes | Merge requests | |||
Milestones | Yes | Yes | Yes | Milestones | Yes | Yes | Yes | Milestones | |||
Projects | Yes | Yes | No | Projects | Yes | Yes | No | Projects | |||
Users | Yes | Yes | Yes | Users | Yes | Yes | Yes | Users | |||
Wikis | No | No | Yes | Wikis | Yes | Yes | Yes | Wikis |
这个是部署好后的效果:
精确搜索:仅适用于代码搜索,并且会在搜索框里面显示:精确代码搜索(由 Zoekt 提供支持)已启用。如果你用过高级搜索,你会发现高级搜索代码经常可能会搜出来一堆无关紧要的东西,精确搜索可以保证搜到的所见即所
GitLab 精确搜索
高级搜索:可以搜索 Wiki、Commit 记录等,会提示:高级搜索 已启用
GitLab 高级搜索
3)容器镜像库 Metadata
如果你已经运营了一段时间的 Gitlab 的容器镜像库,就会发现你的 Gitlab 数据越来越大,经过排查我发现是 Gitlab 的容器镜像库默认情况不会自动做垃圾回收(GC)。(即使你通过 Web 界面删除了一个 Docker Image,容器镜像依然存储在你的 Gitlab 中)
有关垃圾回收的教程,请参考 Running the garbage collection on schedule | GitLab Docs,具体就是运行下面这个命令:
sudo gitlab-ctl stop # 停止 gitlab
sudo gitlab-ctl registry-garbage-collect # 运行垃圾回收
sudo gitlab-ctl start # 重启 gitlab
注意,Gitlab 的垃圾回收需要停止 Gitlab 服务的运行,不能支持在线回收。那要做到支持在线回收,必须迁移使用 metadata database,也就是把容器的元数据存储到数据库里面。这里推测 Gitlab 之前可能采用了 Docker 官方的 Registry 的方案,后面发现存在种种限制,因此又做了一些数据的迁移工作。
You can run garbage collection in the background without the need to schedule it or require read-only mode, if you migrate to the metadata database.
开启了 metadata database 后,就可以愉快的统计容器镜像库
镜像容量统计
4)Camo 代理服务
不知道有没有小伙伴仔细看过 Github 里面引用的一些外链图片的链接。如果你检查过就会发现,所有 Github Readme 引用的外部图片链接,全部变成了 https://camo.githubusercontent.com/
官方解释:为托管图像,GitHub 使用开源项目 Camo。 Camo 为每个文件生成匿名 URL 代理,以隐藏您的浏览器详细信息和来自其他用户的相关信息。 URL 以 https://<subdomain>.githubusercontent.com/ 开头,子域不同,具体取决于图像的上传方式。
这样的好处就是,假如有人为了恶意统计用户的访问 IP,然后引用于一个自己服务器的图片,这样每次用户打开这个 Readme 的时候,浏览器就会顺着这个图片的 URL,去抓取对应的图片,这样就可能导致隐私泄露的问题。
此外,因为国内的一些网络环境不好,一些引用的外网的图片,也可能全部变成了诡异的加载失败,如果能搭建一个自己的图片代理,就会好很多力!
Gitlab 的官方文档其实也是支持 Proxying assets | GitLab Docs,只不过可能很少有朋友关注到了这一点。部署好后的效果如下,可以看到所有的图片全都经过了我的个人 Camo 服务,成功加载:
Camo 效果演示
二、部署具体功能
废话不多说,首先介绍一下我自己是使用 Docker-Compose 的 yml 文件来部署的 Gitlab,我喜欢所有的配置文件集中管理,这样可以很好的迁移。关于 Docker 部署 Gitlab 教程已经烂大街我这里就不需要额外介绍了。
我们三个功能由易到难依次介绍:
1)Camo 代理服务
内部 Issue 链接:2025.08 Week 2: 给自建 Gitlab 增加 Camo 服务图片代理 (#5) · Issue · Musicminion/personal-plan
Github 上有一个开源项目 cactus/go-camo: A secure image proxy server,采用 Go 语言编写,同时具有轻量还有高效的特点,并且还提供了 Docker 的部署方式。
我这里是在一台自己的海外服务器上运行的,这是 docker-compose.yml:
services:
go-camo:
image: ghcr.io/cactus/go-camo:latest
restart: unless-stopped
ports:
- "52380:8080"
command: ["-k", "somekey"]
注意,你应该生成一个自己的密钥,然后用来代替上面的 somekey,这个 key 后面会用来签名使用。然后就是 Nginx 的配置这个也是老生常谈了的:
server {
listen 80;
server_name camo.example.com;
# 强制跳转到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name camo.ayaka.space;
ssl_certificate /path/to/fullchain.cer;
ssl_certificate_key /path/to/*.example.com.key;
# 建议加上一些现代 TLS 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:52380; # 这里是 docker-compose 里的服务名:端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
add_header Cache-Control "public, max-age=3600" always;
}
}
然后需要自己去你的 Gitlab 的个人设置里面,生成一个 PRIVATE-TOKEN 个人访问令牌,权限你可以暂时全部选上,或者勾选 sudo 也行。
Gitlab 个人访问令牌设置界面
然后使用命令行,记得把 <my_private_token> 替换为你的个人访问令牌:
curl --request "PUT" "https://gitlab.example.com/api/v4/application/settings?\
asset_proxy_enabled=true&\
asset_proxy_url=https://camo.example.com&\
asset_proxy_secret_key=somekey" \
--header 'PRIVATE-TOKEN: <my_private_token>'
之后重启 GitLab,你应该就可以看到图片是不是走的你的代理服务器加载的了。
Camo 服务启用后的效果
顺便提一嘴,其实这里还可以个性化的配置缓存过期的时间,具体可以参考 Nginx 的缓存时间配置。
2)ElasticSearch 搜索配置
ElasticSearch 是一个 Java 写的全文搜索工具,说实话非常吃性能,没有 16G 的大内存基本很难跑。而且这个东西的 Docker 部署小有复杂。
首先给你的 Gitlab 的 Docker compose 的文件里面加上以下内容:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
container_name: elasticsearch
restart: always
environment:
- discovery.type=single-node
- TZ=Asia/Shanghai
- xpack.security.enabled=false
- http.max_content_length=1024mb
volumes:
- ./elasticsearch/data:/usr/share/elasticsearch/data
- ./elasticsearch/config:/usr/share/elasticsearch/config
- ./elasticsearch/logs:/usr/share/elasticsearch/logs
- ./elasticsearch/plugins:/usr/share/elasticsearch/plugins
ports:
- '9200:9200'
- '9300:9300'
mem_limit: 16g
cpus: '8'
然后不要着急,先把容器里面的文件数据偷出来:
# 启动一个临时容器(不运行服务)
docker run --name temp_elasticsearch -d docker.elastic.co/elasticsearch/elasticsearch:8.10.2 tail -f /dev/null
# 拷贝数据
docker cp temp_elasticsearch:/usr/share/elasticsearch/data ./elasticsearch/data
docker cp temp_elasticsearch:/usr/share/elasticsearch/config ./elasticsearch/config
docker cp temp_elasticsearch:/usr/share/elasticsearch/logs ./elasticsearch/logs
docker cp temp_elasticsearch:/usr/share/elasticsearch/plugins ./elasticsearch/plugins
# 删除临时容器
docker rm -f temp_elasticsearch
因为其实是这样的,我们稍后会把这些目录挂载出来,但是实际上这些目录里面本身又有文件了,所以我们必须先把配置偷出来,然后才能运行,特别是像那些插件目录,我们必须挂出来。记得启动后检查一下 ES 的日志,如果发现有问题可能还需要对应的解决。
然后就是熟悉的改配置:
services:
gitlab:
image: gitlab/gitlab-ee:18.3.0-ee.0
container_name: gitlab
restart: always
shm_size: '4g'
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
GITLAB_OMNIBUS_CONFIG: |
# Add any other gitlab.rb configuration here, each on its own line
# ... 已有的配置
gitlab_rails['elasticsearch_enabled'] = true
gitlab_rails['elasticsearch_url'] = ['http://elasticsearch:9200']
gitlab_rails['elasticsearch_indexer_enabled'] = true
gitlab_rails['elasticsearch_aws'] = false
然后就是进入管理页面配置了:
勾选开启 ElasticSearch 索引
然后注意 URL 必须配置为从 Gitlab 容器里面可以访问到的搜索的 URL,为了安全起见可以考虑配置一下密码之类的,我这里仅仅是内部访问,就直接忽略了。
配置完成后点 索引实例,然后静候佳音
高级搜索设置面板
另外据说还有一个中文的分词器可以考虑一下,方法是进入 Elasticsearch 容器里面执行:
sudo bin/elasticsearch-plugin install analysis-smartcn
至于 Elasticsearch 不停机重建索引,这个一般适用于更新了 Gitlab 之后,发现搜索搜不了了,或者出现故障的时候,需要重建索引,就点一下,不要点的太频繁。
等所有的项目索引完成,就可以快乐的搜索了!注意,一般开始索引实例之后,上图里面暂停 ElasticSearch 索引的选项会自动打开,如果你发现索引好了,可以取消勾选然后保存。
3)Zoekt 精确代码搜索
内部 Issue 链接:2025.08 Week 3: 给个人 Gitlab 新增代码精确搜索功能 (#8) · Issue · Musicminion/personal-plan
先简要介绍一下 Zoekt 的架构,Gitlab 和 Zoekt 是怎么配合的?Zoekt 会运行一个 indexer,负责构建索引。Zoekt indexer 启动之后,会去找 Gitlab 一直拉取自己的作业需求,比如知道我现在要索引 /root 下面的所有仓库,然后他就会通过 Gitaly 的 socket 接口去拉取这个对应的数据。这就会导致两个问题:
Zoekt indexer 需要和 Gitlab 共享数据目录(socket 通信目录等)
需要解决权限问题,UID/GID 需要手动配置
此外,Zoekt 会把自己的 URL 通过心跳包的形式发送给 Gitlab,当用户搜索的时候,Gitlab 通过心跳包里面获得的 URL,然后发送给 Zoekt web server。web server 搜索然后返回结果理论是这样。
Gitlab 的 Zoekt 容器的版本和 Gitlab 基本算配套发布的。
关于 Docker compose 部署 Zoekt 的流程,参考这里:example/docker-compose · main · GitLab.org / Gitlab Zoekt · GitLab,但是其实这个有些问题,有个环境变量没写会导致一直启动失败。
配置文件里面的几个问题:
你需要找到 gitaly 目录,然后 ls -ln 看一下权限,一般 docker 部署的权限号码应该是 998
gitlab_shell_secret 这个文件可能需要你手动去找一下 gitlab 的这个文件的位置,find / 一下理论有,我这里也记不清楚,但是你最好拷贝一份放给 zoekt 专门用
git.example.com 就是你的 Gitlab 的 URL
最重要的一个事情:Gitlab 主容器里面的 /var/opt/gitlab 这个挂载到宿主机出后,你需要把挂出来的这个目录,重新挂载到 zoekt 里面去!这样才能保证里面可以通过 socket 文件访问 gitaly,因为需要拉取仓库数据。
gitlab:
# 其他配置
volumes:
- './gitlab/config:/etc/gitlab'
- './gitlab/logs:/var/log/gitlab'
- './gitlab/data:/var/opt/gitlab' # 注意观察 /var/opt/gitlab 的挂载点
# registry.gitlab.com/gitlab-org/build/cng/gitlab-zoekt:v18.2.4-fips
zoekt_webserver:
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-zoekt:v18.3.0
container_name: zoekt_webserver
restart: always
# 998 is the UID for ./gitlab/data/git-data/repositories/+gitaly
# use ls -ln to check
user: "998:998"
ports:
- 6070:6070
environment:
GITLAB_ZOEKT_MODE: webserver
WEBSERVER_PORT: 6070
GITLAB_URL: https://git.example.com
GITLAB_ZOEKT_SELF_URL: "http://zoekt_webserver:6070"
GITLAB_ZOEKT_VERSION: "v18.3.0"
GITLAB_ZOEKT_SECRET_PATH: /.gitlab_shell_secret
ZOEKT_ENABLE_DEBUG_LOGGING: true
volumes:
- ./zoekt/zoekt_index_data:/data/index
- ./zoekt/.gitlab_shell_secret:/.gitlab_shell_secret
- ./gitlab/data:/var/opt/gitlab # 注意模拟挂载到 /var/opt/gitlab
depends_on:
- git.ayaka.space
zoekt_indexer:
image: registry.gitlab.com/gitlab-org/build/cng/gitlab-zoekt:v18.3.0
container_name: zoekt_indexer
restart: always
# 998 is the UID for ./gitlab/data/git-data/repositories/+gitaly
# use ls -ln to check
user: "998:998"
ports:
- "6065:6065"
environment:
GITLAB_ZOEKT_MODE: indexer
SERVICE_URL: http://zoekt_indexer:6065
WEBSERVER_PORT: 6070
GITLAB_URL: https://git.example.com
GITLAB_ZOEKT_SELF_URL: "http://zoekt_webserver:6070"
GITLAB_ZOEKT_VERSION: "v18.2.4"
GITLAB_ZOEKT_SECRET_PATH: /.gitlab_shell_secret
ZOEKT_ENABLE_DEBUG_LOGGING: true
volumes:
- ./zoekt/zoekt_index_data:/data/index
- ./zoekt/.gitlab_shell_secret:/.gitlab_shell_secret
- ./gitlab/data:/var/opt/gitlab # 注意模拟挂载到 /var/opt/gitlab
depends_on:
- git.ayaka.space
mem_limit: 8g
cpus: '4'
这些工作做完之后,你就可以启动 zoekt,先看容器是否是正常启动的。然后你先可以进入 Web 界面开启:
精确代码搜索设置面板
在默认情况 Gitlab 18.2.4 之前,他默认只会给新的 Group 或者用户开启这个搜索,不会开启之前旧的名字空间的索引作业的,需要手动指定。比如 <top-level-group-to-index> 改成你的 root,就可以索引当前 root 用户下面的项目。
参考链接:Zoekt chart | GitLab Docs
# 需要进入 gitlab 容器然后执行下面的命令
gitlab-rails console
node = ::Search::Zoekt::Node.online.last
namespace = Namespace.find_by_full_path('<top-level-group-to-index>')
enabled_namespace = Search::Zoekt::EnabledNamespace.find_or_create_by(namespace: namespace)
replica = enabled_namespace.replicas.find_or_create_by(namespace_id: enabled_namespace.root_namespace_id)
node.indices.create!(zoekt_enabled_namespace_id: enabled_namespace.id, namespace_id: namespace.id, zoekt_replica_id: replica.id)
处理好后你应该可以看到索引已经重新建立,然后可以在 Web 界面愉快的搜索了。
4)Docker Registery Metadata
a)迁移教程
内部 Issue 链接:2025.08 Week 3: 给个人 Gitlab Registry 迁移到新的 metadata DB (#9) · Issue · Musicminion/personal-plan
首先,Gitlab 18.3 其实默认会运行一个迁移脚本,把容器镜像仓库迁移到新的 Meadata 数据库中,所以最推荐的做法是直接升级到 18.3。
对于旧版本的 Gitlab,如果不想升级,并且又需要开启容器镜像仓库的元数据数据库,需要参考论坛的链接:论坛教程链接
首先需要创建数据库专用用户(友情提示:建议看完 b 部分我写的迁移导致的问题在运行脚本):
gitlab-psql -c "CREATE USER registry WITH PASSWORD 'registrypassword'"
gitlab-psql -c "CREATE DATABASE registry_database WITH OWNER registry"
需要指定数据库的位置、允许外部用户链接:
registry['database'] = {
'enabled' => false, # Must be false!
'host' => '/var/opt/gitlab/postgresql/',
'user' => 'registry',
'password' => 'registrypassword',
'dbname' => 'registry_database',
'sslmode' => 'disable'
}
postgresql['custom_pg_hba_entries'] = {
registry_db: [
{
type: 'local',
database: 'registry_database',
user: 'registry',
method: 'md5'
}
]
}
然后配置好之后,参考教程:Gitlab Docs | 开启容器镜像仓库元数据数据库。
# 注意这里 storage 的配置,因为我用的本地存储,所以要指定目录:
registry['storage'] = {
'filesystem' => {'rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry'}
'maintenance' => {
'readonly' => {
'enabled' => true # Must be set to true.
}
}
}
然后运行迁移脚本:
sudo -u registry gitlab-ctl registry-database migrate up
sudo -u registry gitlab-ctl registry-database import --log-to-stdout
因为在 Docker 里面 sudo 是不可用的,直接可以删掉 sudo -u registry 这一部分的,如果不是 Docker 部署,直接在物理机器运行就可以。
迁移完成后,原来的用 false 就可以关闭了,此外 'maintenance' 那一栏目也需要删掉,这样用户可以读写了。
registry['database'] = {
'enabled' => true,
'host' => '/var/opt/gitlab/postgresql/',
'user' => 'registry',
'password' => 'registrypassword',
'dbname' => 'registry_database',
'sslmode' => 'disable'
}
迁移完成后,容器镜像仓库是可以自动做垃圾回收 GC 的了,理论来说磁盘空间可以节省不少。
发现容器的数据统计不对,需要参考 Gitlab 的教程:Gitlab Issue | 容器镜像仓库 size 为 0 的原因修复,意思就是这个容器镜像占用的空间是静态更新的,或者通过 notifer,配置 notifer 教程在这 配置 Notifer 的官方文档。所以需要再 Gitlab 的配置文件里面添加:
registry['notifications'] = [
{
'name' => '<test_endpoint>',
'url' => 'https://<gitlab.example.com>/api/v4/container_registry_event/events',
'timeout' => '500ms',
'threshold' => 5, # DEPRECATED: use `maxretries` instead.
'maxretries' => 5,
'backoff' => '1s',
'headers' => {
"Authorization" => ["<AUTHORIZATION_EXAMPLE_TOKEN>"]
}
}
]
gitlab_rails['registry_notification_secret'] = '<AUTHORIZATION_EXAMPLE_TOKEN>' # Must match the auth token in registry['notifications']
这个 notifer 本质是一个 webhook,当用户推送 docker 镜像之后,就会触发一个作业 worker 的请求,重新统计当前仓库的 Docker 镜像大小。
其中 <AUTHORIZATION_EXAMPLE_TOKEN> 需要随机生成的 32 位字符,生成命令如下:
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32 | sed "s/^[0-9]*//"; echo
配置好之后,当你推送镜像,就会触发这个重新计算的操作,并不是立刻显示数据,时延大概是 5 min。效果如下所示,可以看到你用的用量占用了多少 size 了。
使用量配额效果图
b)迁移导致的问题
补充:如果你已经做了迁移,在更新到 18.3 的时候,注意一个小问题。就是 18.3 的迁移脚本会默认创建一个新的用户。更新之后我发现自己 gitlab 容器镜像数据 30 多 G 全丢,然后排查了很久。
其实按照我前面的迁移教程我已经做了一次了,但是 18.3 启动脚本里面又做了一次迁移。按照我之前的配置,迁移脚本告诉我数据库密码不对。所以启动失败。我首先直接偷懒,把之前配置的数据库用户名密码的部分全都删了,然后发现迁移脚本启动成功了就没管。晚上发现数据都不在了(
然后一看数据库里面有两个 registry 的数据库(一个叫registry,一个叫registry_database),显然registry_database 是新创的,但是巧妙的是,新创的默认 db 的用户名是和我之前做迁移的时候用户完全一样的,所以推测启动迁移脚本可能创建了一个用户,然后正好把这个密码给改掉了,导致迁移脚本告诉我数据库密码不对。所以启动失败。所以我就把用户名密码都删除了。
Gitlab 的数据库的示意图如下:
GitLab 数据库列表
然后创了另外一个用户registry_user,把数据库的 Owner 修改到新的用户registry_user,然后修改 Gitlab 里面的配置文件,所有的数据终于回来了。
内部 Issue 链接:2025.08 Week 3: 修复 Gitlab 更新到 18.3 之后容器镜像仓库 Metadata 的问题 (#13) · Issue · Musicminion/personal-plan