让存储故障现形:Kubernetes Volume Health Monitor 原理与生产接入
一、开头:PVC 一直 Bound,但业务已经卡死半小时
存储故障是 Kubernetes 里最「阴险」的一类问题。一个 PV 后端其实是云盘,底层存储节点抖动、卷降级、甚至静默损坏,Kubernetes 侧看到的 PersistentVolumeClaim 状态却永远是 Bound ——因为 K8s 从来没有任何机制让 CSI 驱动「上报存储健康」。结果就是:应用 I/O 卡住、超时、报错,你却只能去翻云厂商控制台或存储阵列 dashboard 交叉比对,才能定位「哦,是那块盘出问题了」。在一次真实故障里,oncall 在三个系统之间来回切换了半个多小时才锁定根因,而那块卷从头到尾在 K8s 里都显示「正常」。这种黑盒,正是 Volume Health Monitor 要打破的。
这个问题社区盯了很久(KEP-1432 最早在 1.21 就有实现尝试),但在 1.37 它被 重新推进到 Alpha ,并带来了 4 个全新的 CSI RPC,让存储健康第一次成为「机器可读、可告警、可联动」的集群一等信号。本文讲清它的原理、如何接入,以及生产落地的注意点。
二、原理讲解:四个 CSI RPC 把健康搬进 API 对象
Volume Health Monitor 的设计哲学是:存储健不健康,只有 CSI 驱动(也就是存储后端)最清楚,所以让 驱动来报 ,kubelet 和控制平面来 收集并结构化 。
它定义了 4 个 CSI RPC,分两端:
控制器侧(Controller 插件上报,写入 PVC 状态):
ControllerListVolumeHealth:列出所有不健康的卷。ControllerGetVolumeHealth:查询某个具体卷的健康。
收集结果写入 PersistentVolumeClaim.status.healthStatus ,字段包含 status (如 Inaccessible 、 Degraded 、 Healthy )、 reason 、 message 。
节点侧(kubelet 调用,写入更细粒度状态):
NodeGetVolumeHealth:获取该节点上某个具体卷的健康,写入Pod.status.volumeHealth。NodeGetStorageHealth:获取该节点上注册的驱动整体健康,写入CSINode.status.storageHealth。
错误词汇保持简单、可扩展、可机器解析: Inaccessible (不可访问)、 Degraded (降级但仍可用)等,厂商可在 reason / message 里给更细的说明。
设计上,控制器侧与节点侧的报告 相互独立、分开展示 ——这很重要,因为「卷在控制器看来健康,但某个节点上挂载点坏了」是完全可能的,两者视角互补才能拼出完整画面。
三、实战配置:启用与读取健康信号
第一步:在 API Server 与 kubelet 开启 feature gate(1.37 Alpha)。
# kube-apiserver / kube-controller-manager
--feature-gates=VolumeHealthMonitor=true
# kubelet
--feature-gates=VolumeHealthMonitor=true
第二步:CSI 驱动需实现对应 RPC。 并非所有驱动都支持。确认你的存储驱动版本实现了 ControllerListVolumeHealth / NodeGetVolumeHealth 等。主流云盘 CSI(如 AWS EBS、GCP PD、Azure Disk 的新版)与部分企业存储已陆续支持;社区 hostpath 等示例驱动通常不含此能力。
第三步:读取健康状态。 控制器侧结果直接看 PVC:
kubectl get pvc data-vol -o jsonpath='{.status.healthStatus}'
# 示例输出:
# {"status":"Degraded","reason":"volume_latency_high","message":"p99 latency > 200ms"}
节点侧结果看 Pod:
kubectl get pod app-0 -o jsonpath='{.status.volumeHealth}'
第四步:把健康接入监控告警。 用 Prometheus 抓取 kube-controller-manager / kubelet 暴露的相关指标(或通过自定义 exporter 把 healthStatus 转成 gauge),示例告警思路:
# PromQL 思路:PVC 健康非 Healthy 即告警
expr: |
kube_pvc_health_status{status!="Healthy"} == 1
# 或更稳妥:基于 controller 暴露的 volume_health 指标
并在 GitOps/ remediation controller 中监听 healthStatus ,对 Inaccessible 的 PVC 触发自动迁移(如删 Pod 让其调度到健康节点、或触发存储故障切换)。
四、生产环境注意事项
- Alpha 成熟度预期。 1.37 是 Alpha,API 字段与行为可能在后续版本变动, 不要把它作为唯一的故障判定依据 ,应与存储厂商自身的监控互为补充。
- 驱动支持是前提。 若你的 CSI 驱动没实现这 4 个 RPC,开了 feature gate 也不会有任何
healthStatus出现。上线前务必在测试集群用kubectl get pvc -o yaml验证字段是否真的被填充。 - 告警分级。
Degraded不等于Inaccessible。建议:Degraded走警告级(工单),Inaccessible走紧急级(电话),避免告警风暴。 - 与 remediation 联动要谨慎。 自动把「卷不健康」的 Pod 删掉重调度,有可能把问题转移到别的节点,甚至扩大影响。联动逻辑要带冷却与最大重试次数。
- 别忘了节点侧视角。 只看
PVC.status.healthStatus(控制器侧)会漏掉「卷本身健康、但某节点挂载点坏了」的情况,务必同时消费Pod.status.volumeHealth与CSINode.status.storageHealth。
五、踩坑实录
- 坑 1:开了 gate 但 PVC 字段永远是空。 排查一通发现是 CSI 驱动版本太老,根本没实现
ControllerListVolumeHealth。修复:升级驱动到支持该 RPC 的版本,并在测试集群先验证字段填充。 - 坑 2:Alpha 字段名中途变更导致 exporter 报错。 1.37 的 Alpha 实现里
healthStatus的结构在早期候选版和 RC 间有过调整,我们的自研 exporter 用硬编码 JSONPath 解析直接挂掉。教训:Alpha 特性不要写死解析,加容错与版本判断。 - 坑 3:
Degraded误报告警淹没值班。 某存储驱动把「轻微延迟抖动」也报成Degraded,结果每天几百条告警。修复:在告警层对reason做白名单,只把volume_latency_high等真正影响业务的 reason 升级为紧急。 - 坑 4:自动 remediation 把问题放大。 监控到 PVC
Inaccessible后,自动化控制器立刻删 Pod,结果新 Pod 调度回同一 degraded 存储后端,循环崩溃。修复:remediation 改为「先隔离该存储后端 + 通知人工确认故障切换」,而非无脑重建。 - 坑 5:只盯 PVC 漏掉节点挂载点故障。 一次某节点 kubelet 与存储阵列网络分区,PVC 控制器侧显示 Healthy,但 Pod 在该节点上 I/O 全挂。后来补齐
Pod.status.volumeHealth监控才捕获。教训:控制器侧与节点侧必须一起看。
七、端到端告警链路搭建
把「存储健康」从 API 对象变成可行动的告警,需要一条完整链路:
- 采集 :kube-controller-manager 暴露的 volume 健康指标,或自写 exporter 把
PVC.status.healthStatus/Pod.status.volumeHealth转成 Prometheus gauge(如kube_volume_health{status="Degraded"})。 - 分级告警 :
Degraded→ warning 级工单;Inaccessible→ critical 级电话。对reason做白名单,避免厂商把「轻微抖动」也报 Degraded 造成告警风暴。 - 关联上下文 :告警里附带 PVC 所属命名空间、挂载它的 Pod、所在节点、底层存储卷 ID,让 oncall 一眼定位,而不是只收到一句「有个卷不健康」。
- 闭环 :告警触发 remediation(隔离存储后端 / 通知人工故障切换),并回写状态到 GitOps 或事件系统,形成可追溯的处置记录。
我们落地后,存储类故障的平均定位时间从「跨三个系统翻半小时」降到「告警里直接给卷 ID 和节点」,MTTR 缩短明显。
八、与存储故障切换(failover)的联动
Inaccessible 出现时,理想的动作不是「删 Pod 重调度」(可能调度回同一坏后端),而是触发存储层的 failover:
- 云盘类:调用云厂商 API 把卷从故障可用区 detachment 并在健康区 re-attach,或由存储类(
allowVolumeExpansion+ 多副本)自动切换。 - 企业存储:通知存储团队做阵列级切换,K8s 侧暂不盲目重建,避免放大影响。
- 设计原则:remediation 必须带 冷却时间 + 最大重试次数 + 人工确认开关 ,防止自动化在故障期间反复横跳。
Volume Health Monitor 给 failover 提供了「机器可读的触发信号」,但「怎么切」仍是存储后端的能力,两者要配合而非指望 K8s 单方面解决。
九、监控面板建议
Grafana 上至少放三张图:控制器侧 PVC 健康分布(按 status 堆叠)、节点侧 Pod 卷健康、CSINode 驱动整体健康。三者叠加,能直观看到「是单卷问题、单节点问题、还是整个驱动问题」——这正是 Volume Health Monitor 把控制器侧与节点侧分开设计的价值所在。
十一、常见疑问 FAQ 与速查
Q1:所有 CSI 驱动都支持吗?
不是。驱动必须实现那 4 个新 RPC(ControllerListVolumeHealth / ControllerGetVolumeHealth / NodeGetVolumeHealth / NodeGetStorageHealth)。上线前在测试集群用 kubectl get pvc -o yaml 确认 status.healthStatus 真的被填充。
Q2:Alpha 能上生产吗?
谨慎。1.37 是 Alpha,字段与行为可能变,不要作为唯一故障判定依据,与存储厂商自身监控互为补充。建议先在非核心业务试点。
Q3:健康字段为空是坏了吗?
未必。可能是驱动没实现 RPC,或 feature gate 只在 kubelet 侧开启而控制器侧没开。两端 gate 都要开,且驱动版本要对。
Q4:Degraded 和 Inaccessible 怎么区分处理?
Degraded =降级但仍可用,走 warning 工单; Inaccessible =不可访问,走 critical 电话并触发 remediation。对 reason 做白名单,避免轻微抖动刷屏。
Q5:只看 PVC 状态够吗?
不够。PVC 是控制器侧视角,漏掉「卷健康但某节点挂载点坏了」。必须同时消费 Pod.status.volumeHealth 与 CSINode.status.storageHealth ,两侧互补。
Q6:自动 remediation 安全吗?
要带冷却时间 + 最大重试 + 人工确认开关。无脑删 Pod 重调度可能调度回同一坏后端,反而扩大影响。自动化只负责「隔离 + 通知」,切换交给存储层或人工。
十三、Volume Health Monitor 接入清单
把存储健康信号接进生产体系,按此清单推进:
- API Server、kube-controller-manager、kubelet 均已开启 VolumeHealthMonitor feature gate(1.37 Alpha)。
- CSI 驱动已升级到实现 4 个新 RPC 的版本,并在测试集群验证字段被真实填充。
- 控制器侧读取
PVC.status.healthStatus,节点侧读取Pod.status.volumeHealth与CSINode.status.storageHealth,两侧都消费。 - 用 exporter 把健康状态转成 Prometheus gauge,建立 PVC / Pod / CSINode 三张可观测面板。
- 分级告警:
Degraded走 warning,Inaccessible走 critical,并对reason做白名单防刷屏。 - 告警携带上下文:命名空间、挂载 Pod、节点、底层卷 ID,让 oncall 一眼定位。
- remediation 带冷却时间、最大重试、人工确认开关,避免自动化在故障期反复横跳。
Inaccessible触发存储层 failover(云盘 detachment / 企业阵列切换),而非盲目重建 Pod。- 与存储厂商自身监控互为补充,Alpha 阶段不把 K8s 信号当唯一真相源。
- 文档化:把「卷不健康」的定级、责任人、处置 SOP 写入存储运维手册。
清单落地后,存储故障从「跨三系统翻半小时」变成「告警直接给卷 ID 与节点」,MTTR 显著缩短。
十五、要点回顾
Volume Health Monitor 填补了 Kubernetes 存储可观测性里最大的一块空白——让「存储到底健不健康」第一次成为集群内部可读、可告警、可联动的信号。落地要点三条: 驱动支持是前提 (没实现 4 个 RPC 就无字段); Alpha 别当唯一真相源 ,与厂商监控互补; 控制器侧与节点侧视角必须兼看 ,否则漏掉「卷健康但节点挂载点坏」的情况。把这套信号接进告警与 remediation 体系,存储类故障的 MTTR 能显著缩短,从「跨三系统翻半小时」变成「告警直接给卷 ID」。
十六、总结与行动建议
Volume Health Monitor 填补了 Kubernetes 存储可观测性里最大的一块空白——让「存储到底健不健康」第一次成为集群内部可读、可告警、可联动的信号。落地要点三条:驱动支持是前提(没实现 4 个 RPC 就无字段);Alpha 别当唯一真相源,与厂商监控互补;控制器侧与节点侧视角必须兼看,否则漏掉「卷健康但节点挂载点坏」的情况。
给不同风险偏好的建议: 稳健型团队 先在测试集群验证字段填充,把信号接进 Grafana 观察一个月再谈生产; 激进型团队 可在非核心业务试点,但 remediation 务必带冷却与人工确认,别让自动化在故障期反复横跳; 存储故障高发的团队 (如跨区云盘、老旧阵列)应优先落地,因为这类环境最能从「机器可读的健康信号」中获益。把这套信号接进告警与 remediation 体系,存储类故障的 MTTR 能从「跨三系统翻半小时」缩短到「告警直接给卷 ID 与节点」。1.37 把它重新推进 Alpha 是个强烈信号:存储健康正在成为一等公民,早接入早受益。
Volume Health Monitor 填补了 Kubernetes 存储可观测性里最大的一块空白——让「存储到底健不健康」第一次成为集群内部可读、可告警、可联动的信号,而不是藏在厂商 dashboard 里的黑盒。1.37 把它重新推进 Alpha 是个强烈信号:存储健康正在成为一等公民。落地时记住三点: 驱动支持是前提、Alpha 别当唯一真相源、控制器侧与节点侧视角必须兼看 。把这套信号接进你的告警与 remediation 体系,存储类故障的 MTTR 能显著缩短。