INCIDENT-20260918-01 — 命名空间迁移期排障记录¶
- 主题目录:
ns_migration/ - 记录对象:2026-09-18 凌晨「命名空间统一为
server」变更期间的 3 个阻塞问题 - 等级:P3(变更期故障)——变更时目标组件无有效数据(GitLab
projects=0),无数据损坏,但变更窗口被拉长约 30 分钟 - 当前状态:已恢复。Traefik 与 GitLab 全栈均运行于
server命名空间,验证通过 - 关联文档:SOP.md、RUNBOOK.md
- 说明:本文件是台账与交接用。日常排障手册见 RUNBOOK.md 第 8 章;部署期的 5 起问题另见 gitlab-deploy-v2/INCIDENT-20260917-01
1. 概要¶
| 项 | 内容 |
|---|---|
| 发生时间 | 2026-09-18 01:06 – 01:22(CST) |
| 影响面 | GitLab 全栈重建过程(Traefik 迁移部分未受影响) |
| 业务影响 | GitLab 停机时间由预期的约 15 分钟延长到约 25 分钟 |
| 数据影响 | 无(迁移前已复核 GitLab 为空:projects=0、Gitaly 无仓库、MinIO 仅空桶) |
| 问题数 | 3 个(PV 交叉绑定、CNPG 无法 bootstrap、集群级残留阻塞部署) |
2. 影响范围¶
- 受影响服务:GitLab 及其依赖(PostgreSQL / Redis / MinIO)—— 全部在重建中,尚未对外提供服务。
- 不受影响:Traefik(已于 00:38 完成迁移并验证)、监控栈(本次明确排除)、集群其他命名空间。
- 数据损坏:无。三个 local PV 的回收策略均为
Retain,且旧数据目录以改名方式保留(*.bak.20260918005523),未做任何删除。
3. 时间线¶
| 时间 | 事件 |
|---|---|
| 00:55 | 下线旧 GitLab(卸载 release + 删除 4 个命名空间),旧 PV 转入 Released |
| 00:55 | 节点数据目录改名保留、删除旧 PV 并按原定义重建(三个 PV 回到 Available) |
| 01:06 | 在 server 内创建 MinIO / PostgreSQL(CNPG) / Redis |
| 01:07 | 问题 1 暴露:发现 gitlab-pg-1 绑定到 pv-redis-ssd、redis-data 绑定到 pv-postgres-ssd(交叉绑定) |
| 01:09 | 删除两个 PVC 并清空 claimRef,改为显式指定 volumeName 重绑 |
| 01:10 | 重建 CNPG Cluster —— 问题 2 暴露:状态长期停在 Setting up primary / BootstrapPending |
| 01:12 | 观察到 operator 每秒重复输出 Creating new Job jobName=gitlab-pg-1-initdb,而该 Job 早已 Complete |
| 01:15 | 判定根因为「预建 PVC 干扰 CNPG 状态机」,改为 PV claimRef 预绑定方案 |
| 01:15 | 处置中又发现新 PVC 抢占了 pv-gitaly-ssd(20Gi),遂把该 PV 也一并预绑定给 repo-data-gitlab-gitaly-0 |
| 01:16 | CNPG Cluster 达到 Cluster in healthy state,gitlab-pg-1 → pv-postgres-ssd 绑定正确 |
| 01:13 | 首次 helm install gitlab 失败 —— 问题 3 暴露:报 GatewayClass 归属校验错误 |
| 01:20 | 确认 GatewayClass gitlab-gw 卡在删除中(finalizer + 原 controller 已随旧命名空间消失),手工摘除 finalizer |
| 01:21 | 重新部署 |
| 01:22 | release deployed,随后 migrations Job 完成、登录页 200、/-/health 返回 GitLab OK |
| 01:35 | 文档注记与备份台账收尾完成 |
4. 根因¶
4.1 问题 1:两个同规格 local PV 被交叉绑定¶
现象:gitlab-pg-1(PostgreSQL)绑到了 pv-redis-ssd,redis-data(Redis)绑到了 pv-postgres-ssd。
证据
NAME STATUS VOLUME
gitlab-pg-1 Bound pv-redis-ssd ← 应为 pv-postgres-ssd
redis-data Bound pv-postgres-ssd ← 应为 pv-redis-ssd
根因:两个 PV 容量(5Gi)、accessModes、storageClassName(local-ssd)完全相同,Deployment/CNPG 创建 PVC 时由调度器先到先得地随机挑选 Available 的 PV,控制器并不理解「这个卷叫 postgres 就该给 PG 用」。
次生问题:修正时删除 PVC 并用 claimRef: null 释放后,新 PVC 又抢占了当时唯一 Available 的 pv-gitaly-ssd(20Gi,满足 5Gi 申请),造成第二次错位。
4.2 问题 2:CNPG 因预建 PVC 而无法 bootstrap¶
现象:Cluster/gitlab-pg 长期处于 Setting up primary;Initialized=False (BootstrapPending);initdb Job 早已 Complete,但 operator 持续重复创建同名 Job。
证据
status.conditions:
- type: Initialized status: "False" reason: BootstrapPending
message: Cluster has not been bootstrapped yet
operator 日志(每秒重复):
{"msg":"Creating new Job","jobName":"gitlab-pg-1-initdb","primary":true}
根因:为让 PostgreSQL 用上正确的卷,我先手工创建了名为 gitlab-pg-1 的 PVC(并指定 volumeName)。而 CNPG(CloudNativePG)对实例 PVC 有归属校验,只认由自己创建/带其标签的 PVC;面对这个"外来"的同名 PVC,它既不接管也不报错,而是判定 bootstrap 未完成,于是无限重试 initdb。
结论:CNPG 的实例 PVC 绝不可预建。要在重建场景下控制卷的归属,正确做法是给 PV 设 claimRef 预绑定(见 5.2)。
4.3 问题 3:集群级 GatewayClass 卡在删除中,阻塞新 release¶
现象:helm install gitlab 失败:
Error: Unable to continue with install: GatewayClass "gitlab-gw" in namespace "" exists and
cannot be imported into the current release: invalid ownership metadata; annotation validation
error: key "meta.helm.sh/release-namespace" must equal "server": current value is "gitlab"
证据
$ kubectl get gatewayclass gitlab-gw -o jsonpath='{.metadata.deletionTimestamp} {.metadata.finalizers}'
2026-09-17T16:50:31Z ["gateway-exists-finalizer.gateway.networking.k8s.io"]
根因:GatewayClass 是集群级资源,前一次 helm uninstall 与命名空间删除都无法立即清掉它;它被 Gateway API 的标准 finalizer gateway-exists-finalizer 拦住(设计上要求其下所有 Gateway 先删除)。而负责处理该 finalizer 的 Envoy Gateway controller 已随旧命名空间被删除,导致对象永久停在 deleting。Helm 随后拒绝把带旧注解(release-namespace: gitlab)的对象纳入新 release(server),部署中断。
5. 处置过程¶
5.1 问题 1 的处置(两轮)¶
# 第一轮:解除交叉绑定
kubectl -n server delete cluster gitlab-pg --wait=false
kubectl -n server delete statefulset redis
kubectl -n server delete pvc gitlab-pg-1 redis-data
kubectl patch pv pv-postgres-ssd -p '{"spec":{"claimRef":null}}'
kubectl patch pv pv-redis-ssd -p '{"spec":{"claimRef":null}}'
# 用 volumeName 显式指定(此轮做法,后被证明对 CNPG 不适用)
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: gitlab-pg-1, namespace: server}
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-ssd
volumeName: pv-postgres-ssd
resources: {requests: {storage: 5Gi}}
YAML
# 第二轮(最终方案):PV claimRef 预绑定 —— 不预建 PVC,只限定"哪个 PVC 名可以绑这个 PV"
kubectl patch pv pv-gitaly-ssd -p '{"spec":{"claimRef":null}}' # 必须先清空(去掉 uid),否则停留在 Released
kubectl patch pv pv-postgres-ssd -p '{"spec":{"claimRef":null}}'
kubectl patch pv pv-gitaly-ssd --type=merge \
-p '{"spec":{"claimRef":{"apiVersion":"v1","kind":"PersistentVolumeClaim","namespace":"server","name":"repo-data-gitlab-gitaly-0"}}}'
kubectl patch pv pv-postgres-ssd --type=merge \
-p '{"spec":{"claimRef":{"apiVersion":"v1","kind":"PersistentVolumeClaim","namespace":"server","name":"gitlab-pg-1"}}}'
关键点:claimRef 中只写 apiVersion/kind/namespace/name 四个字段(不写 uid),PV 状态为 Available 但只接受该名字的 PVC 绑定。用 --type=merge 直接改已有 claimRef 不会清掉旧 uid(实测报 patched (no change) 且状态仍为 Released),必须先 claimRef: null 再设置。
5.2 问题 2 的处置¶
删除手工 PVC,改用 5.1 的 PV 预绑定,让 CNPG 自行创建 PVC:
kubectl -n server delete cluster gitlab-pg --wait=false
kubectl -n server delete job gitlab-pg-1-initdb --ignore-not-found
kubectl -n server delete pvc gitlab-pg-1 --ignore-not-found
# 预绑定 pv-postgres-ssd → server/gitlab-pg-1(见 5.1)
kubectl apply -f <Cluster 定义> # 重建后 CNPG 自建 PVC,一次成功
5.3 问题 3 的处置¶
# 1) 确认没有任何 Gateway 仍引用它
kubectl get gateways -A # 输出 No resources found
# 2) 该 finalizer 本是等待 controller 清理 Gateway 后自动移除;但 controller 已不存在,
# 属"无人处理"的孤儿 finalizer,只能手工摘除
kubectl patch gatewayclass gitlab-gw --type=merge -p '{"metadata":{"finalizers":null}}'
# 3) 确认对象已清除后重新部署
kubectl delete gatewayclass gitlab-gw --ignore-not-found
helm upgrade --install gitlab ./gitlab -n server -f values-gitlab.yaml --timeout 900s
6. 恢复验证¶
# 存储绑定语义正确
kubectl -n server get pvc --no-headers | awk '{print $1, $2, $3}'
# 预期:gitlab-pg-1→pv-postgres-ssd redis-data→pv-redis-ssd repo-data-gitlab-gitaly-0→pv-gitaly-ssd
# 数据库集群健康
kubectl -n server get cluster gitlab-pg # 预期 Cluster in healthy state
# 服务可用
kubectl -n server get job | grep migrations # 预期 Completed
kubectl -n server run gl-check --rm -i --restart=Never --image=curlimages/curl:latest --quiet -- \
sh -c 'curl -s -H "Host: gitlab.rainchen.top" http://envoy-server-gitlab-gw-80b44f81/-/health'
# 预期 GitLab OK
以上均已实测通过。
7. 复盘¶
有效的做法
- 迁移前坚持复核数据为空(
projects=0),并据此选择"重建路径"而非"数据搬迁路径" —— 否则问题 1、2 的排障代价会成倍上升。 - local PV 采用
Retain+ 数据目录改名保留,全程未做删除,保证了可回滚。 - 每步都先看对象状态(
get pvc/get cluster/ operator 日志)再动手,问题定位较快。
拖慢恢复的做法
- 把 CNPG 当成普通 StatefulSet 处理,想用"预建 PVC 指定卷"这种常规手段控制绑定 —— 这是问题 2 的直接原因,且这一轮探索消耗了约 5 分钟。
- 第一次修 PV 交叉绑定时,只关注了 PG 与 Redis 两个卷,忽略了同时处于
Available的pv-gitaly-ssd,导致新 PVC 被它抢走,多返工一轮。 - 部署前没有先清理旧 release 的集群级残留(GatewayClass / ClusterRole),问题 3 直到
helm install报错才暴露。
留待体系化改进
- 这类"变更前清理集群级残留"的检查项,应固化为 SOP 的前置检查步骤(本次已补进
SOP.md第 5 章与附录 B)。 claimRef预绑定的具体写法(字段、顺序、--type=merge的坑)已写入 RUNBOOK.md 场景 3。
8. 行动项¶
| 事项 | 责任人 | 期限 | 状态 |
|---|---|---|---|
| 将「变更前清理旧 release 集群级残留(GatewayClass / ClusterRole / CRD 实例)」写入各变更 SOP 的前置检查 | rain | 2026-09-26 | 待办 |
为主机侧存储准备检查脚本:PV 与 PVC 的对应关系一键核对(含 claimRef 预绑定正确性) |
rain | 2026-09-26 | 待办 |
评估是否将三个 local PV 的用途写入注释/标签(如 app.kubernetes.io/purpose)以便人工识别 |
rain | 2026-10-03 | 待办 |
评估多节点入口方案(消除 k8s-master-01 单点,同时避免 hostPort 调度冲突) |
rain | 2026-10-10 | 待办 |
清理 k8s-node-01:/mnt/ssd/k8s/*.bak.20260918005523(现已证明新实例运行正常,确认无需回滚后可删) |
rain | 2026-09-26 | 待办 |
附:关联变更¶
| 变更 | 文档 |
|---|---|
| 本次命名空间统一变更 | ns_migration/SOP.md |
| GitLab 首次部署期的 4 个阻塞问题 + 1 起凭据失误 | gitlab-deploy-v2/INCIDENT-20260917-01-gitlab-部署与迁移期排障.md |
Traefik 迁移到 server |
traefik_deploy/SOP.md |
来源:
/home/rain/Documents/k8s_ins/ns_migration/INCIDENT-20260918-01-ns迁移期排障.md| 最后更新:2026-09-19 16:53(由build/sync.py同步,请勿在站点侧修改)