| 项目 | 内容 |
|---|---|
| 故障对象 | kube-infra/fke-influxdb(InfluxDB 3.3.0 Bitnami 镜像,后端存储 Ceph S3,生产库 hms-history-31d-v1-3) |
| 故障表现 | 域名 fke-influxdb.fftaicorp.com 整站 503;外部 nginx SSL_read() unexpected eof crit 风暴;Pod CrashLoopBackOff |
| 持续时长 | 156 天,累计重启 4256+ 次(事件计数 BackOff x95395) |
| 根因 | OOMKilled(exitCode 137):内存 limit 仅 4Gi,而启动时要回放 687 万个 WAL 碎片文件,回放期间内存峰值 10 秒内冲破上限被杀,无限死循环 |
| 修复 | 一条 kubectl patch:内存提到 48Gi + 摘除 liveness/readiness 探针 + 钉到大内存节点 → 一次启动成功 |
| 结果 | Pod 稳定 Running 0 重启,实际内存仅 319Mi;带 token /health 返回 200;生产写入恢复(已验证) |
2026/10/26 ... [crit] ... *123456 SSL_read() failed (SSL: ... unexpected eof while reading client request headers)
2026/10/26 ... [crit] ... *123457 SSL_read() failed (SSL: ... unexpected eof while reading client request body)
客户端粗暴掐断 TLS 连接导致。当时以为是 InfluxDB 写入客户端超时断连 —— 后经 access.log 核实,这批 crit 主要来自 apt 软件源客户端中断下载(UA 为 Debian APT-HTTP/1.3),与本案是两件事(见第 06 节附注)。教训:crit 日志先看请求来源再定性。
$ curl -sk -o /dev/null -w '%{http_code}\n' https://fke-influxdb.fftaicorp.com/health
503
$ kubectl get pods -n kube-infra | grep influx
fke-influxdb-6468d5d8d-hjkhr 0/1 CrashLoopBackOff 4256 (3m13s ago) 156d
写入端高频小批量写入(每秒 1 op)
└→ WAL 碎片化:687 万个小文件堆在 Ceph S3 上,持久化滞后 17 天
└→ 每次启动需全量回放 WAL → 未持久化数据全部加载进内存写缓冲
└→ 内存峰值 10 秒内冲破 4Gi limit → OOMKilled (137)
└→ 重启 → 又回放 → 又 OOM → 无限 CrashLoop(4256 次)
└→ Pod 永远不 Ready → Service endpoints 永远为空
└→ Ingress 无可用后端 → 域名 503
└→ 客户端写入全部失败 → 各种连锁报错
sudo kubectl describe ingress fke-influxdb -n kube-infra
# → 后端指向 Service kube-infra/fke-influxdb:8181
sudo kubectl get endpoints fke-influxdb -n kube-infra
# → endpoints 为空!Service 后面没有健康 Pod → 503 的直接原因
sudo kubectl describe pod fke-influxdb-6468d5d8d-hjkhr -n kube-infra | tail -40
# Events:
# Warning BackOff 2m21s (x95395 over 14d) kubelet Back-off restarting failed container
# Conditions: Ready=False, ContainersReady=False
# 关键环境变量: INFLUXDB_OBJECT_STORE=s3, AWS_ENDPOINT=https://fke-ceph.fftaicorp.com
# 挂载: 只有 secret + /tmp emptyDir(无 PVC,WAL 在对象存储上)
sudo kubectl get pod fke-influxdb-6468d5d8d-hjkhr -n kube-infra \
-o jsonpath='{.status.containerStatuses[0].lastState}'; echo
{"terminated":{... "exitCode":137,"finishedAt":"...T03:39:12Z",
"reason":"OOMKilled","startedAt":"...T03:39:02Z"}}
# exitCode 137 + reason OOMKilled,且 startedAt→finishedAt 只隔 10 秒:
# 容器启动 10 秒即被 OOM 杀掉!
sudo kubectl get deploy fke-influxdb -n kube-infra -o yaml | grep -A4 'resources:'
# limits: {cpu: "2", memory: 4Gi}
# requests:{cpu: "1", memory: 2Gi}
sudo kubectl logs fke-influxdb-6468d5d8d-hjkhr -n kube-infra --previous 2>&1 | grep -viE 'Persisting' | tail -40
# 大量: INFO influxdb3_wal::object_store: replaying WAL file ...
# n_ops=1 ... wal_file_number=6879325 → 6879341(还在递增)
# 另一波: INFO influxdb3_write::write_buffer: Persisting 6000+ rows ...
# → 落盘的 parquet 文件日期是 17 天前的!(持久化严重滞后)
WAL 文件编号已到 687 万且每个只有 n_ops=1;按日志回放速度(约 570 个/秒)估算,完整回放需要 3 小时以上,而容器 10 秒就 OOM——回放永远跑不完,每次重启从头再来,这就是 4256 次重启的死循环本质。
| # | 因素 | 细节 |
|---|---|---|
| 1 | 内存配额太小 | limits.memory=4Gi,是按"正常运行"估的,没考虑启动回放峰值 |
| 2 | WAL 碎片化 | 写入端每秒 1 个小操作 → 每个操作生成 1 个 WAL 文件 → 156 天攒了 687 万个文件在 Ceph S3 上 |
| 3 | 启动必须全量回放 | InfluxDB 3 把 WAL 放对象存储,启动时要把所有未持久化数据加载进内存写缓冲再逐批落盘成 parquet |
| 4 | 持久化滞后 17 天 | 落盘速度追不上写入速度,缓冲越积越大 → 回放内存峰值一年比一年高,终于在某个时刻突破 4Gi |
启动 → 回放 WAL(内存持续上涨)→ 10 秒冲破 4Gi → OOMKilled
↑ ↓
└────── kubelet 按 BackOff 间隔重启(最长 5 分钟)←──┘
副作用:每次重启前顺手落一小批盘(所以日志里能看到 Persisting),
但 4256 次 × 每次 10 秒,永远磨不完 17 天的积压。
Pod 永远不 Ready → endpoints 永远为空 → Ingress 对该域名一律 503;写入客户端等不到有效响应/超时,粗暴掐断 TLS 连接,外部 nginx 便记录 SSL crit。表象在 nginx,病根在 OOM。
sudo kubectl top nodes
# fke-01-runner 207624Mi 27% ← 768GB 大内存机器
# fke-02-runner 323970Mi 43% ← 同上,选它(老 Pod 本来就在这,必能调度)
sudo kubectl patch deploy fke-influxdb -n kube-infra --type json -p='[
{"op":"remove","path":"/spec/template/spec/containers/0/livenessProbe"},
{"op":"remove","path":"/spec/template/spec/containers/0/readinessProbe"},
{"op":"replace","path":"/spec/template/spec/containers/0/resources/limits/memory","value":"48Gi"},
{"op":"replace","path":"/spec/template/spec/containers/0/resources/requests/memory","value":"16Gi"},
{"op":"add","path":"/spec/template/spec/nodeSelector","value":{"kubernetes.io/hostname":"fke-02-runner.fftaicorp.com"}}
]'
| 钥匙 | 作用 | 不做会怎样 |
|---|---|---|
| 内存 48Gi | 给回放留足峰值空间 | 继续 10 秒 OOM,死循环 |
| 摘掉 liveness/readiness | 回放期间(约 3 小时)HTTP 端口未监听,tcpSocket 探针 40 秒就会误杀 | OOM 修好了也会被探针杀,换一种死法 |
| 钉到大内存节点 | 普通节点剩余内存有限,768GB 的 runner 节点随便给 | 可能调度到紧节点,或 limit 形同虚设 |
# 第一次钉 fke-01-runner 时 Pod Pending,事件显示:
# 30 node(s) didn't match Pod's node affinity/selector
# 原因:节点的 kubernetes.io/hostname 标签值可能与节点名不一致(短名 vs FQDN)
# 正确姿势:先读节点真实标签值,再用它打补丁
H=$(sudo kubectl get node fke-02-runner.fftaicorp.com \
-o jsonpath='{.metadata.labels.kubernetes\.io/hostname}')
echo "实际标签值: [$H]"
sudo kubectl patch deploy fke-influxdb -n kube-infra --type json -p \
"[{\"op\":\"replace\",\"path\":\"/spec/template/spec/nodeSelector\",\"value\":{\"kubernetes.io/hostname\":\"$H\"}}]"
# ① Pod 状态 + 内存(内存贴 47-48Gi 就再升 96Gi)
watch -n 30 'kubectl get pods -n kube-infra -o wide | grep influx; kubectl top pod -n kube-infra | grep influx'
# ② 落盘进度:parquet 文件日期从 17 天前一路追到"今天"就是好转
kubectl logs -f <新pod名> -n kube-infra 2>&1 | grep --line-buffered 'Persisting' | tail -3
# ③ 验收:带 token 打 /health
TOKEN=$(sudo kubectl get secret fke-influxdb -n kube-infra \
-o jsonpath='{.data.admin-token}' | base64 -d)
curl -s -o /dev/null -w 'health: %{http_code}\n' -H "Authorization: Bearer $TOKEN" \
-H 'Host: fke-influxdb.fftaicorp.com' http://10.18.51.8:30080/health
$ kubectl get pods -n kube-infra -o wide | grep influx
fke-influxdb-c698999cd-dqzzx 1/1 Running 0 115s 10.244.193.20 fke-02-runner
# 156 天来第一次摆脱"10 秒暴毙",老 Pod hjkhr 被滚动更新替换
$ kubectl top pod -n kube-infra | grep influx
fke-influxdb-c698999cd-dqzzx 15m 319Mi
说明 4Gi 撑不住的是启动回放的瞬时峰值,不是常态负载——这也是 OOM 类问题最迷惑人的地方:kubectl top 平时根本看不到峰值。
$ curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: fke-influxdb.fftaicorp.com' \
http://10.18.51.8:30080/health
401 # 带鉴权的 API 服务,没带 token 返回 401 = 正常(503 才是故障)
$ curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
-H 'Host: fke-influxdb.fftaicorp.com' http://10.18.51.8:30080/health
200 # ✅ 数据面正式恢复
$ kubectl logs fke-influxdb-c698999cd-dqzzx -n kube-infra --since=5m | grep 'flushing WAL' | tail -3
INFO ... flushing WAL buffer ... max_timestamp_ns=1793015553000000000 ...
# max_timestamp_ns 折算后 = 当前时刻 → 有数据正带着"现在"的时间戳进来
核对 access.log 发现,制造 crit 的 10.18.51.4 全部流量是 Debian APT-HTTP/1.3 的软件源下载——apt 客户端中断下载时掐断 TLS 是常态,属于良性噪音;而 InfluxDB 的写入方走的是集群内部直连(Pod → Service),根本不经过外部 nginx。两件事撞在一起,差点把排查方向带偏。
# 1. 看重启次数与状态
kubectl get pod <pod> -n <ns> # RESTARTS 很大 + CrashLoopBackOff
# 2. 看退出码(关键!)
kubectl get pod <pod> -n <ns> \
-o jsonpath='{.status.containerStatuses[0].lastState}'
# 3. 看内存用量与配额
kubectl top pod <pod> -n <ns>
kubectl get deploy <deploy> -n <ns> -o yaml | grep -A4 'resources:'
| exitCode / reason | 含义 | 典型动作 |
|---|---|---|
137 + OOMKilled | 内存超 limit 被杀 | 调大 limit;区分"常态吃满"还是"启动峰值" |
137(无 OOMKilled) | SIGKILL(手动 kill / 强制驱逐) | 查 who kill 了它(events / audit) |
143 | SIGTERM(liveness 探针杀 / 滚动更新) | 查探针是否误杀,考虑 startupProbe |
1 | 应用错误退出 | 看 logs --previous 找异常栈 |
101 | Rust 程序 panic(InfluxDB 3 属此类) | 看 panic 栈,考虑升级镜像 |
# 事故服务需要数小时回放,普通 liveness(tcpSocket, 10s 起)40 秒就杀
# 恢复后加回探针的正确姿势:startupProbe 宽容 + 常规 liveness 严格
startupProbe:
tcpSocket: {port: http}
periodSeconds: 10
failureThreshold: 720 # 10s × 720 = 容忍 2 小时启动
livenessProbe:
tcpSocket: {port: http}
periodSeconds: 10
failureThreshold: 3
fke-influxdb 由 Helm(chart influxdb-7.1.18)+ Argo CD 管理。本次 kubectl patch 的 48Gi/去探针/钉节点不在 Git 与 Helm values 里——任何人执行 helm upgrade 或 Argo 自动同步,4Gi + 探针就会回来,事故原样复发。热修之后必须尽快把变更持久化到 values/Git。# Pod 健康与内存
kubectl get pods -n kube-infra -o wide | grep influx
kubectl top pod -n kube-infra | grep influx
# HTTP 数据面(先取 token)
TOKEN=$(sudo kubectl get secret fke-influxdb -n kube-infra \
-o jsonpath='{.data.admin-token}' | base64 -d)
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" \
-H 'Host: fke-influxdb.fftaicorp.com' http://10.18.51.8:30080/health # 期望 200
# 写入是否恢复:WAL 时间戳应贴着当前时间推进
kubectl logs -n kube-infra deploy/fke-influxdb --since=5m 2>&1 \
| grep 'flushing WAL' | tail -3
# 落盘积压是否清零:Persisting 的文件日期应追上"今天"
kubectl logs -n kube-infra deploy/fke-influxdb 2>&1 \
| grep 'Persisting' | tail -3
复盘到这里必须掰正一个常见误解:InfluxDB 不是"API 服务挂别人的数据库",它自己就是数据库,API 只是门面。但 3.x 版本架构很特别 —— 它身后挂的不是数据库,而是对象存储(存算分离)。这次事故的每一个症状,都能从下面这张图上找到位置:
0/dbs/hms-history-31d-v1-3/robot_data_record_v3-0/2026-10-09/xx.parquet图:InfluxDB 3 存算分离架构 —— 红色标记 = 本次事故的三个关联点(缓冲 OOM / WAL 碎片 / 回放入口)
InfluxDB 3 的 object store 只认对象存储,没有传统数据库选项:
| 后端 | 说明 |
|---|---|
| AWS S3 | 官方云上方案 |
| Google Cloud Storage / Azure Blob | 另外两家公有云 |
| 任意 S3 兼容端点 | ⭐ 我们用的:Ceph RGW(同类还有 MinIO、阿里 OSS 等),配置即 AWS_ENDPOINT=https://fke-ceph.fftaicorp.com |
| memory | 仅测试用,数据放内存、重启即丢 |
| 优点 | 代价(对应症状) |
|---|---|
| ✅ 存算分离:计算节点随便重启、数据不丢 —— 崩 156 天,17 天的数据还躺在 WAL 里等着回放 | ⚠️ 每次启动都要从对象存储全量回放 WAL → WAL 一碎(687 万文件)启动即灾难 |
| ✅ parquet 列存:分析型 SQL 查询极快 | ⚠️ 内存缓冲是数据必经之路 → 回放/积压全压在内存,limit 给小就 OOM 死循环 |
| ✅ 容量随对象存储无限扩展,本地零 PVC | ⚠️ 对象存储性能就是它的天花板 → Ceph 慢,落盘滞后积压 17 天 |
| 版本 | 存储方式 | 特点 |
|---|---|---|
| InfluxDB 1.x / 2.x | 本地磁盘 TSM 引擎,需要 PVC | 传统时序库:重启不回放、内存可控,但要自己管本地盘容量 |
| InfluxDB 3.x(我们) | 对象存储 parquet + WAL,本地零持久化 | 存算分离、SQL 查询、容量无限;代价 = 启动回放 + 内存缓冲,正是本次事故踩中的两个坑 |
| 命令 | 输出 → 结论 |
|---|---|
nslookup fke-ceph.fftaicorp.com | CNAME → nginx-s3.fftaicorp.com → 10.18.53.6 —— 内网 IP,数据不出机房 ✅ |
kubectl get pods -A | grep ceph | 只有 ceph-csi(消费外部 Ceph 的"翻译官"DaemonSet),集群内无 RGW → Ceph 是机房独立集群 |
kubectl get storageclass | 双存储体系:Longhorn(默认 SC,节点本地盘) + csi-cephfs-sc(外部 CephFS);InfluxDB 两条路都没走,直用 S3 |
curl -skI https://fke-ceph.fftaicorp.com/ | Server: nginx/1.18.0 → S3 流量前面挡着 nginx-s3 反代(TLS 终止) |
curl -sk https://fke-ceph.fftaicorp.com/ | head | <ListAllMyBucketsResult>...<Owner><ID>anonymous</ID>...<Buckets></Buckets> —— RGW 独有指纹(匿名返回 200 空桶列表;MinIO 会回 AccessDenied)→ 后端实锤 Ceph RGW |
最终确认的完整链路 —— 注意 S3 流量与业务域名走的是两组不同的 nginx:
① InfluxDB 存储链(本次架构)
fke-influxdb Pod(fke 集群节点 10.18.51.x)
│ https://fke-ceph.fftaicorp.com(内网 DNS)
▼
nginx-s3 反代(10.18.53.6,nginx/1.18.0,TLS 终止)
▼
Ceph RGW(机房独立 Ceph 集群)→ OSD 物理盘
✅ 全程 10.x 内网,零外部依赖,数据不出机房
② 业务域名链(503 事故发生在这里,与 ① 互不影响)
浏览器 → nginx-master 主备 → 10.18.51.8:30080 → K8s Ingress → Pod
(这也解释了:域名 503 期间,InfluxDB 写 Ceph 的存储链一直是通的,
WAL 才会一直积攒 —— 写入端从集群内部直连,根本不经过外层 nginx)
influxdb-production-data 桶 ACL 是否严格私有;② 回放风暴期间 687 万请求都要过 nginx-s3 这一跳,它是 Ceph 之外的第二潜在瓶颈,排障时别忘了看它的 error.log。