GitLab on Kubernetes:使用 KubeBlocks 部署生产级高可用 PostgreSQL 和 Redis

如果你直接使用官方的 Helm chart,在 Kubernetes 上部署 GitLab 其实非常简单------直到你遇到数据库层的挑战。官方捆绑的 PostgreSQL 和 Redis 实例用来测试体验非常完美,但它们都是单点故障(Single Point of Failure)。只要其中一个 Pod 重启,你整个团队的 CI/CD 流水线就会瞬间停摆。

这篇指南将带你一步步替换掉这些脆弱的内置数据库,换成由 KubeBlocks 管理的、真正生产级别的高可用(HA)集群。我们将部署 基于 Patroni 的 PostgreSQL (支持流复制和自动选主)以及 基于 Sentinel 的 Redis(1 主 1 从 + 3 个哨兵节点)。

以下所有命令均在 EKS 环境下使用 KubeBlocks 1.0.2 版本测试通过。


前置准备

在开始之前,请确保你已经准备好:

  • 一个 Kubernetes 集群(1.22 或更高版本),并已配置好 kubectl
  • 已安装 Helm 3.x
  • 集群中已安装 KubeBlocks 1.0.2 或更高版本

如果你还没有安装 KubeBlocks,可以通过以下命令快速安装:

bash 复制代码
helm repo add kubeblocks https://apecloud.github.io/helm-charts
helm repo update

helm install kubeblocks kubeblocks/kubeblocks \
  --namespace kb-system \
  --create-namespace \
  --version 1.0.2

等待 KubeBlocks Operator 完全就绪:

bash 复制代码
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/name=kubeblocks \
  -n kb-system \
  --timeout=120s

架构概览

为了保持架构清晰,我们将把两个数据库集群部署在一个专门的 gitlab-data 命名空间中。这能将有状态的数据层与 GitLab 无状态的应用 Pod 严格隔离。


第一步:创建数据库命名空间

首先,为我们的数据层创建一个独立的命名空间。

bash 复制代码
kubectl create namespace gitlab-data

第二步:部署高可用 PostgreSQL

KubeBlocks 提供了一个基于 Patroni 构建的 PostgreSQL Addon。Patroni 是一套久经考验的高可用解决方案,能够无缝处理 Leader 选举、流复制和自动故障转移。

使用 Helm 部署集群:

bash 复制代码
helm install gitlab-pg kubeblocks/postgresql-cluster \
  --namespace gitlab-data \
  --set version=16.4.0 \
  --set mode=replication \
  --set replicas=2 \
  --set cpu=2 \
  --set memory=4 \
  --set storage=50 \
  --set terminationPolicy=Delete

以下是这些核心参数的含义:

  • mode=replication --- 启用 Patroni 流复制模式(1 主 + N-1 从)。
  • replicas=2 --- 部署 1 个主节点和 1 个备节点。(对于本教程来说足够了,但请务必阅读下方的生产环境提示)。
  • terminationPolicy=Delete --- 允许通过 helm delete 直接删除集群。(如果你想连底层的 PVC 数据一起删掉,请使用 WipeOut)。

生产环境提示: 这里我们使用 replicas=2 是为了降低教程的资源成本。但在真正的生产环境中,强烈建议使用 replicas=3。在只有 2 个节点且没有外部 DCS(分布式配置存储,如 etcd 或 Consul)的情况下,如果发生网络分区,Patroni 无法形成法定人数(Quorum)。为了防止脑裂(Split-brain),它会暂停主节点而不是执行故障转移。3 个副本能为 Patroni 提供安全的故障转移所需的多数票机制,而无需依赖外部组件。

等待集群状态变为 Running

bash 复制代码
kubectl wait --for=jsonpath='{.status.phase}'=Running \
  cluster/gitlab-pg \
  -n gitlab-data \
  --timeout=300s

让我们验证一下复制是否正常工作:

bash 复制代码
# 检查主节点(pg_is_in_recovery 应该返回 'f')
kubectl exec -n gitlab-data gitlab-pg-postgresql-0 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

# 检查备节点(pg_is_in_recovery 应该返回 't')
kubectl exec -n gitlab-data gitlab-pg-postgresql-1 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

你应该能看到类似这样的输出:

markdown 复制代码
 pg_is_in_recovery
-------------------
 f                    ← pod-0 是主节点

 pg_is_in_recovery
-------------------
 t                    ← pod-1 是备节点

第三步:创建 GitLab 数据库与用户

接下来,我们需要配置 GitLab 期望的特定数据库和用户。

首先,找出当前哪个 Pod 是 Patroni 的主节点。也就是 pg_is_in_recovery() 返回 f 的那个 Pod:

bash 复制代码
# 找出主节点(返回 'f' = false = 不是备节点)
kubectl exec -n gitlab-data gitlab-pg-postgresql-0 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

kubectl exec -n gitlab-data gitlab-pg-postgresql-1 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

确认主节点(例如 gitlab-pg-postgresql-0)后,对其执行以下命令来设置凭证:

bash 复制代码
# 生成一个高强度的随机密码
GITLAB_PG_PASS=$(openssl rand -base64 18 | tr -d '/+=')
echo "请保存好这个密码: $GITLAB_PG_PASS"

PRIMARY_POD=gitlab-pg-postgresql-0   # 替换为你实际的主节点 Pod 名称

kubectl exec -n gitlab-data $PRIMARY_POD -c postgresql -- \
  psql -U postgres -c "CREATE USER gitlab WITH PASSWORD '$GITLAB_PG_PASS';"

kubectl exec -n gitlab-data $PRIMARY_POD -c postgresql -- \
  psql -U postgres -c "CREATE DATABASE gitlabhq_production OWNER gitlab;"

kubectl exec -n gitlab-data $PRIMARY_POD -c postgresql -- \
  psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gitlabhq_production TO gitlab;"

注意: 因为 Patroni 可能选举任何一个 Pod 作为主节点,你必须确保所有的 DDL(数据定义语言)命令都只在当前的主节点上执行。如果你试图在备节点上写入,会收到 ERROR: cannot execute ... in a read-only transaction 的报错。


第四步:部署高可用 Redis (Sentinel)

GitLab 深度依赖 Redis 来处理缓存、管理 Sidekiq 任务队列以及维护会话状态。

KubeBlocks 的 Redis replication 拓扑会自动部署一个完整的高可用栈:

  • Redis 数据节点(1 主 + N-1 从)
  • 3 个专用于监控和自动故障转移的 Sentinel(哨兵)节点

为了避免 Helm 在解析数值类型时出现类型强制转换的问题,建议创建一个 redis-values.yaml 文件:

yaml 复制代码
version: "7.2.10"
mode: replication
replicas: 2
cpu: 1
memory: 2
storage: 10
terminationPolicy: Delete
sentinel:
  replicas: 3
  cpu: 0.5
  memory: 0.5
  storage: 1

部署 Redis 集群:

bash 复制代码
helm install gitlab-redis kubeblocks/redis-cluster \
  --namespace gitlab-data \
  --values redis-values.yaml

核心参数解析:

  • mode=replication --- 自动同时部署 Redis 数据节点和 Sentinel 组件。
  • replicas=2 --- 为 Redis 数据节点提供 1 主 1 从。
  • sentinel.replicas=3 --- 提供 3 个 Sentinel 节点,建立 2 票法定人数(Quorum)。

等待集群状态变为 Running

bash 复制代码
kubectl wait --for=jsonpath='{.status.phase}'=Running \
  cluster/gitlab-redis \
  -n gitlab-data \
  --timeout=300s

现在,让我们验证一下 Sentinel 是否在正常监控主节点:

bash 复制代码
SENTINEL_PASS=$(kubectl get secret gitlab-redis-redis-sentinel-account-default \
  -n gitlab-data \
  -o jsonpath='{.data.password}' | base64 -d)

kubectl exec -n gitlab-data gitlab-redis-redis-sentinel-0 -c redis-sentinel -- \
  redis-cli -p 26379 -a "$SENTINEL_PASS" sentinel masters

你应该能在输出中看到这些关键信息:

dart 复制代码
name
gitlab-redis-redis
ip
gitlab-redis-redis-0.gitlab-redis-redis-headless.gitlab-data.svc.cluster.local
port
6379
flags
master
num-slaves
1
num-other-sentinels
2
quorum
2

num-slaves: 1num-other-sentinels: 2 这两个值确认了你拥有一个健康的 1+1 Redis 集群,并且所有 3 个 Sentinel 都在线并正常监控。


第五步:为 GitLab 创建 Kubernetes Secrets

GitLab 会直接从 Kubernetes Secrets 中读取数据库凭证。我们需要在 gitlab 命名空间(或者你计划部署 GitLab 应用的命名空间)中创建这些 Secrets。

bash 复制代码
kubectl create namespace gitlab

# 创建 PostgreSQL 密码 Secret
kubectl create secret generic gitlab-postgresql-password \
  --namespace gitlab \
  --from-literal=main-gitlab-password='<你的高强度密码>'

# 从 KubeBlocks 生成的 Secret 中提取 Redis 密码
REDIS_PASS=$(kubectl get secret gitlab-redis-redis-account-default \
  -n gitlab-data \
  -o jsonpath='{.data.password}' | base64 -d)

# 为 GitLab 创建 Redis 密码 Secret
kubectl create secret generic gitlab-redis-secret \
  --namespace gitlab \
  --from-literal=redis-password="$REDIS_PASS"

# 提取 Sentinel 认证密码(这与 Redis 数据密码是分开的)
SENTINEL_PASS=$(kubectl get secret gitlab-redis-redis-sentinel-account-default \
  -n gitlab-data \
  -o jsonpath='{.data.password}' | base64 -d)

# 为 GitLab 创建 Sentinel 密码 Secret
kubectl create secret generic gitlab-redis-sentinel-secret \
  --namespace gitlab \
  --from-literal=sentinel-password="$SENTINEL_PASS"

为什么我们需要两个 Redis Secret? 为了提升安全性,KubeBlocks 使用完全独立的凭证来管理 Redis 数据节点和 Sentinel 节点。Sentinel 密码专门用于在哨兵端口(26379)上执行 AUTH,而标准的 Redis 密码则用于在实际的数据端口(6379)上执行 AUTH


第六步:部署 GitLab

现在进入正题。添加 GitLab Helm 仓库,并准备 values 文件来接入我们刚刚部署的外部高可用数据库。

bash 复制代码
helm repo add gitlab https://charts.gitlab.io/
helm repo update

创建 gitlab-values.yaml 文件:

yaml 复制代码
global:
  hosts:
    domain: gitlab.example.com   # 替换为你的实际域名
    https: true

  ## 外部 PostgreSQL (KubeBlocks Patroni 集群)
  psql:
    host: gitlab-pg-postgresql-postgresql.gitlab-data.svc.cluster.local
    port: 5432
    username: gitlab
    database: gitlabhq_production
    password:
      useSecret: true
      secret: gitlab-postgresql-password
      key: main-gitlab-password

  ## 外部 Redis (KubeBlocks Sentinel 集群)
  redis:
    # 重要:当配置了 sentinels 时,这里的 'host' 必须是 Sentinel 的 MASTER NAME,而不是 service 的域名。
    # KubeBlocks 会基于集群名称分配 master name:<release>-<component>
    host: gitlab-redis-redis
    port: 6379
    auth:
      enabled: true
      secret: gitlab-redis-secret
      key: redis-password
    sentinels:
      - host: gitlab-redis-redis-sentinel-redis-sentinel.gitlab-data.svc.cluster.local
        port: 26379
    sentinelAuth:
      enabled: true
      secret: gitlab-redis-sentinel-secret
      key: sentinel-password

# 显式禁用内置的 PostgreSQL 和 Redis 实例
postgresql:
  install: false

redis:
  install: false

# 禁用内置的 cert-manager(注意:GitLab chart 9.x 使用 'installCertmanager',而不是 'certmanager.install')
installCertmanager: false

certmanager-issuer:
  email: your@email.com

部署 GitLab:

bash 复制代码
helm install gitlab gitlab/gitlab \
  --namespace gitlab \
  --values gitlab-values.yaml \
  --timeout 600s

注意: GitLab 的首次部署非常繁重。它会拉取多个大镜像并执行大量的数据库迁移操作。请给 gitlab-migrations 任务 5--10 分钟的时间来完成,然后再去检查其他应用 Pod 是否就绪。


第七步:验证部署

检查 GitLab Pod 的状态(记得给它几分钟时间完成迁移):

bash 复制代码
kubectl get pods -n gitlab

你需要确认所有的应用 Pod 状态都是 Running 且重启次数为零,并且 migrations 任务已经干净地完成:

sql 复制代码
NAME                                  READY   STATUS      RESTARTS
gitlab-gitaly-0                       1/1     Running     0
gitlab-gitlab-exporter-xxx            1/1     Running     0
gitlab-gitlab-shell-xxx               1/1     Running     0
gitlab-migrations-xxx                 0/1     Completed   0
gitlab-sidekiq-all-in-1-xxx           1/1     Running     0
gitlab-webservice-default-xxx         2/2     Running     0
...

gitlab-migrations-xxx 状态为 Completed(而不是 running 或 crashing)是你确认数据库 schema 已经成功应用到外部 PostgreSQL 集群的最佳标志。

如果想直接从应用层测试数据库连通性,可以在 webservice Pod 内部执行:

bash 复制代码
WEBSERVICE_POD=$(kubectl get pods -n gitlab -l app=webservice -o name | head -1)

kubectl exec -n gitlab $WEBSERVICE_POD -c webservice -- \
  /srv/gitlab/bin/rails runner "puts Gitlab::Database.main.version" 2>&1

通过 Port-Forward 访问 GitLab

如果你无法直接访问 LoadBalancer(例如,在本地测试,或者 ELB 有严格的安全组限制),你可以使用 kubectl port-forward 直接从工作站访问 GitLab UI:

bash 复制代码
# 获取初始 root 密码
kubectl get secret gitlab-gitlab-initial-root-password \
  -n gitlab \
  -o jsonpath='{.data.password}' | base64 -d && echo

# 将 nginx-ingress controller 转发到本地
kubectl port-forward -n gitlab svc/gitlab-nginx-ingress-controller 8888:80

打开浏览器访问 **http://localhost:8888/users/sign_in**,使用以下凭证登录:

  • 用户名: root
  • 密码: (上面命令输出的结果)

注意: 登录后,GitLab 可能会尝试将你重定向到 http://localhost/users/sign_in(丢掉了端口号)。此时只需手动在地址栏改回 http://localhost:8888/users/sign_in 即可保持转发连接。


理解连接细节

GitLab 到底是如何在发生故障转移时,保持与这些高可用集群的连接而不至于迷失方向的?

PostgreSQL

属性
Service gitlab-pg-postgresql-postgresql.gitlab-data.svc.cluster.local
端口 5432
用户名 gitlab
数据库 gitlabhq_production
HA 机制 Patroni(自动主节点选举)

-postgresql Service 是一个标准的 ClusterIP 服务(非 Headless),由 KubeBlocks 动态管理。它保证永远指向当前的 Patroni 主节点。当发生故障转移时,Patroni 会提升备节点,并在底层更新该 Service 的 selector。从 GitLab 的视角来看,不需要任何重新配置;连接只是短暂断开,然后立刻重连到新晋升的主节点上。

Redis

属性
Sentinel Service gitlab-redis-redis-sentinel-redis-sentinel.gitlab-data.svc.cluster.local
Sentinel 端口 26379
Master Name gitlab-redis-redis
HA 机制 Sentinel(自动主节点选举)

GitLab 底层的 Rails 栈通过 sentinels: 配置块原生支持 Redis Sentinel。当 Redis 主节点宕机时,Sentinel 节点会在几秒钟内选出新的主节点,并主动通知所有已连接的客户端(如 Sidekiq)将流量重定向到新的主节点 IP。

重要: 在 GitLab Helm chart 中,当配置了 sentinels 时,global.redis.host 字段实际上充当的是 Sentinel master name ,而不是 Service 域名。KubeBlocks 会系统性地根据集群本身来命名 master:<helm-release>-<component>。因此,对于 release 名为 gitlab-redis 且组件为 redis 的集群,master name 必定是 gitlab-redis-redis。你可以随时通过执行 redis-cli -p 26379 sentinel masters 来手动验证这一点。


故障转移测试

不要只相信理论。让我们故意搞破坏,亲眼看看系统是如何恢复的。

测试 PostgreSQL 故障转移

首先,找出当前的主节点,删除它的 Pod,然后实时观察 Patroni 提升备节点:

bash 复制代码
# 找出当前主节点
kubectl exec -n gitlab-data gitlab-pg-postgresql-0 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

# 杀掉主节点 Pod
kubectl delete pod gitlab-pg-postgresql-0 -n gitlab-data

# 观察 Patroni 选举新主节点(通常在 10-30 秒内完成)
watch kubectl exec -n gitlab-data gitlab-pg-postgresql-1 -c postgresql -- \
  psql -U postgres -c "SELECT pg_is_in_recovery();"

pg_is_in_recovery 在 pod-1 上返回 f 的那一刻,故障转移就完成了。ClusterIP Service 此时已经将流量重定向到了新的主节点。

测试 Redis 故障转移

bash 复制代码
REDIS_PASS=$(kubectl get secret gitlab-redis-redis-account-default \
  -n gitlab-data -o jsonpath='{.data.password}' | base64 -d)

# 确认当前主节点
kubectl exec -n gitlab-data gitlab-redis-redis-0 -c redis -- \
  redis-cli -a "$REDIS_PASS" role

# 杀掉主节点 Pod
kubectl delete pod gitlab-redis-redis-0 -n gitlab-data

# Sentinel 会检测到故障并在约 20 秒内提升备节点。
# 让我们通过 Sentinel 检查新的主节点:
SENTINEL_PASS=$(kubectl get secret gitlab-redis-redis-sentinel-account-default \
  -n gitlab-data -o jsonpath='{.data.password}' | base64 -d)

kubectl exec -n gitlab-data gitlab-redis-redis-sentinel-0 -c redis-sentinel -- \
  redis-cli -p 26379 -a "$SENTINEL_PASS" sentinel masters | grep -A1 "^ip"

扩缩容与升级

扩展 PostgreSQL 读副本

需要更多的读取性能?你可以动态扩展集群:

bash 复制代码
# 将集群扩展为 1 主 + 2 从
kubectl patch cluster gitlab-pg -n gitlab-data \
  --type='json' \
  -p='[{"op":"replace","path":"/spec/componentSpecs/0/replicas","value":3}]'

升级 PostgreSQL 版本

KubeBlocks 支持零停机时间的就地小版本升级。它会先对备节点进行滚动更新,随后执行一次受控的主节点故障转移:

bash 复制代码
kubectl apply -f - <<EOF
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  name: pg-upgrade
  namespace: gitlab-data
spec:
  clusterName: gitlab-pg
  type: Upgrade
  upgrade:
    components:
      - componentName: postgresql
        serviceVersion: "16.9.0"
EOF

扩容存储

磁盘空间不足?无缝扩容数据卷:

bash 复制代码
kubectl apply -f - <<EOF
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  name: pg-volume-expand
  namespace: gitlab-data
spec:
  clusterName: gitlab-pg
  type: VolumeExpansion
  volumeExpansion:
    - componentName: postgresql
      volumeClaimTemplates:
        - name: data
          storage: "100Gi"
EOF

备份与恢复

在生产环境中,备份是必选项。对于 GitLab 部署,有两个绝对需要备份的有状态组件:

  • PostgreSQL --- 这里存放着你所有核心的 GitLab 应用数据:项目、Issue、Merge Request、用户账号以及 CI 流水线配置。
  • Redis --- 虽然它主要作为缓存和任务队列(这意味着它对严格的基于时间点恢复的要求稍低),但在硬重启期间保留 Sentinel 状态依然是最佳实践。

KubeBlocks 为这两种数据库提供了统一的、Kubernetes 原生的备份 API。整体工作流如下:

  1. 配置一个 BackupRepo(指向 S3、GCS 或任何兼容 S3 的对象存储)。
  2. 创建一个 BackupSchedule,通过 cron 表达式自动化定时快照。
  3. 当灾难发生时,使用 OpsRequest (类型设为 Restore)或标准的 Backup/Restore CR 来进行恢复。

详细的步骤指南,请参考官方文档:

提示: 务必在上线前配置好 BackupRepo 和 BackupSchedule。如果你在一个没有备份的 replicas=2 集群上遭遇了灾难性的双节点故障,数据是无法恢复的。


清理环境

如果你只是在测试并希望销毁所有资源:

bash 复制代码
helm delete gitlab -n gitlab
helm delete gitlab-pg -n gitlab-data
helm delete gitlab-redis -n gitlab-data

# 如果你想彻底清除数据,请删除 PersistentVolumeClaims
kubectl delete pvc -n gitlab-data --all

# 最后,删除命名空间
kubectl delete namespace gitlab gitlab-data

总结

组件 改造前 改造后
PostgreSQL 捆绑的单节点实例 Patroni HA:1 主 + 1 从,自动故障转移
Redis 捆绑的单节点实例 Sentinel HA:1 主 + 1 从 + 3 个哨兵节点
故障转移 手动 / 无 10--30 秒内自动完成
扩缩容 手动管理 Pod 原生的 kubectl patch 或 OpsRequest
版本升级 需要停机 通过 KubeBlocks OpsRequest 滚动就地升级

KubeBlocks 完全抽象了在 Kubernetes 上运行有状态数据库的运维复杂度。它通过单一的、统一的 API 处理了资源供给、高可用配置、凭证管理以及 Day-2 运维操作。对于你的 GitLab 部署而言,这意味着数据库层从第一天起就真正达到了生产就绪的标准------而你甚至不需要编写哪怕一行 StatefulSet YAML。


相关网址:

kubeblocks.com/

kubeblocks.com/addons/post...

kubeblocks.com/addons/redi...

github.com/apecloud/ku...

相关推荐
phltxy2 小时前
Redis 常见数据类型之全局通用命令详解
数据库·redis·bootstrap
難釋懷2 小时前
Redis网络模型-用户空间和内核态空间
网络·arm开发·redis
薪火铺子3 小时前
布隆过滤器原理与 Redis 防穿透实战
数据库·redis·缓存
Fan_-_4 小时前
MySQL / PostgreSQL DDL 审核自动化:从人工 review 到 CI 拦截
mysql·postgresql·自动化
.柒宇.4 小时前
Redis高频面试题与跳跃表原理详解
数据库·redis·缓存
苍煜4 小时前
K8s 核心资源详解(Pod/Deployment/Service 实战)
云原生·容器·kubernetes
未若君雅裁4 小时前
Redis 和 MySQL 双写一致性:延迟双删、读写锁、MQ、Canal 怎么选?
数据库·redis·面试
期待のcode5 小时前
Redis数据类型
运维·数据结构·redis
ChaITSimpleLove5 小时前
优化 WSL2 性能:为 Docker 和 K8s 定制高效内存配置指南
docker·容器·性能优化·kubernetes·wsl2·windows开发·pwsh