WAL导致Prometheus 启动失败
ENV
| 类别 | 信息 |
|---|---|
| 环境 | 基地A · Kubernetes 预发集群 |
| 节点 OS | Ubuntu 20.04.6 LTS,Kernel 5.4.0-216-generic |
| 容器运行时 | Docker 25.0.5 |
| Kubernetes | v1.24.6(Client/Server) |
| 监控栈 | Helm kube-prometheus-stack v56.21.4,Prometheus v2.50.1 |
| 存储 | NFS(nfs-client),挂载点 8.8.238.39:/data/data_nfs/... |
| Prometheus PVC | 50Gi RWO,实际使用约 7% |
| Grafana | grafana-hu.igozhang.cn(Ingress nginx) |
1. 问题现象
| 对象 | 状态 |
|---|---|
prometheus-monitoring-kube-prometheus-prometheus-0 |
Running 但 /-/ready 返回 503 |
| StatefulSet | 0/1 Ready(巡检时) |
| 容器重启 | 累计 2159 次 |
prom-q |
Failed(非监控组件,手工测试 Pod 残留) |
误导项: prom-q(curl 测试 Pod,54 天前失败)与监控栈无关,可忽略或删除。
2. 问题原因
2.1 定位推导过程
Step 1 — 确认 Ready 探针失败
timeout 10 kubectl exec -n monitoring \
prometheus-monitoring-kube-prometheus-prometheus-0 -c prometheus \
-- wget -qO- --timeout=5 http://127.0.0.1:9090/-/ready 2>&1
关键输出:HTTP/1.1 503 Service Unavailable
Step 2 — 查看启动日志
timeout 10 kubectl logs prometheus-monitoring-kube-prometheus-prometheus-0 \
-n monitoring -c prometheus --tail=60 2>&1
关键输出:停在 Replaying on-disk memory mappable chunks,TSDB 启动中。
Step 3 — 检查 WAL 与磁盘
timeout 10 kubectl exec -n monitoring \
prometheus-monitoring-kube-prometheus-prometheus-0 -c prometheus \
-- sh -c "df -h /prometheus; ls -lhS /prometheus/wal/ | head -n 10" 2>&1
关键输出:
/prometheus 983G 64.6G 7% (NFS)
wal/ total 57G,2615 个段文件,最后写入 4 月 28–29 日
Step 4 — 估算 Replay 与探针窗口
timeout 10 kubectl get pod prometheus-monitoring-kube-prometheus-prometheus-0 \
-n monitoring -o jsonpath='startupProbe={.spec.containers[0].startupProbe.failureThreshold}{" x "}{.spec.containers[0].startupProbe.periodSeconds}{"s\n"}'
关键输出:startupProbe=60 x 15s → 容忍 15 分钟
Replay 进度日志:
WAL segment loaded segment=175 maxSegment=2703 (~2.5s/段 → 总耗时 ~110 分钟)
Step 5 — 确认死亡螺旋
15 分钟探针超时 → 杀容器 → 从头 Replay → 再 503 → 再杀 → 2159 次重启
Step 6 — 修复过程 secondary 错误
清空 WAL 时未先经 CR 缩容,Operator 重建 Pod 与清理 Pod 同时挂载 NFS →
opening storage failed: lock DB directory: resource temporarily unavailable
2.2 根因结论
| 层级 | 原因 |
|---|---|
| 直接原因 | WAL 57GB / 2703 段,启动 Replay 需 ~110 分钟 |
| 触发机制 | Startup 探针仅容忍 15 分钟,Replay 未完成即杀容器 |
| 结果 | 「Replay → 503 → 被杀 → 再 Replay」死亡螺旋 |
| 存储因素 | NFS 上 TSDB,flock/I/O 弱,Replay 更慢、维护时易 lock 冲突 |
2.3 形成原因推测
运维在 4 月下旬 Prometheus 首次启动变慢后,未排查 WAL 膨胀根因而反复 delete/restart Pod;开发/运维 部署 Helm Chart 时使用默认 startup 探针(15 分钟)且 TSDB 放在 NFS,未配置 terminationGracePeriodSeconds 与 retentionSize。Pod 异常退出后 WAL 从 4/28 累积至 57GB 且 2 个月未完成一次完整 Replay,最终进入 2159 次重启的死循环。prom-q 为 运维 手工 curl 测试后未清理,造成巡检误判。
3. 解决方案
3.1 最终恢复步骤(已执行,TSDB 全新初始化)
① 经 CR 停止(防 Operator 重建)
kubectl patch prometheus monitoring-kube-prometheus-prometheus -n monitoring \
--type=merge -p '{"spec":{"replicas":0}}'
kubectl delete pod prometheus-monitoring-kube-prometheus-prometheus-0 \
prom-wal-clean -n monitoring --ignore-not-found --grace-period=0 --force
② 确认无 Pod 占用 PVC
kubectl get pod -n monitoring --no-headers | grep -E "prometheus-monitoring|prom-wal-clean" || echo "无相关 Pod"
③ 清空 TSDB 目录(丢失全部历史指标,全新采集)
# 临时 Pod 挂载 PVC 执行
# rm -rf /prometheus/* && mkdir -p /prometheus/wal && sync && echo FRESH_TSDB_OK
④ 经 CR 启动
kubectl patch prometheus monitoring-kube-prometheus-prometheus -n monitoring \
--type=merge -p '{"spec":{"replicas":1}}'
3.2 验证
timeout 10 sh -c "
kubectl get pod prometheus-monitoring-kube-prometheus-prometheus-0 -n monitoring \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{\": ready=\"}{.ready}{\" restarts=\"}{.restartCount}{\"\n\"}{end}';
kubectl exec -n monitoring prometheus-monitoring-kube-prometheus-prometheus-0 -c prometheus \
-- wget -qO- --timeout=5 http://127.0.0.1:9090/-/ready 2>&1
"
期望输出:
prometheus: ready=true restarts=0
Prometheus Server is Ready.
3.3 清理残留测试 Pod
kubectl delete pod prom-q -n monitoring
3.4 Helm 部署优化(防止复发)
在原部署命令基础上增加:
helm upgrade --install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--version 56.21.4 \
# ... 原有 grafana/alertmanager 配置保持不变 ... \
--set prometheus.prometheusSpec.retention='15d' \
--set prometheus.prometheusSpec.retentionSize='45GB' \
--set prometheus.prometheusSpec.walCompression=true \
--set prometheus.prometheusSpec.terminationGracePeriodSeconds=600 \
--set prometheus.prometheusSpec.resources.requests.memory='4Gi' \
--set prometheus.prometheusSpec.resources.limits.memory='8Gi' \
--set prometheus.prometheusSpec.containers[0].name='prometheus' \
--set prometheus.prometheusSpec.containers[0].startupProbe.failureThreshold=480 \
--set prometheus.prometheusSpec.containers[0].startupProbe.periodSeconds=15 \
--set prometheus.prometheusSpec.containers[0].startupProbe.httpGet.path='/-/ready' \
--set prometheus.prometheusSpec.containers[0].startupProbe.httpGet.port=9090
升级后验证探针:
timeout 10 kubectl get prometheus -n monitoring \
-o jsonpath='failureThreshold={.items[0].spec.containers[0].startupProbe.failureThreshold}{"\n"}'
期望:failureThreshold=480
4. 后续预防措施
开发
- Helm values 固化 startup 探针、
retentionSize、walCompression,禁止依赖 Operator 默认值。 - 评估 Prometheus TSDB 使用本地 SSD StorageClass,Grafana/Alertmanager 可继续 NFS。
- 限制高基数 label 与 scrape target 数量,控制 WAL 增长速度。
运维
- 维护标准流程: 先
Prometheus CR replicas=0→ 确认 Pod 消失 → 再操作 PVC → 再replicas=1;禁止对 Running Pod--force删除。 - 禁止
kubectl run测试 Pod 长期残留;用完即删。 - 监控告警:
PrometheusNotReady(10m)、PodRestartBurst(1h > 3 次)。 - 定期巡检 WAL 段数(> 500 预警)与 PVC 使用率。
- 密码通过 Secret 管理,不写进 shell history。
5. 配置参数建议
| 参数 | 当前值 | 建议值 | 原因/后果 |
|---|---|---|---|
startupProbe.failureThreshold |
60 |
480 |
60×15s=15min,小于 Replay 需求(~110min),必触发死亡螺旋 |
startupProbe.periodSeconds |
15 |
15(保持) |
配合 failureThreshold=480 → 120min 窗口 |
terminationGracePeriodSeconds |
默认 30s | 600 |
过短强杀导致 WAL 无法正常 flush,持续膨胀 |
retention |
15d |
15d(保持) |
已合理 |
retentionSize |
未设置 | 45GB |
50Gi PVC 留余量,防止磁盘写满 |
walCompression |
未开启 | true |
降低 WAL 体积与 Replay 时间 |
resources.limits.memory |
未设置 | 8Gi |
无 limit 易 OOM 强杀,留下大量 WAL |
storageClassName |
nfs-client |
本地盘(如 local-path) |
NFS flock/I/O 弱,Replay 慢且维护易 lock 冲突 |
| PVC size | 50Gi |
50Gi(保持) |
当前够用;配合 retentionSize 即可 |
附录:死亡螺旋机制
Pod 启动 → Replay 57GB WAL(~110min)
→ /-/ready = 503
→ startup 探针 15min 超时
→ kubelet 杀容器
→ RestartCount +1
→ 从头 Replay(永不完成)
优化后:WAL 控制在小体积 + startup 120min 窗口 + 优雅退出 → 正常运行时 Ready 在分钟级。