如果你直接使用官方的 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: 1 和 num-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。整体工作流如下:
- 配置一个 BackupRepo(指向 S3、GCS 或任何兼容 S3 的对象存储)。
- 创建一个 BackupSchedule,通过 cron 表达式自动化定时快照。
- 当灾难发生时,使用 OpsRequest (类型设为
Restore)或标准的Backup/RestoreCR 来进行恢复。
详细的步骤指南,请参考官方文档:
提示: 务必在上线前配置好 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。
相关网址: