☰
Docker Compose 部署 GitLab:资源规划、持久化、备份升级与排障实战
2026/10/26 5:30:07 网站建设 项目流程

自建一套代码托管平台这事,我前后折腾过好几轮,从最早在物理机上直接装 Omnibus 包,到后来用虚拟机做模板,再到这几年稳定在容器里跑 docker 部署 GitLab,路子越走越窄,但也越走越稳。容器化最大的价值不是"新潮",而是把一整套 Ruby on Rails、PostgreSQL、Redis、Gitaly、Sidekiq、Nginx 打包成一个可复现的单元,换机器、做迁移、搞回滚,心里都有底。2024 年这套流程已经相当成熟,GitLab 16.x 到 17.x 的镜像结构基本没变,但细节上踩的坑换了一批,比如 Puma 全面替代 Unicorn 之后的内存曲线、PostgreSQL 版本升级带来的恢复兼容性问题。这篇内容写给三类人:第一次自建、想一次跑通不返工的新手;跑起来了但三天两头 502、备份不敢做、升级不敢动的半吊子维护者;以及需要把部署过程沉淀成文档、交给别人接手的团队负责人。下面这些参数、命令、目录结构都是我在实际环境里验证过的,你可以直接照着改。

1. 为什么到今天还有必要自建 GitLab

1.1 托管服务很好,但有些约束绕不开

先把话说清楚:如果你的团队只有三五个人、代码不涉密、对审计没要求,那用现成的托管平台是最省事的,没必要为了"技术自主"给自己找活干。真正逼着人自建 GitLab 的场景,通常是这么几类。第一类是内网隔离环境,开发和构建都在没有外网出口的网段里,代码只能留在本地。第二类是合规与审计,要求代码、Issue、CI 日志、制品全部落在自己可控的存储上,出了问题能自己取证。第三类是成本结构问题,团队规模到了一定程度,按人头订阅的费用会变成一个不小的固定支出,而自建的成本是一条相对平坦的曲线。第四类是集成需求,GitLab 的 Webhook、CI Runner、容器镜像仓库、包管理仓库都能和内网已有的系统打通,托管服务在这方面的自由度有限。

我自己的判断标准很简单:代码是否需要长期留在自己能控制的物理介质上。如果需要,就自建;如果不需要,就别自建。这个判断能挡掉一大半没必要的运维负担。

自建之后要接受的现实是,你同时也接手了一份运维责任:升级、备份、监控、故障恢复都得有人管。所以部署方案的第一目标不是"跑起来",而是"以后好维护"。这也是我坚持用容器而不是源码编译的原因——容器把版本、依赖、启动方式锁死了,升级就是换镜像标签,回滚就是换回旧标签,中间不需要重新编译 Ruby 扩展。

1.2 容器部署的三条路线,怎么选

同样是 Docker,落地方式差别很大,直接决定了以后维护的舒服程度。

路线典型做法优点代价适用场景
单容器 all-in-onedocker run一条命令拉起gitlab/gitlab-ce上手最快,五分钟能跑命令行参数长,改配置要重新 run,容易漏参数个人测试、临时验证
Compose 编排一份docker-compose.yml描述全部参数配置可版本化、可复制、易回滚需要理解 YAML 和变量覆盖规则绝大多数自建场景
拆分组件GitLab 容器 + 外部 PostgreSQL + 外部 Redis + 外部对象存储单点故障可控,数据库可独立备份扩容架构复杂,排障链条长中大型团队、已有数据库运维能力

我用得最多的是 Compose 单容器方案。理由很实在:GitLab 的 Omnibus 镜像内部已经自带 PostgreSQL、Redis、Gitaly、Nginx,这些组件在同一个容器里由gitlab-ctl统一编排,互相之间的连接走本地 socket 或 127.0.0.1,稳定性和性能都不差。拆出去的唯一强理由是数据库备份和恢复的独立性,但那点收益在几百人以下的团队基本体现不出来,反而把"一条命令恢复"变成了"三步恢复还得对版本号"。

至于 Kubernetes,说实话我不推荐用它跑单实例 GitLab。GitLab 官方的 Helm Chart 面向的是水平扩展场景,要引入外部数据库、外部 Redis、对象存储,配置量比 Compose 大一个数量级。你只是想要一台能用的代码服务器,没必要先给自己修一条高速公路。

1.3 资源规划:算清楚再买机器

GitLab 是出了名的吃内存,这个必须提前算。官方给出的基线是:4 核 CPU + 8GB 内存起步,其中 8GB 里建议留 2GB 以上给 swap。低于这个数字不是跑不起来,是会在 push 大仓库、开合并请求、跑 CI 的时候随机 502。

我一般按这个公式估:

内存需求 ≈ 基础开销 2.5GB + Puma worker 数 × 每 worker 约 500MB + Sidekiq 并发数 × 每并发约 300MB + PostgreSQL shared_buffers(建议 内存的 15%) + 系统与页缓存预留 1GB

默认配置下 Puma worker 是 2 个、Sidekiq 并发 10 个,加起来大概 4.5GB,加上系统开销正好压在 8GB 线上。所以 8GB 机器上跑默认配置是安全的,但别再往上堆别的服务。如果机器只有 4GB,可以把 Puma worker 降到 1、Sidekiq 并发降到 5,代价是并发访问时响应变慢,但能活着。

磁盘方面,GitLab 本体镜像解压后接近 3GB,加上数据目录、日志、备份,我一般给系统盘 60GB 起步,数据盘单独挂一块,按"当前代码总量 × 3"预留——一份是仓库本体,一份是备份,一份是增长余量。备份目录最好能挂到独立的存储上,不然磁盘写满的时候,GitLab 会先因为日志写不进去而挂掉。

CPU 反而是最不敏感的资源,4 核足够日常使用,CI Runner 是另一回事,别和生产环境混在一台机器上,构建任务会把 IO 吃干净,导致网页端卡顿。

2. 部署前必须定下来的四件事

2.1 宿主机环境检查清单

动手之前先把这几项过一遍,能省掉后面一半的排障时间。

  • 内核版本与 Docker 版本:建议 Docker 24.x 以上,Compose 用 v2 插件(docker compose而不是docker-compose)。老版本 Compose v1 对depends_on和健康检查的支持有差异,容易出诡异问题。
  • SELinux 状态:CentOS 系默认开启,挂载卷必须加:Z标签,否则容器内进程读不了宿主目录,表现是启动卡在Waiting for GitLab to boot。
  • 防火墙与安全组:除了 80/443,SSH 端口一定要单独规划,见 2.3 节。
  • swap:8GB 内存的机器务必开 2-4GB swap,vm.swappiness调到 10 左右,让它只在真正吃紧的时候用。
  • 时间同步:装chrony或systemd-timesyncd,时间漂移会导致 TOTP 二次验证失败、CI 缓存过期判断异常。
  • 文件句柄上限:fs.file-max和容器的nofile都调高,GitLab 会开大量并发连接。

内核参数我一般直接写进/etc/sysctl.d/99-gitlab.conf:

vm.overcommit_memory = 1 vm.swappiness = 10 fs.inotify.max_user_watches = 524288 fs.file-max = 1048576 net.core.somaxconn = 1024

vm.overcommit_memory = 1这条不是可选项。Redis 在 fork 做持久化的时候需要申请一份和当前内存等量的地址空间,如果系统按严格策略拒绝,Redis 会报Cannot allocate memory,然后 GitLab 的缓存和会话就开始出问题,表现是登录后立刻掉登录态。

2.2 目录结构与持久化设计

容器是无状态的,这一点必须刻在脑子里。GitLab 容器里有三个目录必须映射到宿主机,缺一个都会在重启后丢数据:

容器路径内容丢失后果
/etc/gitlab主配置、gitlab-secrets.json、SSH 主机密钥密钥丢失后所有用户需重新配置 2FA,CI 变量无法解密
/var/opt/gitlab仓库数据、数据库、Redis、上传附件、制品全部业务数据丢失
/var/log/gitlab各组件的日志排障无据可查

我习惯在宿主机上建这样一个结构:

/srv/gitlab/ ├── config/ # 对应 /etc/gitlab ├── data/ # 对应 /var/opt/gitlab ├── logs/ # 对应 /var/log/gitlab └── backups/ # 单独挂一份给备份用

data目录建议单独挂一块盘或者一个逻辑卷,这样扩容的时候不用动系统盘。config目录必须单独做定期备份——很多人只备份了仓库数据,结果恢复的时候发现 CI/CD 变量全部失效、双重验证全部要重来,原因就是gitlab-secrets.json没备份。

注意:data目录不要放在 NFS 上跑。Gitaly 依赖文件锁和特定的 IO 语义,NFS 上会随机报could not read from repository或者fsync failed。如果非要用网络存储,只把backups目录放上去。

2.3 端口规划:SSH 是最大的坑

GitLab 默认用宿主机的 22 端口做 SSH 克隆,但宿主机自己也要用 22 做管理登录,冲突几乎是必然的。我的做法是:

  • 宿主机 SSH 保持 22,用于运维
  • GitLab 容器的 SSH 映射到 2222
  • external_url用 HTTPS 域名,gitlab_rails['gitlab_shell_ssh_port'] = 2222

映射关系写清楚:容器内始终监听 22,宿主机映射成2222:22。配置里那个gitlab_shell_ssh_port只影响 GitLab 页面上显示的克隆地址和~/.ssh/config提示,不改变容器内的实际监听端口。很多人只改了端口映射没改这个变量,结果网页上复制的 SSH 地址还是git@host:repo.git,一 clone 就超时。

HTTP 端口同理,80:80和443:443是最省心的映射,让 GitLab 自己管 TLS。如果你前面还有一层 Nginx 或负载均衡做统一入口,那就把容器映射成8080:80,在external_url里写完整的https://git.example.internal,然后在外层代理里补齐X-Forwarded-Proto、X-Forwarded-For、Host三个头。少了X-Forwarded-Proto,GitLab 会认为自己是 HTTP 的,重定向循环、WebSocket 连不上、邮件里的链接全是 http,全是这一个头引起的。

2.4 external_url 与后续迁移的关系

external_url是整个部署里最不该写错的一个变量。它决定了 GitLab 生成的所有绝对链接、Webhook 回调地址、CI 的克隆地址、OAuth 回调。一旦上线后再改,已经克隆下来的仓库 remote 地址全部要改一遍,Webhook 也要重新配。

我的建议是一开始就用域名,不要用 IP。哪怕只是在测试环境,也提前在 hosts 或者内网 DNS 里配好一个名字。域名换了改 DNS 就行,IP 换了那真是要动配置、动代码、动文档。

如果确实要改,流程是:改external_url→docker compose up -d重启 → 进容器gitlab-ctl reconfigure→ 观察gitlab-ctl status全部为run。至于已有仓库的 remote 地址,可以用 Git 的insteadOf配置批量替换,或者直接在每个仓库里git remote set-url。

3. Compose 编排文件的完整拆解

3.1 一份可以直接用的配置

下面这份是我目前在用的,改动主要在三处:时区、SSH 端口、内存调优。注释里写了每一项的意图。

services: gitlab: image: gitlab/gitlab-ce:17.4.1-ce.0 container_name: gitlab restart: always hostname: git.example.internal shm_size: '256m' environment: TZ: Asia/Shanghai GITLAB_OMNIBUS_CONFIG: | # 对外访问地址,必须带协议 external_url 'https://git.example.internal' # 关闭内置 Nginx 的自动 HTTPS 时,用它接管 # letsencrypt['enable'] = false # SSH 显示端口与宿主机映射保持一致 gitlab_rails['gitlab_shell_ssh_port'] = 2222 # 资源调优 puma['worker_processes'] = 2 puma['min_threads'] = 4 puma['max_threads'] = 8 sidekiq['max_concurrency'] = 8 postgresql['shared_buffers'] = '1GB' postgresql['max_worker_processes'] = 8 # 关闭监控组件,单机能省 1GB 内存 prometheus_monitoring['enable'] = false grafana['enable'] = false # 时区与备份保留 gitlab_rails['time_zone'] = 'Asia/Shanghai' gitlab_rails['backup_keep_time'] = 604800 # Git 仓库内置的 housekeeping 参数 gitlab_rails['gitlab_default_projects_features_container_registry'] = true ports: - "80:80" - "443:443" - "2222:22" volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/data:/var/opt/gitlab - /srv/gitlab/logs:/var/log/gitlab healthcheck: test: ["CMD", "curl", "-fs", "http://localhost/-/health"] interval: 30s timeout: 10s retries: 5 start_period: 180s

镜像标签我建议锁定到具体的小版本,比如17.4.1-ce.0,不要用latest。用latest等于把升级时机交给了镜像仓库,某天夜里docker compose pull一下,第二天上班发现数据迁移卡在一半。这套系统不需要追求最新,需要的是可预期。

shm_size给 256MB 是因为 PostgreSQL 和浏览器内核渲染都会用到共享内存,默认 64MB 在大仓库的差异视图里容易崩。

3.2 GITLAB_OMNIBUS_CONFIG 的生效机制

这个环境变量是理解整个部署的关键。容器启动时会把它里面的内容追加写入/etc/gitlab/gitlab.rb,然后自动执行一次gitlab-ctl reconfigure。也就是说,它就是gitlab.rb的片段,语法和 Omnibus 配置完全一致。

这带来两个重要推论。

第一,优先级问题。如果你的/srv/gitlab/config/gitlab.rb里也写了同样的配置项(比如之前手工改过),那么最终生效的是文件里靠后的那一条。因为环境变量的内容是追加到文件末尾的,会覆盖前面。所以千万不要两边同时维护同一个配置项,改的时候只改一处,我习惯只写在 Compose 里,gitlab.rb里保持干净。

第二,改配置不用重建容器。修改 Compose 文件里的GITLAB_OMNIBUS_CONFIG后,执行docker compose up -d会重建容器并触发 reconfigure。这个过程会重新读配置、重启受影响的服务,通常 1 到 3 分钟。这段时间网页端是 502,属于正常现象,别慌着重启。

reconfigure 之后一定要看一眼状态:

docker exec gitlab gitlab-ctl status

正常的输出里每一行都以run:开头。出现down:或者fail:就得看日志了。我看得最多的是这几个服务出问题:gitaly(权限或磁盘满)、postgresql(内存不够被 OOM kill)、puma(端口被占)。排查顺序建议是gitlab-ctl tail <服务名>,日志比任何猜测都有用。

3.3 数据卷权限的坑,绕不开就正面处理

宿主机目录和容器内用户 UID 对不上的问题,几乎每个新手都会撞一次。容器里的 GitLab 跑在git用户下(UID 998 左右,不同版本会变),而宿主机的目录是 root 建的。表现就是启动后卡在Configuring GitLab,日志里反复出现Permission denied @ dir_s_mkdir。

处理方式有两种,我推荐第二种。

第一种是粗暴的chmod 777,能跑,但会让宿主机上所有用户都能改数据,内网环境也许能接受,正式的机器上我不这么干。

第二种是让容器自己初始化目录。做法是先把宿主目录清空,直接docker compose up -d,让容器以 root 启动完成初始化,Omnibus 镜像的入口脚本会自动把目录改到正确的属主,然后降权到 git 用户跑主进程。之后再重启就不需要手工改权限了。注意,如果目录里有残留的文件(比如上一次启动失败留下的),初始化脚本会跳过 chown,所以清空重来比修修补补可靠。

SELinux 开启的机器上,卷挂载必须带:Z:

volumes: - /srv/gitlab/config:/etc/gitlab:Z

没加这个标签,容器进程会被 SELinux 拦住,日志里是Permission denied,但你去ls -l看权限明明是 777,这会让人怀疑人生。

注意:不要用docker cp往里塞文件来"修复",也不要在容器运行时用宿主机改data目录下的文件。Gitaly 有自己的缓存和索引,外部改动会让仓库处于不一致状态,后续的git fsck会报一堆悬空对象。

3.4 首次启动的时间预期与观察方法

第一次启动一定会慢,这不是故障。整个过程大致是:解压镜像 → 初始化目录 → 启动 PostgreSQL 并建库 → 执行数据库迁移 → 编译前端资源 → 启动 Puma 和 Sidekiq。在 4 核 8G 的机器上,我实测过大概 4 到 8 分钟,磁盘慢的话会到 12 分钟。

观察进度的正确姿势是跟着日志走:

docker logs -f gitlab

退出条件是看到这一行:

gitlab Reconfigured!

以及随后的 Puma 启动成功提示。这时候用健康检查接口确认:

curl -s http://localhost/-/health # 返回 GitLab OK

另外建议把/-/readiness也看一眼,返回的 JSON 里能看到每个组件的就绪状态。这两个端点是给负载均衡做探活用的,比curl /靠谱得多,因为首页会返回 302 到登录页,用状态码判断容易误判。

首次登录的 root 密码不是固定的,它在容器的/etc/gitlab/initial_root_password里:

docker exec gitlab cat /etc/gitlab/initial_root_password

注意:这个文件在首次 reconfigure 之后24 小时会被自动删除。拿到密码第一时间登录改掉,并且把新密码存进密码管理器。我见过有人隔了两天才想起来,只能进容器用gitlab-rake "gitlab:password:reset[root]"重设。

4. 上线之后:初始化配置与日常运维

4.1 第一次登录该做的六件事

登录进 root 账号之后,别急着拉人进来,先把这几项做完,后面能省很多事。

  1. 改 root 密码,并且考虑把 root 账号用于应急,日常管理另开一个管理员账号。root 是个通用名,被撞库的风险最高。
  2. 关闭公开注册。在管理区的 General → Sign-up restrictions 里取消Sign-up enabled。内网环境尤其重要,否则任何能访问到页面的人都能自己注册。
  3. 配置 SMTP。没有邮件,密码找回、合并请求通知、CI 失败告警全部失效,用起来会很难受。内网有邮件服务器的话直接填,没有的话可以接一个内部的邮件中继。
  4. 设置时区与语言。默认是 UTC,提交时间会看起来偏移 8 小时,虽然不影响功能,但看日志的时候容易误判。
  5. 建第一个项目并验证克隆,HTTP 和 SSH 两种方式都要试,这是验证端口规划是否正确的唯一办法。
  6. 配置备份任务并立即手动跑一次,别等到出事才第一次执行备份脚本。

SMTP 的配置写进GITLAB_OMNIBUS_CONFIG就行:

gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "mail.example.internal" gitlab_rails['smtp_port'] = 587 gitlab_rails['smtp_user_name'] = "gitlab@example.internal" gitlab_rails['smtp_password'] = "填入实际密码" gitlab_rails['smtp_domain'] = "example.internal" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['gitlab_email_from'] = "gitlab@example.internal"

改完重启容器,进容器用gitlab-rails console发一封测试邮件:

Notify.test_email('someone@example.internal', 'test', 'hello').deliver_now

报错信息会直接告诉你连不上还是认证失败,比在网页上点"发送测试邮件"得到的提示详细得多。

4.2 备份:做对这三个动作才算数

GitLab 的备份命令很简单,但只跑备份命令是不够的,这是最容易出事的地方。

完整的备份包含两部分:数据备份和配置文件备份。

# 1. 数据备份(在容器内执行) docker exec -t gitlab gitlab-backup create STRATEGY=copy # 2. 配置文件备份(宿主机执行) tar czf /srv/gitlab/backups/config-$(date +%F).tar.gz \ -C /srv/gitlab/config . # 3. 校验备份文件完整性 ls -lh /srv/gitlab/data/backups/

第一条命令产出的是一个 tar 包,名字形如1712345678_2024_04_05_17.4.1-ce.tar,放在/var/opt/gitlab/backups里,也就是宿主机的/srv/gitlab/data/backups。文件名里的版本号很重要,恢复时要用到。

第二条命令备份config目录,其中gitlab-secrets.json是绝对不能丢的。它里面存着数据库加密密钥、CI 变量的加密密钥、OAuth 应用的 secret。丢了它,数据库能恢复但里面的加密字段全是乱码,2FA 全部失效,Runner 全部要重注册。

注意:gitlab-backup不备份/etc/gitlab,也不备份容器镜像本身。所以完整的灾备应该是"数据备份 + config 备份 + 一份记录镜像标签的文档"。

恢复流程我写在这里,希望你永远用不上,但一定要提前读过一遍:

# 1. 停掉写入服务 docker exec gitlab gitlab-ctl stop puma docker exec gitlab gitlab-ctl stop sidekiq # 2. 把备份文件放到指定位置并确保属主正确 docker exec gitlab chown git:git /var/opt/gitlab/backups/<备份文件名> # 3. 执行恢复 docker exec gitlab gitlab-rake "gitlab:backup:restore BACKUP=<不带.tar的备份文件名前缀>" # 4. 恢复配置文件后重载 tar xzf config-xxxx.tar.gz -C /srv/gitlab/config docker exec gitlab gitlab-ctl reconfigure # 5. 重启并检查 docker exec gitlab gitlab-ctl restart

恢复时版本必须匹配。用 17.4 的备份恢复到 17.5 上是可以的(向上兼容),反过来不行。跨大版本恢复必须先恢复到原来的版本,再按升级路径逐步升上去。

备份拿去异地存一份这个习惯很重要。本地备份和原始数据在同一块盘上,盘挂了两份一起没。我一般用rsync每天推一次到另一台机器,或者挂一个对象存储做冷备。

4.3 升级:小步慢走,一次一个版本

容器部署的升级看起来只是换个镜像标签:

docker compose pull docker compose up -d

但 GitLab 的升级有硬性路径要求:不能跨小版本,不能跳大版本。官方文档里明确写了,升级必须按照17.0 → 17.1 → 17.2这样一步步走,跨大版本要先升到当前大版本的最后一个版本,再升到下一个大版本。

为什么这么严格?因为数据库迁移脚本是按顺序写的,跳过中间的版本,表结构就停在一个不存在的中间状态,后续的迁移脚本会失败,容器起来后卡在 reconfigure 阶段无限重启。

我的升级流程固定成这样:

  1. 先看官方升级路径页面,确认下一个版本号
  2. 手动跑一次完整备份(数据 + config),确认备份文件存在且大小合理
  3. 改 Compose 里的镜像标签,docker compose up -d
  4. docker logs -f gitlab盯着,直到Reconfigured!
  5. 登录验证:能打开首页、能克隆、能提交、CI 能跑
  6. 稳定运行一天后再考虑下一个版本

回滚更简单,把标签改回去重启就行,前提是那次升级没有执行不可逆的数据库迁移。所以升级后立刻验证很重要,别等到第二天出问题才发现,那时候数据库已经迁移过了,降级镜像启动会报 "database schema version mismatch"。

注意:升级前把data/backups里旧的备份清一清,但要保留至少两份,一份是升级前的,一份是更早的。磁盘写满导致升级失败是最冤的一种故障。

4.4 监控与日志:别等用户报障

自建的 GitLab 出了故障,通常第一个知道的是用的人,不是维护的人。所以基础的监控得配。

最省事的是把容器健康检查接进现有的监控体系。/-/health返回 200 就说明 Web 服务活着,/-/readiness的 JSON 里能看到gitaly、postgresql、redis的就绪状态。磁盘使用率单独盯一个指标,超过 80% 就该清理了。内存持续打满、swap 大量使用,是 Puma worker 太多的信号,调小worker_processes比加内存便宜。

日志用gitlab-ctl tail看,但别在高峰期一直开着,输出量很大。更实际的方案是把/srv/gitlab/logs挂进已有的日志采集体系,按大小做轮转:

logging['logrotate_frequency'] = "daily" logging['logrotate_rotate'] = 14 logging['logrotate_size'] = "200M"

不配轮转的话,production_json.log一个月能涨到几十 GB,把系统盘写满,然后 GitLab 因为写不了日志直接挂了,这种事故我见过不止一次。

5. 常见问题排查实录

5.1 启动慢、页面 502、容器反复重启

这是出现频率最高的一类问题,绝大多数根源是内存。

症状:docker ps显示容器是 Up,但页面一直 502;或者容器每隔几分钟重启一次;docker stats看到内存使用顶在峰值。

排查:

# 看容器是否被 OOM kill docker inspect gitlab | grep -i oom # 看内核日志有没有 kill 记录 dmesg | tail -50 | grep -i "killed process"

如果确实是 OOM,先把组件减掉:prometheus_monitoring['enable'] = false、grafana['enable'] = false这两项能省下接近 1GB。再把puma['worker_processes']降到 1、sidekiq['max_concurrency']降到 5。8GB 机器上做完这些通常就稳了。

另一个常见原因是 swap 没开。4GB 内存的机器不开 swap,基本没救。开了 swap 之后还要确认vm.overcommit_memory = 1,否则 Redis 还是会因为无法 fork 而拒绝启动。

还有一个隐蔽的原因:/var/opt/gitlab所在的分区用的是 overlay 或者某些网络文件系统,导致 PostgreSQL 无法使用fsync,性能掉到谷底,启动阶段耗时十几分钟。这种情况只能换到本地 ext4 或 xfs 分区上。

5.2 权限问题与卷映射失败

症状:容器启动后日志停在Configuring GitLab,不断刷Permission denied,或者 GitLab 起来了但推送代码时报remote: GitLab: API is not accessible。

排查顺序:

  1. 看宿主机目录属主:ls -ln /srv/gitlab/data
  2. 看容器内对应用户:docker exec gitlab id git
  3. 两者的 UID 必须一致

不一致的话,最干净的做法是停容器、清空目录、重来一次,让容器自己初始化。如果目录里已经有数据不能清,就手工改属主:

docker stop gitlab chown -R 998:998 /srv/gitlab/data chown -R root:root /srv/gitlab/config docker start gitlab

config目录属主是 root 而不是 git,这点容易被搞混,因为容器里的初始化和 reconfigure 是由 root 执行的。

5.3 SSH 克隆失败与端口冲突

症状:网页上能看代码,但git clone git@host:group/repo.git超时;或者提示Permission denied (publickey)。

第一个可能:宿主机 22 端口被 sshd 占了,容器根本没映射上。用ss -lntp | grep -E '22|2222'确认。

第二个可能:端口映射改了但gitlab_shell_ssh_port没改,网页给的地址不对。改了变量之后必须重启容器并等待 reconfigure 完成才会生效。

第三个可能:用户的公钥没加到 GitLab 账号里,或者加错了账号。这个最容易排查,网页端测试一下 HTTP 克隆就能区分是 Git 服务问题还是 SSH 认证问题。

第四个可能:客户端~/.ssh/known_hosts里存着旧的主机指纹。容器重建后 SSH 主机密钥变化,会报明确的REMOTE HOST IDENTIFICATION HAS CHANGED,删掉对应行就行。主机密钥保存在config/ssh_host_*里,只要config目录做了持久化,重建容器不会变。

5.4 速查表:现象、原因、处理

现象最可能的原因处理方式
页面 502,容器 Up内存不足,Puma 被 kill减组件、降 worker、开 swap
启动卡在 Configuring目录权限或 SELinux清空重来,或加:Z标签
Redis 报 Cannot allocate memoryvm.overcommit_memory未设为 1写 sysctl 并sysctl -p
登录后立刻掉线同上,或 secrets 文件丢失检查gitlab-secrets.json
邮件发不出SMTP 端口/认证方式不匹配用 rails console 发测试邮件看报错
克隆超时22 端口冲突映射 2222 并配shell_ssh_port
提交时间差 8 小时容器时区为 UTC设TZ与gitlab_rails['time_zone']
CI 变量在恢复后失效未备份gitlab-secrets.json从 config 备份中恢复并 reconfigure
升级后无限重启跳版本升级,迁移失败回滚镜像,按路径逐版本升
磁盘告警日志未轮转、备份堆积配 logrotate,设backup_keep_time
推送大仓库失败请求体超限调nginx['client_max_body_size']

最后一项多提一句,nginx['client_max_body_size']默认是 250MB,看起来够用,但如果你用 Git LFS 或者导入大型仓库,很容易撞上。调到 1GB 之前先想想为什么会有这么大的推送,很多时候是误把构建产物提交进去了,加个.gitignore比调参数更根本。

6. 我在实际维护中攒下的几点体会

跑了两三年下来,最深的感受是:这套系统的稳定性更多取决于"别乱动",而不是"配置得多精妙"。我见过太多人一上来就把 Puma 调到 8 个 worker、Sidekiq 开到 25 并发,觉得这样性能好,结果内存天天打满,页面时快时慢,还找不到原因。默认值其实是官方根据经验调过的,除非你有明确的瓶颈指标,否则别动它。

第二个体会是备份的可恢复性比备份本身更重要。我现在的习惯是每个季度挑一台闲置机器,从备份做一次完整的恢复演练,走一遍"拉镜像 → 建目录 → 恢复数据 → 恢复 config → 跑起来 → 验证 CI 变量还在"。这个演练做过两次之后,你对自己这套东西的边界就清楚了,真出事的时候不会慌。第一次演练大概率会失败,失败点通常是 secrets 文件或者版本号对不上,而这正是演练的意义。

第三个是别把 GitLab 和 CI Runner 放一台机器。Runner 跑构建的时候 IO 和内存都吃得很凶,和 GitLab 抢资源的结果是网页端卡顿、推代码超时。Runner 单独一把机器,或者放容器里做资源限制,成本不高但体验差别很大。

最后分享一个省事的小技巧:把常用的运维命令写成 Makefile 或者几个 shell 脚本,放到/srv/gitlab/下面。常用的无非是up、down、logs、backup、restore、status这几条,写成脚本之后,交接给别人的时候不用讲半天,一句./ops.sh backup就能跑。这个习惯比任何文档都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询