一次 EVS"disk doesn't exist"报错的完整排查:我差点删掉生产Kafka

集群持续报 EVS 云盘元数据同步失败,排查定位到是废弃 Kafka 残留的僵尸 PV/PVC 指向了已被删除的云盘。就在我以为万事俱备、准备执行清理时,删除前的最后一道终验揪出了一个假阴性------集群里还有一套同名的活 Kafka 集群。本文记录完整排查链路、我自己挖的坑,以及华为云 CCE 存储体系里几个值得记住的冷知识。


01 一条每 5 分钟刷一次的 Warning

那天上午,控制台事件里躺着一堆聚合报错:

text 复制代码
(combined from similar events): Failed to sync evs metadatas: add evs metadata failed:
failed to send csi request to add metadata to disk a1585c62-xxxx-xxxx-xxxx-xxxx
rpc error: code = Unknown desc = failed to add evs metadata,
evs disk a1585c62-xxxx-xxxx-xxxx-xxxx doesn't exist

拆解这条报错的三个信息点:

  1. Failed to sync evs metadatas ------ CCE 的云盘 CSI 驱动(everest)在周期性同步 EVS 云盘的元数据
  2. doesn't exist ------ 华为云 API 明确返回:这块盘不存在了
  3. combined from similar events ------ K8s 事件聚合机制,说明这不是一次性抖动,它在反复发生

作为单人运维,我的第一反应永远是三连问:影响业务吗?是不是误删?要不要回滚?

先定性:报错来自 controller 侧的后台同步,不触碰任何运行中的负载,当前零业务影响。但它揭示了一个事实------集群里有个存储对象在指向一块已经消失的盘。这事必须查清。

02 定位:谁在引用一块不存在的盘?

一条 jsonpath 把全集群 PV 按底层盘 ID 拉出来对账:

bash 复制代码
kubectl get pv -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.csi.volumeHandle}{"\t"}{.spec.persistentVolumeReclaimPolicy}{"\t"}{.status.phase}{"\n"}{end}' | grep a1585c62

输出关键信息:

text 复制代码
pvc-864bf0c0-xxxx    a1585c62-xxxx    Retain    Bound

两个状态值得玩味:

  • Retain ------ 删 PVC 不会联动删盘。说明盘是被人单独手动删掉的,不是走 PVC 删除的正常链路
  • Bound ------ 对应的 PVC 对象还活着,还绑在这个 PV 上

顺着查 PVC:

bash 复制代码
kubectl get pvc -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,PV:.spec.volumeName,CREATED:.metadata.creationTimestamp' | grep 864bf0c0
text 复制代码
middleware-test   data-kafka-controller-1   pvc-864bf0c0-xxxx   2026-04-10

是个 Kafka 的数据盘,4 月份创建的。但这里有个矛盾点:正在被 Pod 挂载的 EVS 盘受云厂商保护,删不掉。现在盘没了,反推可知------不可能有任何 Pod 真正挂着它。这大概率是个孤儿 PVC。

03 盘点:不止一块盘

用事件消息把所有同类报错的盘 ID 全量捞出来:

bash 复制代码
kubectl get events -A -o jsonpath='{range .items[*]}{.message}{"\n"}{end}' | grep -o "evs disk [a-f0-9-]*" | sort | uniq -c

3 块盘全部报 doesn't exist。再一看 StatefulSet:

text 复制代码
kafka-controller        0/0     139d     ← 4月建的旧集群,已缩容到 0,对象没删
kafka-test-controller   1/1     6d19h    ← 6天前新上的测试集群

时间线拼图完成:4 月部署的旧 Kafka 集群 → 后来废弃缩容到 0 → 6 天前一波"Kafka 治理"(新集群上线)→ 旧集群 3 块空闲状态的盘被顺手删了 → PV/PVC 对象残留在集群里,每 5 分钟被 csi-evs 摸一次报一次错。

3 块盘正好对上旧集群的 3 个副本,数据盘随集群废弃一同消失。清理方案很快定下来:

bash 复制代码
kubectl delete sts kafka-controller -n middleware-test
kubectl delete pvc data-kafka-controller-0 data-kafka-controller-1 data-kafka-controller-2 -n middleware-test
kubectl delete pv pvc-c4914973 pvc-864bf0c0 pvc-7c5fdbee

删除前照例三问:误删? ------只删废弃对象,已验证无 Pod 挂载;业务? ------数据已随盘消失,删对象零新增损失;回滚?------无可回滚,值得留的盘早已不存在。

看起来万事俱备。直到最后一道终验。

04 终验:救命的最后一道关

删除 PVC 前,我跑了三类引用核查:运行中的 Pod、工作负载模板、CronJob 模板。其中 Pod 实体扫描是这样:

bash 复制代码
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.volumes[*]}{.persistentVolumeClaim.claimName}{","}{end}{"\n"}{end}' | grep data-kafka-controller

输出让我后背一凉:

text 复制代码
middleware   kafka-controller-0   data-kafka-controller-0,,,,,,,
middleware   kafka-controller-1   data-kafka-controller-1,,,,,,,
middleware   kafka-controller-2   data-kafka-controller-2,,,,,,,

三个 Pod 正在运行,而且挂着 data-kafka-controller-0/1/2!

这里必须先认个错:我第一轮的"无引用"扫描,grep 用的是 PV 的 uid ,而 Pod 输出的是 PVC 名------两者永远匹配不上,输出为空给了我虚假的安全感。假阴性是我自己挖的坑,这次是被"删除前再扫一遍"的流程救的。

冷静下来看 namespace------命中的三行全部在 middleware ,而我要清理的对象全在 middleware-test

K8s 的一条铁则在这里起了决定性作用:Pod 只能挂载同 namespace 的 PVC

也就是说,集群里存在两套同名 Kafka 集群 :middleware 里有一套活的,middleware-test 里有一套废弃的------它们用同一个 StatefulSet 名字、同一套 PVC 命名模式(volumeClaimTemplate 生成的 data-<sts名>-<序号>),全靠 namespace 隔离,井水不犯河水。

05 同名集群,两个世界

接下来做边界对账,把两套集群的存储彻底钉死:

bash 复制代码
# middleware(活集群)的 PVC 绑定的 PV
kubectl get pvc -n middleware -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.volumeName}{"\n"}{end}' | grep data-kafka-controller
# 再查这些 PV 的底层盘 ID
kubectl get pv <PV名> -o custom-columns='NAME:.metadata.name,DISK:.spec.csi.volumeHandle'
text 复制代码
middleware 活集群:      盘 af26abbb / 40cd901a / 730868a5   (健康,挂载中)
middleware-test 废弃:   盘 a1585c62 / c6f674a6 / ee992f8e   (已消失,报错来源)

两组盘 ID 零交集。PV 和云盘是通过 volumeHandle 一对一映射的,PV uid 不相交 = 盘必然不相交------这不是概率推断,是结构性事实。

还有一个漂亮的旁证:middleware 活集群的 kafka-controller-1 Pod 六天前刚重启过,并成功重新挂载了数据盘------挂载中的盘受云厂商保护删不掉,它活得好好的。

边界确认,放行执行。全程命令显式带 -n middleware-test,这个参数就是防同名误伤的最后防线。

06 执行:三步清理与一个 NotFound

bash 复制代码
kubectl delete sts kafka-controller -n middleware-test      # deleted
kubectl delete pvc data-kafka-controller-0 data-kafka-controller-1 data-kafka-controller-2 -n middleware-test   # deleted ×3
kubectl delete pv pvc-c4914973 pvc-864bf0c0 pvc-7c5fdbee    # NotFound ×3 !

第三步报 NotFound 不是错误------标准 K8s 下 Retain 策略的 PV 在 PVC 删除后应该变成 Released 留下来等手动清理,但 CCE 的 everest 插件在联动删除 PV 对象,我跑到第三步时它们已经没了。

这是华为云 CCE 和原生 K8s 行为的一个真实差异点,记下来,以后清存储别按原生预期走。

执行后验证:生产 3 broker + 1 UI、测试 1 broker + 1 UI,6 个 Pod 的 AGE 一个没变------零重启零触碰;middleware-test 里只剩新集群的 PVC;原来的 doesn't exist 报错归零。

07 一条吓人的警告:finalizers Forbidden

删除完成后,事件里立刻冒出三条新警告:

text 复制代码
PersistentVolume "pvc-7c5fdbee-xxxx" is invalid: metadata.finalizers: Forbidden:
no new finalizers can be added if the object is being deleted,
found new finalizers []string{"kubernetes.io/pv-protection"}

乍一看吓人,拆解后是删除窗口期的一次正常竞态:

  • everest 联动删除 PV,给对象打上"删除中"标记(deletionTimestamp)
  • 与此同时 K8s 的 pv-protection 控制器发现"这 PV 刚才还 Bound 着,得加保护锁"
  • API server 拒绝:正在删除的对象禁止添加新 finalizer
  • 保护锁没加上,PV 干净利落地删除完成

所以这条警告反而是删除成功的旁证。如果 PV 真被保护锁卡住,kubectl get pv 会看到它挂着 Terminating 状态赖着不走,那才是需要处理的情形。

08 怎么判断"报错真的止住了"?

分享一个实战技巧,看 kubectl get eventsLAST SEEN 列:

bash 复制代码
kubectl get events -A | grep -E "doesn't exist|finalizers"
  • 报错还在发生 → LAST SEEN 永远是 5s23s1m 这种不断刷新的值
  • 报错真停了 → LAST SEEN 定格在某个值不再变化

我删除后 7 分钟查,三条 finalizer 警告全部定格在 7m31s------正是执行删除的那一刻,之后再没发生任何一条。事件对象有约 1 小时的 TTL,过期前 grep 有输出是正常的,别慌,看时间戳有没有在刷新。

09 复盘:这次踩坑换来的东西

知识点清单

  1. Pod 只能挂载同 namespace 的 PVC ------ 跨 ns 同名资源必须核对 ns 列,同名≠同一套
  2. CCE everest 对 Retain PV 会联动删除(而非标准 K8s 的 Released 保留),删 PV 报 NotFound 属预期
  3. pv-protection finalizer 的竞态警告无害,反而是删除流程正常完成的旁证
  4. volumeClaimTemplate 的引用不出现在 spec.template.spec.volumes ------ 模板层扫描有结构性盲区,引用核查要以运行中 Pod 实体为准
  5. 判断报错止住没止住,看事件 LAST SEEN 是否持续刷新,不看 grep 有没有输出
  6. 中间件下线 SOP:删 sts → 删 PVC → 确认云侧盘消失,三连清。僵尸 Bound PVC 不触发业务告警,只刷存储事件------而"没告警的资源"恰恰是最容易被当成垃圾清掉的

流程教训

  • "零引用"这类否定性结论,验证命令的关键词必须和被查对象的命名体系对齐。我拿 PV uid 去匹配 PVC 名,输出为空给了我虚假的安全感
  • 删除类操作永远显式带 -n,这是防同名误伤的最后防线
  • 终验要设计成"自己跑、眼见为实"------删除面前,口头保证一文不值

写在最后

单人运维没有第二双眼睛,所有结论最终只有自己兜底。这次如果没有删除前的最后一道终验,我可能已经基于一个假阴性结论执行了清理------命令里显式带 namespace 也许能保住活集群,但整个行动就建立在错误的认知上了。

你也有过"输出为空就以为万事大吉"的时刻吗?评论区聊聊。如果觉得有用,点个赞让更多被 K8s 存储问题折磨的同学看到。

本系列记录单人运维的实战日常,下一篇:《镜像堆积把节点磁盘干到 75%,我只删旧版本就释放了 40GB》

相关推荐
优化Henry16 分钟前
5G站点BBU功能模块集成化与偶发案例
运维·网络·学习·5g·tdd
小HANN1 小时前
高可用架构实战:Keepalived+LVS(DR)+MariaDB主主集群搭建指南
linux·运维·服务器·经验分享
Sylvia-girl1 小时前
【一】:Linux基础指令
linux·运维·服务器
KKKlucifer2 小时前
运维域与IT域融合——云网安全一体化运营体系建设
大数据·运维·安全
0+1112 小时前
Linux --基础IO
linux·运维·服务器
资深电气设计2 小时前
雕铣机主轴驱动升级趋势分析:变频器技术价值与选型逻辑探讨
运维
名字还没想好☜2 小时前
Prometheus 告警实战:写 alerting rules、用 Alertmanager 做路由分组与抑制
运维·前端·javascript·docker·kubernetes·prometheus
Zkaisen3 小时前
【Gitee】SSH 公钥、GPG 公钥、私人令牌的区别
运维·gitee·ssh
HXSYS3 小时前
无人机库房管理标准化,《无人机装调维修师》规范存储运维
运维·无人机