GitLab 运维实战:部署、权限模型与备份恢复

第一次接手 GitLab 是入职第二个月,前任运维交接时说了一句”这平台平时不用管,坏了再说”。结果第三周磁盘就写满了——仓库备份和日志把根分区撑爆,全组开发停工半天。从那以后我给 GitLab 定了两条规矩:磁盘单独挂盘并设监控,备份每天跑并且每月做一次恢复演练。这篇就是这套维护工作的完整记录。

一、运维和开发在 Git 上不是一回事

同样是用 Git,职责完全不同:

维度 开发工程师 运维工程师
角色 Git 的使用者 平台的维护者 + 规范制定者
日常 写代码、提交、分支、合并、解冲突 部署维护 GitLab、管权限、备份恢复、配 Runner
关心点 我的代码能不能提交合并 平台稳不稳、权限对不对、能不能审计
典型操作 add / commit / push / merge gitlab-ctl、gitlab-rake、备份恢复
输出 业务代码 流水线模板、分支规范、审计记录

落到日常工作里,运维侧大概有这几块:

  1. GitLab 平台运维:安装升级、备份恢复、磁盘监控;
  2. 用户与权限:对接 LDAP/AD,配置群组和项目权限;
  3. Runner 管理:注册执行器、打标签、区分不同环境的构建;
  4. 代码质量门禁:分支保护、强制 MR 审批、必须通过流水线才能合并;
  5. CI/CD 建设:写 .gitlab-ci.yml 模板、管构建产物、配 CI/CD 变量;
  6. 发布与回滚:合并 main 自动部署,出问题 revert 回滚;
  7. 规范设计:分支模型、MR 审查流程、.gitignore 与 Git LFS 管理。

二、先看清 GitLab 的组件地图

GitLab 是一个 Ruby/Rails 应用,单机安装也含七八个组件,排障的时候知道”哪个症状对应哪个组件”能省掉一半时间:

组件 作用 运维关注点
nginx 静态 Web 入口 external_url 决定访问地址和端口
gitlab-workhorse 轻量反向代理 大文件上传 / 下载加速
unicorn / puma Rails 应用服务 502 的常见来源,先看它的日志
postgresql 主数据库 备份恢复的核心
redis 缓存 / 队列 Sidekiq 依赖它
sidekiq 异步后台任务 队列堆积会拖慢全站
gitaly Git 仓库存储(RPC) 占磁盘大头,git 操作慢先查它

三、部署与关键配置

生产环境建议起步 4C8G,磁盘按仓库增长规划——我吃过的亏就是没给仓库单独挂盘。

1
2
3
4
5
6
7
8
# 1. 环境准备
systemctl disable firewalld # 或按需放行端口
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

# 2. 安装(rpm 方式)
yum -y install policycoreutils-python curl
curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | bash
yum -y install gitlab-ce # 离线环境用 rpm -ivh gitlab-ce-16.9.8-ce.0.el7.x86_64.rpm

然后是 /etc/gitlab/gitlab.rb 里几个必改项:

1
2
3
4
5
6
7
external_url 'http://10.0.8.200:8090'         # 访问地址和端口
nginx['listen_port'] = 8090 # 强制 Nginx 端口,避免和宿主服务打架
gitlab_rails['gitlab_shell_ssh_port'] = 2222 # SSH 克隆端口,22 被占用时用这个

# 备份路径与保留时间(这两个不配,早晚磁盘告警)
gitlab_rails['backup_path'] = '/data/backup/gitlab'
gitlab_rails['backup_keep_time'] = 604800 # 保留 7 天
1
2
3
4
5
6
7
8
9
10
# 3. 生效并自检
gitlab-ctl reconfigure
gitlab-ctl status # 所有组件状态
gitlab-rake gitlab:check SANITIZE=true # 全量自检

# 常用命令
gitlab-ctl restart
gitlab-ctl stop nginx # 单独停某个组件
gitlab-ctl tail # 看全部日志
gitlab-ctl tail postgresql # 看指定组件日志

初始密码在 /etc/gitlab/initial_root_password,24 小时后会被自动删除,第一次登录必须马上改密码,否则就只能 gitlab-rake "gitlab:password:reset" 来救了。

还有一个容易漏的开关:Webhook 要回调内网地址时,需要在”管理中心 → 设置 → 网络”里允许本地网络请求,否则推送事件根本发不出去。

四、代码怎么进 GitLab

两种常见方式,第一条是克隆空仓库再放代码,日常最省事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方式一:克隆空仓库 → 拷贝代码 → 提交推送(克隆自带远程地址)
git clone git@10.0.8.200:ops/game.git
cd game/
# 放代码(上传压缩包解压或直接拷贝)
git add .
git commit -m "game_v1"
git push -u origin master

# 方式二:本地已有仓库 → 关联远程 → 推送
mkdir git_data && cd git_data/
git init
touch a.txt && git add . && git commit -m "test_v1"
git remote add origin git@10.0.8.200:ops/game.git
git push -u origin master

SSH 公钥贴到”右上角头像 → Preferences → SSH Keys”,之后就免密推送了。

五、权限模型:用户 → 组 → 项目

GitLab 的权限核心是”用户 → 组 → 项目”三层,角色分 5 级:

角色 权限 典型对象
Guest 只能看 Issue,看不了代码 客户、外包旁观
Reporter 看代码、提 Issue 测试、产品
Developer 推分支、提 MR,不能直接推 main 开发
Maintainer 管项目设置、合并 MR、打 Tag 技术组长
Owner 项目全部权限 项目负责人 / 运维

生产环境的落地姿势:

  1. 按部门建组,比如 ops 组、dev 组,再在组内建项目,项目权限默认继承组;
  2. 开发给 Developer、组长 Maintainer、运维 Owner/Admin;
  3. 对接 LDAP/AD,账号统一从企业目录同步,禁止手工建号;
  4. main 分支开保护(Protected branches),开发推 main 直接拒绝,必须走 MR。

一个完整协作流程的样子(用 dev 账号走一遍):

1
2
3
4
5
6
7
8
9
10
# dev 用户在自己机器上克隆并配置提交身份
git clone git@10.0.8.200:ops/game.git
git config --global user.email "dev@example.com"
git config --global user.name "dev"

# 改代码后直接推 master 会被拒(分支保护生效)→ 建自己的分支推
git switch -c dev
git commit -am "首页改版"
git push -u origin dev
# 网页上发起 MR → 由 root/Maintainer 审查合并 → 删除源分支

这个”先被拒绝、再走 MR”的流程我建议在给新人开账号时当面演示一次,比发文档有效——自己撞一次分支保护,比看十遍规则记得牢。另外注意克隆方式:git@ 走 SSH 免密,http:// 方式每次要输账号密码,给部署脚本用的时候别搞混。

六、代码审计:这份活到底谁干

“审计开发的提交是不是运维的活”这个问题,分工是清楚的:运维搭平台和流程,代码内容由技术组长 / 资深开发审查。

审计层面 谁负责 做什么
代码内容审查(Code Review) 技术组长 / 资深开发 审查 MR 的代码质量、逻辑、安全
提交规范审计 运维 + 自动检查 提交信息格式、禁止密钥入库、限制大文件
操作行为审计(Audit Events) 运维 谁登录了、谁改了权限、谁删了分支
合规审计 运维 审计日志留存、操作留痕可追溯

运维侧要落地的几件事:

1
2
3
4
5
6
# 1. 审计事件:管理中心 → 审计事件,登录、权限变更、项目删除、分支保护变更都能查
# 定期导出归档(等保场景日志留存 6 个月起步)
# 2. 提交规范自动化:用 Push Rules 配提交信息正则,或 pre-receive 钩子拦截
# 3. MR 审批强制:main 分支保护 + 至少 1 人审批 + 流水线必须通过
# 4. 密钥防护:开启 Secret Detection,发现泄露先轮换密钥再清历史
# 5. 用 git log 审计具体改动:git log --since / --author

七、备份与恢复:每月必须演练一次

备份这件事的定义很简单:没验证过能恢复的备份等于没有备份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 手动备份(仓库 + 数据库 + 附件)
gitlab-rake gitlab:backup:create
# 产物在 /var/opt/gitlab/backups/,形如 EPOCH_日期_版本_gitlab_backup.tar

# 2. 定时备份(crontab 每天凌晨 2 点)
0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1

# 3. 恢复(只能恢复到与备份相同的 GitLab 版本!)
gitlab-ctl stop unicorn sidekiq # 停掉数据写入服务
gitlab-rake gitlab:backup:restore BACKUP=1700000000_2024_02_20_16.9.8
# 交互确认会提示会删除现有数据重建
gitlab-ctl restart
gitlab-rake gitlab:check SANITIZE=true

# 4. 迁移到新服务器 = 备份 + 恢复,这是最稳妥的迁移方式

三个必须记住的坑:

  1. 备份不包含 /etc/gitlab/gitlab.rb,配置文件要单独备份;
  2. 跨版本恢复不行,升级前先备份,恢复前确认版本号一致;
  3. backup_keep_time 控制保留份数,磁盘要按备份量规划——建议备份目录单独挂盘。

我的实际做法是:每天自动备份,每周人工看一眼备份文件大小是否正常,每月在测试环境恢复一次确认数据完整。第三步才是真正兜底的那步。

八、升级与 HA 的取舍

升级的官方硬规矩:只能跨 2 个次要版本。14.x 要先升 15.x 再升 16.x,不能一步跳过去。

1
2
3
4
5
6
# 升级流程
# 1. 备份当前版本(升级前必做)
# 2. 停服务:gitlab-ctl stop unicorn sidekiq
# 3. 装新包:yum -y install gitlab-ce-16.x.x
# 4. 生效:gitlab-ctl reconfigure && gitlab-ctl restart
# 5. 验证:gitlab-rake gitlab:check,再跑一遍关键流程(登录、克隆、推送)

HA 方面的建议就一条:不要一上来就堆 HA。单机版撑 500 用户以内没问题;规模上来再考虑 PostgreSQL/Redis 外置、应用多节点、Gitaly 集群。复杂度上去了,故障模式也跟着翻倍,先把”备份 + 恢复演练”做扎实,比什么高可用都实在。

九、常见故障排查

故障 常见原因 处理路径
页面 502 unicorn/puma 挂了、内存不足、磁盘满 gitlab-ctl status → tail unicorn → free -h / df -h
git 操作超时卡死 Gitaly 异常、仓库过大、磁盘 IO 高 tail gitaly → df -h → du -sh 仓库目录
提交被拒 分支保护、无推送权限 查分支保护规则,改走 MR
Runner 离线 机器故障、token 失效、网络不通 gitlab-runner verify → 重新 register
克隆报 Permission denied SSH 密钥没配或换了机器 ssh -T 测试 → 重新贴公钥
仓库越来越大 大文件入库、没跑 GC git gc、配置 Git LFS、限制 push 大小
磁盘满告警 备份/日志/仓库增长 df -h 定位 → 清理旧备份 → 日志轮转

排障三板斧,顺序别乱:

1
2
3
4
gitlab-ctl status                      # 1. 先看哪个组件 down 了
gitlab-ctl tail <组件名> # 2. 看对应组件日志(unicorn/postgresql/gitaly/redis)
gitlab-rake gitlab:check SANITIZE=true # 3. 全量自检
df -h && free -h && top # 资源三连,90% 的"平台慢"最后都落在磁盘或内存上

最后

维护 GitLab 这类平台,日常动作其实不多,但每一项都要按规矩来:权限最小化、备份加演练、升级别跳版本、排障先看 status 和日志。平台本身不难,难的是把这些动作坚持成习惯——这类基础设施出一次事,全组都跟着停摆。