4.5. GitLab 平台部署
GitLab 不只是一个 Git 仓库,它把代码托管、合并请求、CI 流水线和制品仓库塞进了一套应用。本章用仓库里的清单把它跑起来,重点讲清「哪些数据必须持久化」,以及从实验形态走到生产形态还差什么。
- 层级:
L4 · 实战
- 难度:
进阶
- 预计:
60 分钟
- 前置:
工作负载、Service 与 Ingress、存储与持久化
4.5.1. 你将学到
GitLab 的 webservice、sidekiq、gitaly、runner、redis、postgresql 各自负责什么
deploy/与deploy-persistent/的差异,以及 emptyDir 到底丢了什么如何拿到初始 root 密码、如何用 Ingress 暴露 Web 与 SSH
备份、SMTP 与资源评估的基本做法
4.5.2. 为什么需要它
自建 GitLab 的动机通常不是「省钱」,而是代码不能出内网、CI Runner 要跑在自有硬件上、审计日志要自己留。一旦这个平台承载了团队每天的 push 和 merge,它就从「一个 Web 应用」变成了有状态的基础设施:仓库数据丢了,代码就没了;数据库丢了,issue 和 MR 历史就没了。
仓库里的示例为了教学做了极大的简化:用 sameersbn/gitlab 这个 all-in-one 镜像,把 Rails、Sidekiq、Gitaly 全塞进一个容器,再配一个 PostgreSQL 和一个 Redis。这能让你在十分钟内看到登录页,但它和官方 Helm Chart 的架构差得很远。
组件 |
职责 |
官方 Chart 中的形态 |
本仓库的实现 |
|---|---|---|---|
webservice |
Rails/Puma,提供 Web UI、API、Git HTTP |
独立 Deployment,可多副本 |
与 sidekiq、gitaly 同容器 |
sidekiq |
异步任务:流水线调度、邮件、清理 |
独立 Deployment |
同容器内由 runit 拉起 |
gitaly |
Git 仓库的实际存储与 RPC 服务 |
StatefulSet + 独立 PVC |
同容器,数据在 |
gitlab-runner |
执行 CI Job |
独立 Deployment(需单独注册) |
未包含,需自行部署 |
redis |
缓存、Sidekiq 队列 |
外部 Redis 或 Chart 内置 |
|
postgresql |
用户、项目、issue、MR 等元数据 |
外部 PG 或 Chart 内置 |
|
4.5.3. 核心概念
4.5.3.1. 两种部署形态的差异
deploy/ 与 deploy-persistent/ 的清单几乎逐行相同,差异只有一处:卷的来源。
对象 |
|
|
|---|---|---|
gitlab 的 |
|
PVC |
postgresql 的 |
|
PVC |
redis 的 |
|
PVC |
依赖 |
无 |
StorageClass |
emptyDir 的生命周期跟随 Pod:Pod 被删除、被驱逐、节点重启导致重建,数据全部消失。所以非持久化版本只适合验证「能不能启动」,一旦你建了仓库、推了代码,就不该再删 Pod。
图:请求从 Ingress 进入唯一的 gitlab Pod,三份状态分别落在三个 PVC 上;任何一份 PVC 丢失都意味着平台不可恢复。
4.5.3.2. 配置是怎么注入的
gitlab 容器完全靠环境变量配置,关键几组:
GITLAB_SECRETS_*:四个密钥,用于加密数据库中的敏感字段、会话、2FA。上线后不能改,改了已加密的数据就解不开。DB_*、REDIS_*:数据库与 Redis 地址,账号密码来自 Secretgitlab的db_user、db_pass、db_name。GITLAB_HOST、GITLAB_PORT、GITLAB_SSH_PORT:生成克隆地址时用的外部主机名与端口。仓库写的是git.default.svc.cluster.local,服务名(应为gitlab)和 namespace(应为gitlab)都不对,克隆地址会指错。GITLAB_ROOT_PASSWORD:首次启动时设置的 root 密码,镜像默认值是5iveL!fe(至少 8 位)。仓库留空,等于沿用这个公开默认值。GITLAB_BACKUP_SCHEDULE=daily、GITLAB_BACKUP_TIME=01:00:内置备份计划。SMTP_*:仓库里SMTP_ENABLED=false,邮件默认发不出去。
警告
Secret 里的值不是占位符
deploy/secret.yaml 里的 base64 解码后是 gitlab / magedu.com / gitlab_production。这些是公开在仓库里的弱口令,实验可以照用,任何能被外网访问的环境都必须换掉;生产环境用 SealedSecret 或外部密钥管理,不要把明文或 base64 提交回 Git。
4.5.3.3. 存储与访问
持久化版本要求 openebs-hostpath 存在,或集群有默认 StorageClass。PVC 的 accessModes 都是 ReadWriteOnce,因为 gitlab、postgresql、redis 都是单副本;如果后面把 gitlab 扩到多副本,RWO 卷会直接阻止第二个 Pod 挂载——这也是生产要把 Gitaly 拆出去的原因。
Service 是 type: LoadBalancer,没有 MetalLB 时 EXTERNAL-IP 永远是 <pending>,但 NodePort 仍可用,Ingress 也不受影响。
4.5.3.4. 资源评估
清单没有设置 requests/limits,调度器会把三个 Pod 随便塞。按百人以内团队的经验值,可以这样估:
组件 |
CPU |
内存 |
存储 |
说明 |
|---|---|---|---|---|
gitlab |
2–4 核 |
4–8 Gi |
10–50 Gi |
冷启动吃满 CPU,低于 2 核容易探针超时 |
postgresql + redis |
1.5 核 |
3 Gi |
15 Gi |
与仓库数据量同量级,redis 可丢但不能 OOM |
真正上线时把这些数字写进清单的 resources,并给 gitlab 单独留出 8–10 Gi 内存,否则 sidekiq 的批量任务会把它 OOMKill。
4.5.3.5. 生产化建议
清单离生产至少差四件事:拆组件(官方 Chart 把 webservice、sidekiq、gitaly 分开)、外置 PostgreSQL 与 Redis、把备份落到对象存储、补 TLS 与 runner 隔离。顺序上先拆存储与数据库,再谈多副本——Gitaly 不拆,扩副本没有意义。
4.5.4. 动手实验
实验:从零跑起 GitLab,再从临时存储切到持久化
目标:完成一次「能登录、能推代码、重启不丢数据」的部署,并理解两次部署之间丢了什么。
步骤 1 · 检查前置条件
kubectl get storageclass
kubectl get ingressclass
至少要有一个默认 StorageClass 和 nginx IngressClass。
步骤 2 · 创建 namespace
kubectl create namespace gitlab
kubectl get ns gitlab
步骤 3 · 部署非持久化版本
kubectl apply -f deploy/ -n gitlab
kubectl get pods -n gitlab -w
首次启动要初始化数据库、编译资源,livenessProbe 的 initialDelaySeconds 是 180 秒,此时往往只有 gitlab 还在启动,postgresql 与 redis 应已 1/1。等 gitlab 变成 1/1 Running 后再继续,否则登录页会 502。
步骤 4 · 设置已知密码并登录
仓库的清单没有设置 GITLAB_ROOT_PASSWORD。用一份 strategic merge patch 补上,同时把错误的 GITLAB_HOST 改对:
spec:
template:
spec:
containers:
- name: gitlab
env:
- name: GITLAB_HOST
value: gitlab.magedu.com
- name: GITLAB_ROOT_PASSWORD
value: "Magedu@12345"
kubectl patch deployment gitlab -n gitlab --patch-file gitlab-env-patch.yaml
kubectl rollout status deployment/gitlab -n gitlab
kubectl port-forward -n gitlab svc/gitlab 8080:80
HTTP/1.1 302 Found
Location: /users/sign_in
用 root / Magedu@12345 登录。没打补丁的实例初始密码是 5iveL!fe,新版 GitLab 会强制改密;这也是必须显式设置 GITLAB_ROOT_PASSWORD 的原因。
步骤 5 · 切换到持久化版本
先确认当前没有任何 PVC,再删除旧对象。这一步会丢掉非持久化版本里的所有数据,包括刚建的仓库:
kubectl get pvc -n gitlab
kubectl delete -f deploy/ -n gitlab
kubectl apply -f deploy-persistent/ -n gitlab
kubectl get pvc,pods -n gitlab
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
persistentvolumeclaim/gitlab-pvc Bound pvc-3f1c... 10Gi RWO openebs-hostpath
persistentvolumeclaim/postgresql-pvc Bound pvc-8a2d... 5Gi RWO openebs-hostpath
persistentvolumeclaim/redis-pvc Bound pvc-9c4e... 5Gi RWO openebs-hostpath
如果 PVC 停在 Pending,先看 kubectl describe pvc gitlab-pvc -n gitlab 的 Events,多半是 StorageClass 名字不存在或没有默认 SC。
步骤 6 · 用 Ingress 访问
Ingress 已随清单创建,补一条本地域名解析即可:
kubectl get ingress -n gitlab
echo "192.168.1.240 gitlab.magedu.com" | sudo tee -a /etc/hosts # 换成 Ingress 入口 IP
curl -I http://gitlab.magedu.com
NAME CLASS HOSTS ADDRESS PORTS AGE
gitlab nginx gitlab.magedu.com 192.168.1.240 80 6m
用 curl -I 看到 302 跳到 /users/sign_in 就说明 Ingress → Service → Pod 链路通了。集群外的机器要访问,需要把 gitlab.magedu.com 解析到 Ingress Controller 的入口地址(LoadBalancer IP 或任一节点的 NodePort)。
步骤 7 · 备份与 SMTP
kubectl exec -n gitlab deploy/gitlab -- gitlab-rake gitlab:backup:create
kubectl exec -n gitlab deploy/gitlab -- ls -lh /home/git/data/backups
备份文件在数据卷里,和源数据同生共死,必须再拷出去:
kubectl cp gitlab/gitlab-6b7c9f7d4b-2xk9p:/home/git/data/backups/1704067200_2024_01_01_17.3.1_gitlab_backup.tar ./gitlab-backup.tar
开 SMTP 只需把 SMTP_ENABLED 改成 "true",并补齐 SMTP_USER、SMTP_PASS;仓库已在 Secret 里留了 smtp_user / smtp_pass 两个 key,改完滚动更新即可。
4.5.4.1. 验证清单
kubectl get pods -n gitlab三个 Pod 全部Running且READY 1/1kubectl get pvc -n gitlab三个 PVC 全部Bound用
root能登录,右上角能看到管理员区域gitlab-rake gitlab:backup:create产出的 tar 包非空gitlab-rake gitlab:backup:create产出的 tar 包非空
4.5.5. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
gitlab Pod 反复重启 |
启动慢被 liveness 杀掉;数据库未就绪 |
调大 |
|
Secret 中 |
改 Secret 后必须重建 postgresql 数据卷,否则旧库密码不匹配 |
默认密码 |
新版禁止默认密码,或实例已改密 |
用 |
PVC |
StorageClass 不存在、没打默认标记、 |
|
克隆地址是 |
|
改为实际域名或 |
SSH 克隆 22 端口被拒 |
节点 22 被 sshd 占用;Service 未暴露 22 |
用 HTTP 克隆,或换 |
备注
关于旧 API
仓库里的 Ingress 已经是 networking.k8s.io/v1,pathType 也写了 Prefix,在 v1.31 上可直接用。若你从更早的资料里抄到 extensions/v1beta1 的 Ingress,需要改三处:apiVersion 换成 networking.k8s.io/v1、serviceName/servicePort 换成 backend.service.name/number、补 pathType。同理,Service 的 type: LoadBalancer 在裸机上要依赖 MetalLB,否则只会得到 <pending>。
4.5.6. 进阶挑战
把 PostgreSQL 换成集群外实例(或 CloudNativePG),验证删掉
postgresqlDeployment 后 GitLab 仍可登录,并说明为什么这能减少一次故障域。给 GitLab 加一个
gitlab-runnerDeployment,用kubectl create secret generic存 registration token,注册一个dockerexecutor 的 runner,跑通一条.gitlab-ci.yml。用
cert-manager给gitlab.magedu.com签发证书,把 Ingress 改成 TLS,并确认git clone的 HTTPS 地址自动变成https://。
4.5.7. 小结
GitLab 的六类组件里,真正有状态的是 gitaly 的仓库目录和 postgresql 的元数据,redis 可以丢但会导致队列任务重放。
本仓库用 all-in-one 镜像换来部署简单,代价是单点、难扩、升级粒度粗;生产应换成官方 Chart 或 GitLab Operator。
deploy/与deploy-persistent/只差卷的来源,但这一处差异决定了「实验」和「能用」的分界线。