跳转至

GitLab 平台部署

GitLab 不只是一个 Git 仓库,它把代码托管、合并请求、CI 流水线和制品仓库塞进了一套应用。本章用仓库里的清单把它跑起来,重点讲清「哪些数据必须持久化」,以及从实验形态走到生产形态还差什么。

难度 · 进阶 预计 60 分钟 前置:工作负载、Service 与 Ingress、存储与持久化

你将学到

  • 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 地址,账号密码来自 Secret gitlab 的 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 · 检查前置条件

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 改对:

gitlab-env-patch.yaml
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,改完滚动更新即可。

验证清单

  • 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>。

进阶挑战

  1. 把 PostgreSQL 换成集群外实例(或 CloudNativePG),验证删掉 postgresql Deployment 后 GitLab 仍可登录,并说明为什么这能减少一次故障域。
  2. 给 GitLab 加一个 gitlab-runner Deployment,用 kubectl create secret generic 存 registration token,注册一个 docker executor 的 runner,跑通一条 .gitlab-ci.yml。
  3. 用 cert-manager 给 gitlab.magedu.com 签发证书,把 Ingress 改成 TLS,并确认 git clone 的 HTTPS 地址自动变成 https://。

小结

  • GitLab 的六类组件里,真正有状态的是 gitaly 的仓库目录和 postgresql 的元数据,redis 可以丢但会导致队列任务重放。
  • 本仓库用 all-in-one 镜像换来部署简单,代价是单点、难扩、升级粒度粗;生产应换成官方 Chart 或 GitLab Operator。
  • deploy/ 与 deploy-persistent/ 只差卷的来源,但这一处差异决定了「实验」和「能用」的分界线。

延伸阅读