GitLab和Gerrit都部署好了,是不是就觉得系统已经能直接交给团队用了?我之前的经验是,部署完成恰恰只是开始,后面这一堆“收尾工作”才是决定这套代码协作体系能不能真正稳定跑起来的关键。尤其是GitLab和Gerrit两套系统同时存在时,它们的定位完全不同:GitLab负责代码托管、CI/CD和日常开发分支的管理,Gerrit则专注在代码评审环节,强制每次提交都经过Review才能合入。这两者如何配合、各自的日常维护怎么做、团队成员怎么顺畅地拉代码提评审,都需要在部署完成后一项一项落实。
这篇文章是“部署后的工作”系列的第二篇,重点分享我在实际运维和使用中踩过的坑、验证过的方案。适合刚部署完GitLab和Gerrit、正准备把它推向团队使用的人,也适合已经在用但被各种配置和权限问题折磨的维护者。我会从初始化检查、日常运维、Gerrit使用细节、CI/CD接入、常见问题排查这几个维度展开,尽量把能直接抄作业的步骤和命令都写清楚。
1. 部署完成后的第一轮检查与账户初始化
1.1 部署完先别急着建项目
很多人在GitLab和Gerrit装好后第一件事就是创建仓库、拉代码,然后发现各种奇怪的问题。我建议先花半小时做一轮基础检查,否则后面出了问题很难定位是部署的问题还是配置的问题。
先看GitLab。如果你是拿Docker部署的,第一件事是确认容器状态和初始密码。GitLab首次启动时会自动生成一个初始root密码,存放在 /etc/gitlab/initial_root_password 文件里,这个文件默认会在24小时后被删除。很多人第一次登录时找不到密码,就是因为拖得太久。正确做法是部署完立刻进去改密码:
bash复制docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
# 登录后立即修改root密码
docker exec -it gitlab gitlab-rails runner "user = User.find_by(username: 'root'); user.password = '你的新密码'; user.password_confirmation = '你的新密码'; user.save!"
Gerrit这边的初始化相对简单,用 review-site 目录启动后,默认管理员账号通常是在初始化时通过 --admin-user 参数指定的。比如我用的是:
bash复制java -jar gerrit.war init --batch --dev -d review_site
这种方式下管理员账号默认没有密码,第一次SSH登录Gerrit时需要立即通过 ssh -p 29418 管理员账号@gerrit服务器 执行命令来设置密码,或者通过网页端登录后设置。这一步很多人会跳过,结果后面配置Webhook或CI时怎么都认证不过去。
1.2 项目可见性默认策略:internal/private与关闭guest访问
部署完GitLab,默认的创建项目权限会比较开放。如果你不想让外部人员随便看到仓库内容,项目可见性建议直接统一设置为 internal 或 private。internal的意思是登录用户都能看到,private则只有被显式授权的成员能看到。
实际操作中我建议把全局默认设置改为内部可见,再按项目级别严格控制。修改方式是:
- 进入 Admin Area → Settings → General → Visibility and access controls
- 把 Default project visibility 改为 Internal
- 同时取消勾选 Allow users to create projects 这个选项,避免普通用户自己乱建一堆公开项目
关闭guest访问也很重要。GitLab里guest角色默认能看到项目内的公开Issue和Wiki,如果仓库是internal,guest也能看到代码。我的做法是从项目成员里彻底移除guest角色,最低只给Reporter权限,并且要求所有项目都要明确指定成员。
1.3 内存与资源评估
GitLab是出了名的内存大户,部署完以后一定要先盯一下资源占用,否则跑一段时间之后会因为内存不足直接OOM,现象就是网页打不开、git push超时。我见过一台4G内存的服务器跑GitLab 16.x,刚启动就吃掉3.2G,还没开始用就频繁告警。
如果内存紧张,有几个立竿见影的优化手段:
- 关闭不需要的Prometheus监控:编辑
/etc/gitlab/gitlab.rb,把prometheus_monitoring['enable'] = false - 减少Unicorn/Puma worker数量:设置
puma['worker_processes'] = 2 - 关掉Grafana:
grafana['enable'] = false
Gerrit是Java应用,对内存的控制相对稳定,但如果不设置JVM堆大小,默认会按物理内存的1/4来跑,建议在 etc/gerrit.config 的 container 段明确指定:
ini复制[container]
heapLimit = "2g"
先把资源这块搞定,后面再谈功能配置才有意义。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. GitLab日常运维与版本升级
2.1 Docker方式升级的版本跳跃问题
GitLab官方推荐小版本升级要按顺序走,不能跨大版本直接跳。尤其是从旧版本升到新版本,如果版本跨度太大,会触发数据库迁移失败,甚至损坏数据。我遇到过一个实际案例:有人从13.x直接跳到16.x,结果Docker容器反复重启,最后只能回滚备份。
如果是Docker部署,升级前必做的三件事:
- 备份:
docker exec -t gitlab gitlab-backup create - 备份配置文件:
docker cp gitlab:/etc/gitlab/gitlab.rb ./gitlab.rb.bak - 确认升级路径:查看官方升级路径文档,按大版本逐级升级
举个例子,如果当前是13.12,目标是16.x,建议路径是13.12 → 14.0 → 14.10 → 15.0 → 15.11 → 16.0,每到一个大版本节点先验证服务正常,再继续升。Docker的 upgrade 命令虽然能拉最新镜像,但跨版本时还是手动控制更稳:
bash复制docker stop gitlab
docker rm gitlab
docker run --detach \
--hostname gitlab.example.com \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:16.11.3-ce.0
2.2 高危漏洞修复的基本思路
GitLab历史上出过不少高危漏洞,比如SSRF、存储型XSS、任意文件读取等。修复的核心思路其实很简单:及时升级到包含漏洞修复的版本,同时做好权限收敛。我看到很多人问“GitLab高危漏洞修复方案”,实际落地时就是两步:
- 第一步,查看官方Security Release公告,确认当前版本是否受影响,以及修复版本号是多少
- 第二步,按上面说的升级路径,把GitLab升到对应修复版本
另外有一些通用加固手段可以降低风险:
- 关闭匿名访问:确保
Allow authentication bypass相关配置关闭 - 限制外部登录方式:比如关闭Google OAuth、GitHub OAuth等不用的认证源
- 设置访问IP白名单:通过防火墙或GitLab自带配置限制管理端口的访问来源
需要提醒的是,不要为了修复漏洞跳版本升级,很多安全公告里的修复版本是在某个具体版本分支上的,直接跨主版本升级反而更容易出问题。
2.3 Restore时报无权限的问题
GitLab备份恢复是运维里最容易被忽略的一项,往往等到真出事才发现恢复不了。gitlab-backup restore 时报无权限,最常见的原因是备份文件的所有者不是 git 用户。
GitLab在恢复时需要读取备份目录下的文件,如果备份是通过 docker cp 复制出来的,文件所有者通常是宿主机用户,放进容器后用户ID对不上,就会报类似 Permission denied 或 tar: Cannot open 的错误。
解决方式很简单,恢复前把备份目录权限改掉:
bash复制docker exec -it gitlab chown -R git:git /var/opt/gitlab/backups/
docker exec -it gitlab chmod 700 /var/opt/gitlab/backups/
然后执行恢复:
bash复制docker exec -it gitlab gitlab-backup restore BACKUP=时间戳
恢复完成后一定要执行 gitlab-rake gitlab:check 确认数据一致性,再重启服务。另外注意,恢复操作会覆盖当前数据,执行前必须确认当前实例上没有什么需要保留的新数据。
2.4 上传文件大小限制
GitLab默认对上传文件大小有限制,很多团队在推送大文件或者通过Web界面传附件时发现报错,多半就是这个限制导致的。修改方式是编辑 /etc/gitlab/gitlab.rb:
ruby复制nginx['client_max_body_size'] = "100m"
gitlab_rails['gitlab_max_file_size'] = 100
修改后执行 gitlab-ctl reconfigure 生效。需要注意,gitlab_max_file_size 的单位是MB,影响的是通过Git LFS、Web IDE或API上传的大文件。如果团队要存二进制资源,我还是建议启用Git LFS,而不是一味调大限制,否则仓库体积会快速膨胀,pull/push体验直线下降。
3. Gerrit侧的工作:拉代码、SSH Key与配置
3.1 Gerrit怎么拉代码
Gerrit和GitLab的使用习惯差异很大。第一次接触Gerrit的人最容易困惑的一点是:为什么我 git push origin master 被拒绝了?因为Gerrit的默认逻辑不允许直接推送到分支,必须推送到 refs/for/master,也就是评审引用。
完整的Gerrit提交流程是这样的:
bash复制# 先克隆代码
git clone ssh://用户名@gerrit服务器:29418/项目名.git
cd 项目名
# 创建新分支并提交
git checkout -b feature/my-feature
# 修改代码...
git add .
git commit -m "实现新功能"
# 推送到评审队列
git push origin HEAD:refs/for/master
推送成功后Gerrit会生成一个Change,评审人在网页上Review,打分,然后才能合入。我之前写过一篇系列的第一篇,里面有完整的仓库创建和权限组配置,这里就不重复了。
3.2 SSH Key用密钥还是公钥
这是一个经常有人问的问题,也是一个让不少新手白折腾半天的坑。Gerrit和GitLab一样,服务器上保存的是你的公钥,你自己本地留的是私钥。私钥一旦泄露,相当于账号丢了,所以本地私钥必须设置口令保护,并且不要复制到别的机器上。
生成密钥和配置公钥的步骤:
bash复制# 本地生成密钥对,如果已经有则跳过
ssh-keygen -t ed25519 -C "你的邮箱"
# 查看公钥内容
cat ~/.ssh/id_ed25519.pub
把公钥内容复制到Gerrit网页端 Settings → SSH Keys 里,保存即可。注意一定要复制 .pub 文件的内容,不是 id_ed25519 那个私钥文件。
如果在Gerrit服务器上配置SSH Key,还有个细节:Gerrit的SSH端口默认是29418,不是22。用 ssh -p 29418 登录时,如果客户端报 Permission denied (publickey),先检查公钥是否添加到Gerrit,再检查本地 ~/.ssh/config 是否配置正确。我一般会在 ~/.ssh/config 里加一段:
code复制Host gerrit
HostName gerrit.example.com
Port 29418
User 你的用户名
IdentityFile ~/.ssh/id_ed25519
这样用 ssh gerrit 就能登录,不用每次敲完整参数。
3.3 Gerrit配置文件要点
Gerrit的配置文件在 etc/gerrit.config,部署完成后最需要关注几个关键项:
gerrit.basePath:Git仓库的存储路径,默认是git目录,恢复备份时这个路径不能改sshd.listenAddress:SSH服务监听的端口,默认*:29418auth.type:认证方式,常见的是http或ldap。如果是本地账户认证,密码存在Gerrit自身的数据库里sendemail.smtpServer:邮件服务器配置,不配的话评审通知发不出去
另一个容易被忽略的是 etc/secure.config,里面存放数据库密码、SMTP密码等敏感信息。比如:
ini复制[auth]
registerEmailPrivateKey = 一串随机生成的密钥
[database]
password = 数据库密码
这个文件的权限必须设置成只有运行Gerrit的用户可读写,否则等同泄露凭据。
3.4 Contributor Agreement与权限组
Gerrit部署完成后还有一个容易漏掉的步骤:配置Contributor Agreement。很多团队没有开启这项,结果外部贡献者无法提交代码。开启方式是:
- 进入项目 → General → Contributor Agreement
- 勾选 New contributor agreements,然后创建一份ICLA或CLA协议
实际操作中,内部团队用不用协议都可以,但如果有外包或合作伙伴参与,建议开启。另外权限组也是Gerrit的一个大坑,默认的 Administrators 组有全部权限,Registered Users 组只有基础权限。我一般会单独建组:DevOps、Developer、QA,分别映射到每个项目的不同ref权限上,避免所有人都能直接 refs/for/* 以外的推送权限。
4. 让GitLab真正自动化起来:CI/CD与Webhooks
4.1 GitLab CI/CD最小可用实践
GitLab和Gerrit配合使用后,GitLab主要承担两件事:一是代码托管和归档,二是跑CI/CD流水线。很多团队把CI直接放在GitLab上,Gerrit只做评审流。这样分工的好处是,Gerrit专注评审逻辑,CI由GitLab的Runner执行,两者互不干扰。
要跑起GitLab CI/CD,先得有Runner。注册Runner的命令如下:
bash复制gitlab-runner register \
--url https://gitlab.example.com \
--token 项目的runner token \
--executor docker \
--docker-image alpine:latest \
--description "shared runner"
然后在仓库根目录创建 .gitlab-ci.yml,一个最简单的模板:
yaml复制stages:
- build
- test
build-job:
stage: build
script:
- echo "编译项目..."
- make build
artifacts:
paths:
- dist/
test-job:
stage: test
script:
- echo "运行测试..."
- make test
如果你是用Docker executor,注意Runner容器和GitLab容器要能互相访问。我之前遇到过Runner明明注册成功,但Job一直卡在pending,排查半天发现是Runner所在的网络无法访问GitLab实例的地址,后来在Runner的 config.toml 里设置了 network_mode = "host" 解决。
4.2 Webhooks如何配置
Webhooks是GitLab和外部系统联动的重要方式。比如Gerrit评审通过后,希望能触发GitLab的流水线,或者在GitLab上有新分支创建时,自动通知消息系统,这都需要配置Webhooks。
配置入口是:项目 → Settings → Webhooks。需要填写URL和Secret Token。一个典型的场景是把GitLab的push事件推送给内部的自动化部署平台:
- URL:
https://deploy.example.com/webhook/gitlab - Secret Token:一个随机生成的字符串
- Trigger:勾选Push events、Merge request events
勾选Push events后,每次push都会向URL发送一个POST请求,请求头里包含 X-Gitlab-Token,接收方验证这个Token即可。调试Webhook时,GitLab自带一个“Test”按钮,可以模拟push事件并查看响应结果,这个非常实用。
我在配置Webhooks时踩过的坑主要有两个:
- 没有设置Secret Token,导致任何知道URL的人都能伪造请求触发部署
- 本地测试时Webhook发不到内网地址,原因是GitLab默认禁止向本地网络发送Webhook,需要在 Admin Area → Settings → Network → Outbound requests 里勾选允许访问本地网络
4.3 AI Code Review接入GitLab的思路
GitLab本身有Code Quality功能,但那是基于规则的分析。现在很多团队在尝试接入AI Code Review,让机器先帮人过一遍代码。大致思路是:用GitLab Webhook监听Merge Request事件,事件触发后,调用大模型API对变更代码进行审查,再把结果以评论的形式通过GitLab API贴回到MR里。
落地时需要注意几点:
- 审查是针对
changes,也就是diff内容,不要直接把整个仓库丢给模型,成本和效果都不行 - 请求大模型API时要控制并发,避免Webhook风暴把服务打挂
- AI审查的评论要标注清楚是机器生成的,人工Review仍然不可替代
这种方式适合作为代码Review的辅助环节,尤其是重复性的规范检查、潜在空指针、缺少错误处理这类问题,AI通常能抓得比较准。但它需要的是一次一次跑大模型的开销,以及一个稳定的Runner来执行,放到我们的CI/CD体系里是顺理成章的。
5. 日常使用中的常见问题与排查清单
5.1 登录失败/API Token失效
热词里有一个很典型的报错信息:login failed. check api token or gitlab version. log in via git if the versi。这个报错我在用Python脚本调用GitLab API时遇到过,原因多半是API Token失效,或者GitLab版本升级后API接口不兼容。
排查思路:
- 先在网页端确认Token是否过期。GitLab个人访问Token可以设置有效期,过期后调用API就会报这个错误
- 再确认Token的scope是否包含所需权限,比如调用Merge Request相关接口需要
apiscope - 最后确认GitLab版本与代码里使用的API版本是否兼容。GitLab有时会废弃某些旧API字段,导致脚本拿到不同于预期的响应
如果是通过 git 命令访问,报这个错通常是HTTP凭据过期了。Linux下可以执行 git config --global --unset credential.helper 后重新推送,让Git重新询问用户名密码或Token。
5.2 分支和仓库管理
热搜词里还有“gitlab要先commit代码,然后再push吗”“gitlab删除分支”“gitlab删除仓库”。这三个问题虽然基础,但确实影响日常效率。
关于commit和push的关系:commit 是在本地生成一个提交记录,push 是把本地提交推送到远程仓库。Git不同于SVN的地方在于,commit并不影响远程仓库,只有push才会。所以正确顺序一定是:先 git add 把改动加入暂存区,再 git commit 生成提交,再 git push 推送到远程。如果只commit不push,别人是看不到你的改动的。
删除分支的命令:
bash复制# 删除本地分支
git branch -d feature/old-branch
# 删除远程分支
git push origin --delete feature/old-branch
删除仓库在GitLab网页端是:项目 → Settings → General → Advanced → Delete project。注意,这个操作会连代码和所有Issue、MR记录一起删除,且默认不启用。需要在Admin Area开启允许删除功能,输入项目名称确认后才执行。我一般建议运维保留一个备份再删。
5.3 GitLab内存占用过大怎么办
这个问题在1.3小节提过一部分,这里补充一个排查命令:
bash复制docker stats --no-stream
看看是哪个进程吃内存。GitLab里有几个固定的高内存组件:prometheus、gitaly、puma、sidekiq。如果内存只有4G,我建议直接关闭prometheus和grafana,只保留核心的gitaly、puma、sidekiq。如果连gitaly都看着吃内存,可以检查是不是仓库数量太多,或者有仓库在执行GC。
另外GitLab 16.x之后默认开启了Puma线程池动态调整,建议显式配置:
ruby复制puma['worker_processes'] = 2
puma['min_threads'] = 4
puma['max_threads'] = 8
5.4 常见问题速查表
我整理了一份自己在实际使用中遇到过的排查清单,可以直接贴到运维文档里:
| 症状 | 常见原因 | 解决方式 |
|---|---|---|
login failed. check api token or gitlab version |
Token过期或scope不足 | 重新生成Token,确认api scope |
git push 被拒绝 |
Gerrit不允许直接推分支 | 改用 HEAD:refs/for/master 推送 |
SSH登录Gerrit报 Permission denied |
公钥未添加或本地用错私钥 | 确认公钥、私钥配对 |
| GitLab Restore报无权限 | 备份文件属主不是git用户 | chown -R git:git 后重试 |
| Runner Job一直pending | Runner注册错误或网络不通 | 检查Runner网络和Token |
| Webhook收不到请求 | 本地网络请求被拦截 | 在Outbound requests中允许 |
| GitLab启动后OOM | 内存不足或Prometheus开销大 | 关闭监控组件,调低worker数 |
| 上传大文件失败 | 上传大小限制太小 | 调整nginx client_max_body_size |
| Gerrit评审完代码没有合入 | 缺少 Code-Review+2 和 Verified+1 |
给评审人分配对应权限 |
5.5 一个容易被忽视的细节:Gerrit的“Verified”标签
Gerrit和GitLab一个很大的不同在于,Gerrit的合入条件默认包含两个标签:Code-Review 和 Verified。很多人配好了评审权限,评审人也给了 Code-Review +2,但代码就是合不进去,原因就是没有 Verified +1。
Verified 标签通常由CI系统打上,表示该Change通过了构建和测试。如果团队没有接CI,可以把 Verified 的执行权也交给评审人或者管理员。配置位置在项目或全局权限的 refs/* 下,给某组添加 Label Verified 权限:
bash复制# 示例:让Administrators组可以打Verified
set-project-permissions --group Administrators --ref refs/* --label Verified --range -1..+1
也可以建议团队在单机小团队场景下,直接给一个 验证人 角色,由测试人员在Gerrit网页端点击 Reply → Verified +1。这一步打通了,整个Gerrit的评审流程才算真正闭环。
结尾:一点个人经验
部署后的工作千头万绪,但我每次接手一套刚部署好的GitLab和Gerrit,都会按同样的顺序去处理:先看资源占用,再定权限策略,然后跑通一条完整的代码提交评审合并链路,最后才去弄CI/CD和Webhook。这条链路一旦通了,后面所有的工作都是在这个基础上做加法。
另外我个人的一个小习惯是:在GitLab和Gerrit上都开一个专门的“运维记录”项目或Change,把每次升级、配置变更、问题处理的过程记录下来。这套系统平时看着不复杂,但半年之后回看当时的配置,如果没有记录,基本只能靠猜。文档不一定写给团队看,更多是写给三个月后的自己看。
GitLab和Gerrit这一组合,一个管托管和自动化,一个管评审和把关,配合好了会让代码流转非常顺畅。如果在踩坑过程中有什么新的问题,欢迎在实际环境中多试、多验证,毕竟每个团队的网络架构和权限模型都不一样,最终跑顺了才是最重要的。
