GitLab 平台部署¶
GitLab 不只是一个 Git 仓库,它把代码托管、合并请求、CI 流水线和制品仓库塞进了一套应用。本章用仓库里的清单把它跑起来,重点讲清「哪些数据必须持久化」,以及从实验形态走到生产形态还差什么。
你将学到¶
- GitLab 的 webservice、sidekiq、gitaly、runner、redis、postgresql 各自负责什么
deploy/与deploy-persistent/的差异,以及 emptyDir 到底丢了什么- 如何拿到初始 root 密码、如何用 Ingress 暴露 Web 与 SSH
- 备份、SMTP 与资源评估的基本做法
为什么需要它¶
自建 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 | 同容器,数据在 /home/git/data |
| gitlab-runner | 执行 CI Job | 独立 Deployment(需单独注册) | 未包含,需自行部署 |
| redis | 缓存、Sidekiq 队列 | 外部 Redis 或 Chart 内置 | redis:6.2 Deployment |
| postgresql | 用户、项目、issue、MR 等元数据 | 外部 PG 或 Chart 内置 | sameersbn/postgresql:14 Deployment |
核心概念¶
两种部署形态的差异¶
deploy/ 与 deploy-persistent/ 的清单几乎逐行相同,差异只有一处:卷的来源。
| 对象 | deploy/ |
deploy-persistent/ |
|---|---|---|
gitlab 的 /home/git/data |
emptyDir: {} |
PVC gitlab-pvc,10Gi |
postgresql 的 /var/lib/postgresql |
emptyDir: {} |
PVC postgresql-pvc,5Gi |
redis 的 /var/lib/redis |
emptyDir: {} |
PVC redis-pvc,5Gi |
| 依赖 | 无 | StorageClass openebs-hostpath 或默认 StorageClass |
emptyDir 的生命周期跟随 Pod:Pod 被删除、被驱逐、节点重启导致重建,数据全部消失。所以非持久化版本只适合验证「能不能启动」,一旦你建了仓库、推了代码,就不该再删 Pod。
flowchart LR
U[浏览器 / git 客户端] --> I[Ingress gitlab.magedu.com]
I --> S[Service gitlab 80 与 22]
S --> G[Pod gitlab all-in-one]
G --> P[postgresql]
G --> R[redis]
G --- D[PVC gitlab-pvc]
P --- PD[PVC postgresql-pvc]
R --- RD[PVC redis-pvc]
图:请求从 Ingress 进入唯一的 gitlab Pod,三份状态分别落在三个 PVC 上;任何一份 PVC 丢失都意味着平台不可恢复。
配置是怎么注入的¶
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。
存储与访问¶
持久化版本要求 openebs-hostpath 存在,或集群有默认 StorageClass。PVC 的 accessModes 都是 ReadWriteOnce,因为 gitlab、postgresql、redis 都是单副本;如果后面把 gitlab 扩到多副本,RWO 卷会直接阻止第二个 Pod 挂载——这也是生产要把 Gitaly 拆出去的原因。
Service 是 type: LoadBalancer,没有 MetalLB 时 EXTERNAL-IP 永远是 <pending>,但 NodePort 仍可用,Ingress 也不受影响。
资源评估¶
清单没有设置 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。
生产化建议¶
清单离生产至少差四件事:拆组件(官方 Chart 把 webservice、sidekiq、gitaly 分开)、外置 PostgreSQL 与 Redis、把备份落到对象存储、补 TLS 与 runner 隔离。顺序上先拆存储与数据库,再谈多副本——Gitaly 不拆,扩副本没有意义。
动手实验¶
实验:从零跑起 GitLab,再从临时存储切到持久化
目标:完成一次「能登录、能推代码、重启不丢数据」的部署,并理解两次部署之间丢了什么。
步骤 1 · 检查前置条件
至少要有一个默认 StorageClass 和 nginx IngressClass。
步骤 2 · 创建 namespace
步骤 3 · 部署非持久化版本
首次启动要初始化数据库、编译资源,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
用 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
用 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,改完滚动更新即可。
验证清单¶
-
kubectl get pods -n gitlab三个 Pod 全部Running且READY 1/1 -
kubectl get pvc -n gitlab三个 PVC 全部Bound - 用
root能登录,右上角能看到管理员区域 -
gitlab-rake gitlab:backup:create产出的 tar 包非空 -
gitlab-rake gitlab:backup:create产出的 tar 包非空
常见问题与排错¶
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| gitlab Pod 反复重启 | 启动慢被 liveness 杀掉;数据库未就绪 | 调大 initialDelaySeconds;先确认 postgresql 就绪 |
PG::ConnectionBad |
Secret 中 db_user/db_pass/db_name 与 PG 初始化值不一致 |
改 Secret 后必须重建 postgresql 数据卷,否则旧库密码不匹配 |
默认密码 5iveL!fe 登不上 |
新版禁止默认密码,或实例已改密 | 用 GITLAB_ROOT_PASSWORD 重部署,或进容器用 gitlab-rake 重置 |
PVC Pending |
StorageClass 不存在、没打默认标记、WaitForFirstConsumer 且无可用节点 |
kubectl describe pvc;确认 SC 名称与集群一致 |
克隆地址是 git.default.svc.cluster.local |
GITLAB_HOST 沿用了示例的 namespace |
改为实际域名或 gitlab.<ns>.svc.cluster.local |
| SSH 克隆 22 端口被拒 | 节点 22 被 sshd 占用;Service 未暴露 22 | 用 HTTP 克隆,或换 GITLAB_SSH_PORT 并映射 NodePort |
关于旧 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>。
进阶挑战¶
- 把 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://。
小结¶
- GitLab 的六类组件里,真正有状态的是 gitaly 的仓库目录和 postgresql 的元数据,redis 可以丢但会导致队列任务重放。
- 本仓库用 all-in-one 镜像换来部署简单,代价是单点、难扩、升级粒度粗;生产应换成官方 Chart 或 GitLab Operator。
deploy/与deploy-persistent/只差卷的来源,但这一处差异决定了「实验」和「能用」的分界线。