前面已经部署了 SonarQube,用来做代码质量检测。SonarQube 单独使用也可以,但它和 GitLab 配合起来价值更大:

GitLab 负责代码托管、Merge Request、CI/CD
SonarQube 负责代码质量扫描、漏洞检查、质量门禁
GitLab CI 在提交或合并时触发 sonar-scanner
扫描结果回传 SonarQube,再决定是否允许继续发布

这篇文章记录在 Kubernetes 集群里部署 GitLab 的路线。当前集群已有:

Kubernetes 三主节点
Longhorn 存储
ingress-nginx
MetalLB
cert-manager
CloudNativePG
SonarQube

计划使用的访问域名:

https://gitlab.jihw.top
https://registry.jihw.top

其中:

gitlab.jihw.top    GitLab Web / Git over HTTP
registry.jihw.top  GitLab Container Registry

GitLab 适合放在 K8s 里吗

可以放,但要先知道它比 SonarQube 重很多。

GitLab 不只是一个 Web 服务,它包含:

WebService / Rails
Sidekiq 后台任务
Gitaly Git 仓库存储服务
Toolbox 备份和维护工具
Container Registry
KAS / GitLab Agent Server
PostgreSQL
Redis / Valkey
对象存储
GitLab Runner

所以 GitLab 的 Kubernetes 部署更像一个平台,而不是一个普通 Deployment。

如果只是个人或很小的团队,最省心的方式其实是单独一台虚拟机安装 GitLab Omnibus。
如果已经有稳定的 Kubernetes、Ingress、证书、存储、备份体系,再用 Helm Chart 部署 GitLab 才比较合适。

和 SonarQube 如何配合

GitLab 和 SonarQube 通常这样配合:

1. 开发者 push 代码到 GitLab
2. GitLab CI 触发流水线
3. 流水线执行构建、测试、sonar-scanner
4. 扫描结果上传到 SonarQube
5. SonarQube 计算 Quality Gate
6. GitLab Merge Request 里展示检查结果

GitLab 侧需要:

项目仓库
.gitlab-ci.yml
CI/CD Variables
Runner

SonarQube 侧需要:

项目
Project Token
Quality Gate
GitLab ALM Integration

先部署 GitLab,后面再把 SonarQube 接进去。

部署方式选择

GitLab 官方提供 Helm Chart:

helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm search repo gitlab/gitlab

需要注意:GitLab Helm Chart 近几年变化比较大。GitLab 19.0 起,官方更推荐 Gateway API;同时 PostgreSQL、Redis、对象存储这类依赖不应该再依赖 chart 内置组件做长期运行。

当前集群已经有 ingress-nginx,所以本文选择:

关闭 GitLab chart 自带的 Ingress Controller
关闭 GitLab chart 内置 cert-manager
沿用现有 ingress-nginx
沿用现有 cert-manager
使用 Longhorn 做持久化存储
外部 PostgreSQL / Redis / 对象存储按长期部署规划

这比“一条 helm install 装全部”麻烦一些,但边界更清楚,也更容易备份和维护。

资源建议

GitLab 很吃资源。学习环境建议至少:

CPU:    4 core 起步
Memory: 8Gi 起步
Disk:   100Gi 起步

长期使用建议:

CPU:    8 core+
Memory: 16Gi+
Disk:   按仓库、镜像、构建产物容量规划

如果节点资源不够,GitLab 会表现为:

Pod Pending
Pod OOMKilled
Web 页面很慢
Sidekiq 堆积
Git push / clone 超时

当前不考虑 GitLab 高可用,只准备放在一台专用 worker 上。推荐这台虚拟机配置:

最低能跑:4 vCPU / 8GB RAM / 200GB SSD
推荐配置:8 vCPU / 16GB RAM / 500GB SSD
如果 Runner 也跑在这台机器上:8 vCPU+ / 24GB~32GB RAM / 500GB~1TB SSD

我更推荐直接给:

8 vCPU
16GB RAM
500GB SSD

这样 GitLab 本体、Gitaly、Registry 和少量后台任务会舒服很多。GitLab Runner 后续最好单独规划,避免构建任务和 GitLab 本体抢 CPU、内存和磁盘 IO。

单独 worker 节点方案

这次目标是不做 GitLab 高可用,只让 GitLab 跑在一台专用 worker 节点上。

整体步骤:

1. 准备一台新虚拟机
2. 安装 containerd、kubeadm、kubelet
3. 加入当前 Kubernetes 集群
4. 给节点打 label
5. GitLab values 里用 nodeSelector 固定调度
6. 使用 longhorn-retain 保存数据

准备虚拟机

建议系统:

Ubuntu Server 22.04 LTS 或 24.04 LTS

建议磁盘:

/var/lib/containerd  镜像和容器数据
/var/lib/kubelet     Pod 挂载目录
/var/lib/longhorn    Longhorn 磁盘目录

如果只有一块 500GB 系统盘,也可以先全部放在同一块盘上。后续如果仓库和镜像增长很快,再给 Longhorn 单独加数据盘。

基础设置:

swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab

cat <<'EOF' >/etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

cat <<'EOF' >/etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables=1
net.ipv4.ip_forward=1
net.bridge.bridge-nf-call-ip6tables=1
EOF

sysctl --system

安装 containerd、kubeadm、kubelet 的步骤要和现有集群版本保持一致。可以先在当前集群查看版本:

kubectl get nodes
kubectl version

加入 Kubernetes 集群

在任意控制平面节点上生成 join 命令:

kubeadm token create --print-join-command

它会输出类似:

kubeadm join 192.168.3.217:6443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash>

在新 worker 节点上执行这条 join 命令。

回到控制平面确认:

kubectl get nodes -o wide

这里实际加入的 GitLab worker 是 192.168.3.218,Kubernetes 节点名是:

k8s-worker1

给节点打 label

回到 master 节点,或者任意一台已经配置好 kubeconfig 的机器,给这台 worker 打专用标签:

kubectl label node k8s-worker1 workload=gitlab --overwrite

确认:

kubectl get nodes --show-labels | grep gitlab

后面 GitLab values 里会使用:

global:
  nodeSelector:
    workload: gitlab

这样 GitLab chart 里的主要组件都会被调度到这个节点。

暂时不要加 taint

如果想让这台节点只跑 GitLab,理论上可以加 taint:

kubectl taint node k8s-worker1 dedicated=gitlab:NoSchedule

但第一次部署不建议马上加。原因是 GitLab chart 组件很多,外部依赖也多,如果 toleration 没有覆盖到所有 Pod,容易出现部分组件一直 Pending

更稳的做法:

1. 先只加 label
2. 用 global.nodeSelector 固定 GitLab 到这台节点
3. 部署成功并观察稳定
4. 确认所有 GitLab 组件 tolerations 配置后,再考虑 taint

当前目标只是让 GitLab 跑在单独 worker 上,nodeSelector 已经够用。

创建命名空间

kubectl create namespace gitlab

准备 StorageClass

GitLab 里最重要的数据是 Git 仓库,也就是 Gitaly 的数据卷。它不应该使用 Delete 回收策略。

前面部署 SonarQube 时已经创建过:

longhorn-retain

如果还没有,可以创建:

cat <<'EOF' | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-retain
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  fsType: ext4
EOF

确认:

kubectl get storageclass

外部依赖规划

GitLab 长期运行建议把这些依赖拆出来:

PostgreSQL   GitLab 元数据、用户、权限、CI 状态
Redis/Valkey 缓存、队列、会话
对象存储     artifacts、LFS、uploads、packages、registry、backup

本文先把部署路线写清楚。实际生产时建议:

PostgreSQL   使用 CloudNativePG
Redis/Valkey 使用独立 Helm Chart 或专门的 Redis/Valkey 集群
对象存储     使用 MinIO、Garage、Ceph RGW 或云厂商 S3

不要让 GitLab、业务系统、SonarQube 共用同一个 database 和 user。最低要求是独立 database、独立 user;更好的方式是独立 PostgreSQL Cluster。

PostgreSQL 准备思路

如果用 CloudNativePG,可以单独给 GitLab 创建 PostgreSQL:

cat <<'EOF' | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: gitlab-postgresql
  namespace: gitlab
spec:
  instances: 1

  storage:
    size: 50Gi
    storageClass: longhorn-retain

  bootstrap:
    initdb:
      database: gitlabhq_production
      owner: gitlab
EOF

等待 Ready:

kubectl -n gitlab get cluster
kubectl -n gitlab get pods -l cnpg.io/cluster=gitlab-postgresql -o wide
kubectl -n gitlab get secret | grep gitlab-postgresql

GitLab 连接地址类似:

gitlab-postgresql-rw.gitlab.svc.cluster.local:5432

注意:GitLab 新版本对数据库拆分、CI database、连接池等有更多配置项。正式部署前要按当前 chart 版本的官方文档确认 PostgreSQL 配置。

Redis / Valkey 准备思路

Redis 或 Valkey 用来支撑缓存、队列和会话。

可以单独部署一个 Redis:

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

helm upgrade --install gitlab-redis bitnami/redis \
  -n gitlab \
  --set architecture=standalone \
  --set auth.enabled=true \
  --set auth.password='change-this-redis-password' \
  --set master.persistence.enabled=true \
  --set master.persistence.storageClass=longhorn-retain \
  --set master.persistence.size=10Gi

长期环境不要把密码直接写在命令里,应该使用 Secret 或 values 文件管理。

Redis 地址类似:

gitlab-redis-master.gitlab.svc.cluster.local:6379

对象存储准备思路

GitLab 有很多文件类数据:

CI artifacts
LFS 对象
用户上传文件
Packages
Container Registry
Terraform state
备份文件

这些更适合放对象存储,而不是全部压在 Pod 本地卷里。

可以使用:

MinIO
Garage
Ceph RGW
阿里云 OSS / AWS S3 / 其他 S3 兼容存储

对象存储至少要规划这些 bucket:

gitlab-artifacts
gitlab-lfs
gitlab-uploads
gitlab-packages
gitlab-registry
gitlab-backups
gitlab-tmp

GitLab Chart 的对象存储配置比较长,建议单独维护 gitlab-object-storage.yaml,不要把 access key 写进博客或 Git 仓库。

准备 GitLab root 密码

先创建初始 root 密码 Secret:

kubectl -n gitlab create secret generic gitlab-root-password \
  --from-literal=password='change-this-root-password'

后续登录:

username: root
password: 上面 Secret 里的 password

准备 values.yaml

创建 gitlab-values.yaml

下面是这次实际安装使用的基础骨架。当前复用 new-api 命名空间里已有的 PostgreSQL 和 Redis,所以 GitLab 只单独创建自己的数据库、数据库密码 Secret、root 密码 Secret。

因为当前还没有独立对象存储,先临时启用 chart 内置 MinIO,并使用 Longhorn 持久化。Registry 暂时关闭,等后面对象存储规划好后再打开。

global:
  edition: ce

  nodeSelector:
    workload: gitlab

  hosts:
    domain: jihw.top
    https: true
    gitlab:
      name: gitlab.jihw.top
    registry:
      name: registry.jihw.top

  ingress:
    enabled: true
    class: nginx
    configureCertmanager: false
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-alidns-prod
      nginx.ingress.kubernetes.io/proxy-body-size: "0"
    tls:
      enabled: true

  initialRootPassword:
    secret: gitlab-root-password
    key: password

  psql:
    host: newapi-postgres-rw.new-api.svc.cluster.local
    port: 5432
    database: gitlabhq_production
    username: gitlab
    password:
      secret: gitlab-postgresql
      key: password

  redis:
    host: redis-master-proxy.new-api.svc.cluster.local
    port: 6379
    auth:
      enabled: true
      secret: gitlab-redis
      key: redis-password

  minio:
    enabled: true

  kas:
    enabled: false

upgradeCheck:
  enabled: false

gatewayApi:
  enabled: false

nginx-ingress:
  enabled: false

installCertmanager: false

certmanager:
  install: false

prometheus:
  install: false

gitlab-runner:
  install: false

postgresql:
  install: false

redis:
  install: false

minio:
  persistence:
    storageClass: longhorn-retain
    size: 20Gi

registry:
  enabled: false

gitlab:
  kas:
    enabled: false
    minReplicas: 1
    maxReplicas: 1

  gitlab-shell:
    minReplicas: 1
    maxReplicas: 1

  gitlab-pages:
    enabled: false

  webservice:
    minReplicas: 1
    maxReplicas: 1
    workerTimeout: 120
    resources:
      requests:
        cpu: 300m
        memory: 1Gi

  sidekiq:
    minReplicas: 1
    maxReplicas: 1
    resources:
      requests:
        cpu: 200m
        memory: 768Mi

  gitaly:
    persistence:
      storageClass: longhorn-retain
      size: 20Gi

    resources:
      requests:
        cpu: 200m
        memory: 512Mi

  toolbox:
    replicas: 1

几个重点:

global.edition=ce 使用 Community Edition
global.nodeSelector 固定 GitLab 组件到专用 worker 节点
gatewayApi.enabled=false 沿用现有 ingress-nginx
nginx-ingress.enabled=false 不安装 chart 自带 Ingress Controller
installCertmanager=false 不安装 chart 自带 cert-manager
postgresql.install=false 使用 new-api 命名空间已有的 PostgreSQL
redis.install=false 使用 new-api 命名空间已有的 Redis
global.minio.enabled=true 临时启用 chart 内置 MinIO 作为对象存储
registry.enabled=false 暂时不启用 Container Registry
global.kas.enabled=false 暂时不启用 KAS,降低单 worker 资源压力
gitaly.persistence 使用 longhorn-retain,保护 Git 仓库数据

cert-manager.io/cluster-issuer 要写成当前集群真实存在的 ClusterIssuer:

kubectl get clusterissuer

当前集群可用的是:

letsencrypt-alidns-prod
letsencrypt-alidns-staging

安装 GitLab

helm upgrade --install gitlab gitlab/gitlab \
  -n gitlab \
  -f gitlab-values.yaml

GitLab 组件很多,第一次启动会比较久:

kubectl -n gitlab get pods -w

确认 GitLab Pod 是否都调度到了专用 worker:

kubectl -n gitlab get pods -o wide

正常情况下,GitLab 相关 Pod 的 NODE 应该都是:

k8s-worker1

如果有 Pod 没有调度到这个节点,检查:

kubectl get node k8s-worker1 --show-labels
kubectl -n gitlab describe pod <pod-name>

重点看是否有:

node(s) didn't match Pod's node affinity/selector
Insufficient cpu
Insufficient memory
pod has unbound immediate PersistentVolumeClaims

查看 Ingress:

kubectl -n gitlab get ingress
kubectl -n gitlab get certificate

查看 PVC:

kubectl -n gitlab get pvc
kubectl get pv | grep gitlab

配置 DNS

Ingress/MetalLB 当前入口地址是:

192.168.3.230

DNS 需要解析:

gitlab.jihw.top    -> 192.168.3.230
registry.jihw.top  -> 192.168.3.230

测试:

curl -I https://gitlab.jihw.top
curl -I https://registry.jihw.top/v2/

Container Registry 未登录时返回 401 Unauthorized 是正常的,说明入口已经通了。

首次登录

打开:

https://gitlab.jihw.top

账号:

username: root
password: gitlab-root-password Secret 中的 password

查看密码:

kubectl -n gitlab get secret gitlab-root-password \
  -o jsonpath='{.data.password}' | base64 -d

首次登录后建议马上:

1. 修改 root 密码
2. 创建自己的管理员账号
3. 关闭公开注册
4. 配置 SMTP 邮件
5. 配置备份策略

关闭公开注册:

Admin Area -> Settings -> General -> Sign-up restrictions

安装 GitLab Runner

GitLab 本身负责代码托管和 CI 管理,真正跑流水线的是 Runner。

可以单独部署 Runner:

helm repo add gitlab https://charts.gitlab.io/
helm repo update

helm upgrade --install gitlab-runner gitlab/gitlab-runner \
  -n gitlab \
  --set gitlabUrl=https://gitlab.jihw.top \
  --set runnerToken='你的 Runner Token'

Runner Token 在 GitLab 页面获取:

Admin Area -> CI/CD -> Runners

或者项目级:

Project -> Settings -> CI/CD -> Runners

长期使用建议给 Runner 单独写 gitlab-runner-values.yaml,限制并发、资源和可用命名空间。

接入 SonarQube

GitLab 部署好后,可以在项目里加 .gitlab-ci.yml

示例:

stages:
  - test

sonarqube:
  stage: test
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - sonar-scanner
      -Dsonar.projectKey="${CI_PROJECT_PATH_SLUG}"
      -Dsonar.projectName="${CI_PROJECT_PATH}"
      -Dsonar.sources=.
      -Dsonar.host.url="${SONAR_HOST_URL}"
      -Dsonar.token="${SONAR_TOKEN}"

在 GitLab 项目里配置 CI/CD Variables:

SONAR_HOST_URL=https://sonarqube.jihw.top
SONAR_TOKEN=SonarQube 项目 Token

如果 SonarQube 和 GitLab 都在同一个内网 Kubernetes 集群里,也可以用内网地址:

SONAR_HOST_URL=http://sonarqube-sonarqube.sonarqube.svc.cluster.local:9000

但对人类查看和回调集成来说,建议仍然配置 SonarQube 的公网或内网 HTTPS 域名。

常用排查

Pod 没启动

kubectl -n gitlab get pods -o wide
kubectl -n gitlab describe pod <pod-name>
kubectl -n gitlab logs <pod-name>

重点看:

资源不足
PVC 未 Bound
镜像拉取失败
PostgreSQL 连接失败
Redis 连接失败
对象存储配置错误
证书未签发

如果使用专用 worker,还要检查 Pod 是否被 nodeSelector 卡住:

kubectl get node k8s-worker1 --show-labels
kubectl -n gitlab describe pod <pod-name> | grep -A5 -i events

如果看到:

node(s) didn't match Pod's node affinity/selector

说明节点标签和 values 里的 global.nodeSelector 对不上。

Ingress 不通

kubectl -n gitlab get ingress
kubectl -n gitlab describe ingress
kubectl -n gitlab get certificate
kubectl -n ingress-nginx get svc ingress-nginx-controller -o wide
curl -kI https://gitlab.jihw.top

如果证书不签发,先确认 ClusterIssuer:

kubectl get clusterissuer

Git push 慢或失败

检查:

kubectl -n gitlab get pods | grep gitaly
kubectl -n gitlab get pvc | grep gitaly
kubectl -n gitlab logs <gitaly-pod>

Gitaly 是 Git 仓库的核心组件。它的 PVC 不要随便删。

PVC 回收策略

GitLab 的关键数据建议使用 Retain

kubectl -n gitlab get pvc
kubectl get pv | grep gitlab
kubectl get pv <pv-name> -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'

如果发现重要 PV 是 Delete

kubectl patch pv <pv-name> \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

备份建议

GitLab 备份比普通应用复杂,要覆盖:

PostgreSQL 数据库
Gitaly Git 仓库数据
对象存储 bucket
Secrets
Helm values

建议:

1. CloudNativePG 定期备份 PostgreSQL
2. Longhorn Snapshot / Backup 保护 Gitaly PVC
3. 对象存储开启版本控制或单独备份
4. 保存 gitlab-values.yaml
5. 保存关键 Secret
6. 升级 GitLab 前先备份

这次虽然不考虑 GitLab 高可用,只部署在一台 worker 上,但备份仍然必须做。单 worker 只是不做应用层高可用,不代表可以接受数据丢失。

如果这台 worker 故障:

GitLab 服务会不可用
Longhorn 如果有健康副本,数据还有机会在其他节点恢复
如果没有备份,误删、数据库损坏、对象存储丢失仍然很难救

不要把 Retain 当成备份。Retain 只能降低误删 PVC 时的数据清理风险,不能替代数据库备份和对象存储备份。

升级建议

GitLab 升级要比 SonarQube 更谨慎。

建议:

1. 阅读当前版本到目标版本的升级说明
2. 先备份数据库、Gitaly、对象存储
3. 先在测试环境验证 chart values
4. 小版本逐步升级,不要跨太多版本
5. 升级后检查 migrations、Sidekiq、Gitaly、Registry

查看当前 Helm release:

helm -n gitlab list
helm -n gitlab get values gitlab

升级:

helm repo update
helm -n gitlab upgrade gitlab gitlab/gitlab \
  -f gitlab-values.yaml

参考

  • GitLab Helm Chart 文档:https://docs.gitlab.com/charts/
  • GitLab Helm Chart 安装文档:https://docs.gitlab.com/charts/installation/
  • GitLab 外部 Ingress 文档:https://docs.gitlab.com/charts/advanced/external-ingress/
  • GitLab 外部 PostgreSQL 文档:https://docs.gitlab.com/charts/advanced/external-db/
  • GitLab 外部 Redis 文档:https://docs.gitlab.com/charts/advanced/external-redis/
  • GitLab 外部对象存储文档:https://docs.gitlab.com/charts/advanced/external-object-storage/
  • GitLab Runner Helm Chart 文档:https://docs.gitlab.com/runner/install/kubernetes/
  • Kubernetes Ingress 文档:https://kubernetes.io/docs/concepts/services-networking/ingress/