本文主要记录 Namespace 长期处于 Terminating、常规 kubectl delete / patch finalizer 均失败时的排查过程;案例中根因是 KubeVirt 相关 ValidatingWebhook 指向的 Service 已不存在,形成删除死锁。文末补充在 API 路径走不通时通过 etcd 清理的高危操作说明。

需要注意的是:直接操作 etcd 属于破坏性手段,仅适用于确认业务可丢弃、且 API 与 webhook 已无法自愈的实验/可重建环境。生产集群应优先修复 webhook / 卸载 Operator,而不是一上来 etcdctl del

问题现象

查看集群 Namespace:

kubectl get ns -o wide

示例输出(主机名与内网信息已脱敏):

NAME                   STATUS        AGE
argocd                 Active        398d
default                Active        491d
kube-system            Active        491d
kubevirt               Terminating   433d
nginx-gateway          Terminating   190d
purelb                 Active        491d
...

kubevirtnginx-gateway 等长期停在 Terminating,常规删除无进展。

环境背景

项目说明
场景自建 Kubernetes 集群
问题 NS示例为 kubevirt(曾部署 KubeVirt)
症状特征deletionTimestamp 已写入;spec.finalizers 仍含 kubernetes
关联组件ValidatingWebhook / Operator Service 缺失

具体发行版与 K8s 小版本以现场为准(待确认)。

排查过程

查看 Namespace 详情

kubectl get ns kubevirt -o yaml

关键字段示意(已裁剪):

metadata:
  name: kubevirt
  deletionTimestamp: "2023-11-13T09:04:18Z"
spec:
  finalizers:
  - kubernetes
status:
  phase: Terminating
  conditions:
  - type: NamespaceDeletionDiscoveryFailure
    status: "True"
    reason: DiscoveryFailed
    message: >-
      Discovery failed for some groups ... subresources.kubevirt.io/v1: stale GroupVersion discovery
  - type: NamespaceDeletionContentFailure
    status: "True"
    reason: ContentDeletionFailed
    message: >-
      Failed to delete all resource types ... failed calling webhook "kubevirt-validator.kubevirt.io":
      Post "https://kubevirt-operator-webhook.kubevirt.svc:443/kubevirt-validate-delete?timeout=10s":
      service "kubevirt-operator-webhook" not found
  - type: NamespaceContentRemaining
    status: "False"
    reason: ContentRemoved

解读:

  • 删除已发起(有 deletionTimestamp),kubernetes finalizer 仍在,说明控制面认为清理未完成。
  • NamespaceDeletionContentFailure=True 是关键信号:删除内容时调用 webhook 失败。
  • 错误核心:service "kubevirt-operator-webhook" not found —— 校验/删除 webhook 还在,后端 Service 已没了。

Namespace 卡在 Terminating 的常见原因

在深入案例前,先对齐一般原因(便于对照 describe / conditions):

  1. 资源删不干净:对象带 finalizer;PVC/PV 绑定;CRD 与自定义资源依赖。
  2. Webhook 阻断:Validating/MutatingWebhook 指向的 Service/Pod 不可用,更新与删除被 API 拒绝。
  3. 控制面异常:namespace-controller、apiserver 异常(相对少见,先查前面两类)。

优先命令:

kubectl describe ns <namespace>
kubectl get all,crd -n <namespace>
kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration

检查 NS 内残留

kubectl get all -n kubevirt

示例仍可见 KubeVirt 聚合资源:

NAME                            AGE    PHASE
kubevirt.kubevirt.io/kubevirt   433d   Deployed

尝试编辑 / 删除 CR 失败

试图去掉 CR 上的 finalizer 或直接删除:

kubectl edit kubevirt.kubevirt.io/kubevirt -n kubevirt
# 拟删除 finalizers: foregroundDeleteKubeVirt

报错示意:

error: ... failed calling webhook "kubevirt-update-validator.kubevirt.io":
Post "https://kubevirt-operator-webhook.kubevirt.svc:443/kubevirt-validate-update?timeout=10s":
service "kubevirt-operator-webhook" not found
kubectl delete kubevirt.kubevirt.io/kubevirt -n kubevirt

同样因 delete 校验 webhook 找不到 Service 而失败。

强制删 Namespace 仍不够

kubectl delete ns kubevirt --grace-period=0 --force

可能提示 force deleted,但 kubectl get ns 仍显示 Terminating。原因是 finalizer / 内容删除逻辑仍被 webhook 与 API 对象卡住,--force 并不能绕过所有准入与 etcd 中的残留关系。

原因分析

删除链路形成死锁:

  1. 删除 Namespace / CR 需要调用 KubeVirt 的 ValidatingWebhook。
  2. Webhook 配置仍指向 kubevirt-operator-webhook.kubevirt.svc
  3. 该 Service(及可能的 Operator 负载)已不存在。
  4. 准入失败 → 资源删不掉 → kubernetes finalizer 不移除 → Namespace 永久 Terminating

NamespaceDeletionDiscoveryFailure(stale GroupVersion)说明 KubeVirt 相关 APIService / 发现也处于不健康状态,进一步拖累清理。

优先修复路径(推荐,本文案例未完整走通时再考虑 etcd):

  1. 重新安装/恢复 KubeVirt Operator,使 webhook Service 可用,再按官方流程卸载。
  2. 或删除/修改相关 ValidatingWebhookConfiguration / MutatingWebhookConfiguration(确认无其他业务依赖后),再删 CR 与 Namespace。
  3. 清理失效 APIService,消除 discovery 失败。

仅在上述路径不可用、且接受数据丢失时,才考虑下一节的 etcd 强清。

解决方法

常规手段(优先)

移除 Namespace finalizer(仅当确认内容已空或可丢弃):

kubectl patch ns <namespace-name> -p '{"metadata":{"finalizers":null}}' --type=merge

若 webhook 仍拦截对子资源的操作,这一步也可能不够;需先处理 webhook 配置。

删除失效 webhook 配置示例(名称以现场 kubectl get 为准):

kubectl get validatingwebhookconfiguration | grep -i kubevirt
kubectl delete validatingwebhookconfiguration virt-operator-validator virt-api-validator
# 视情况处理 mutatingwebhookconfiguration

然后再删 CR、CRD 与 Namespace。

高危:etcd 直接删除残留键

以下操作会绕过 API 语义,可能导致集群状态不一致。执行前:

  • 备份 etcd
  • 确认目标键仅属于待废弃组件
  • 在维护窗口操作,并准备好从备份恢复

列出相关键(示例,需已配置 etcdctl 与证书,路径按发行版调整):

etcdctl get / --prefix --keys-only=true | grep virt

可见 CRD、APIService、ClusterRole(Binding)、Webhook 配置、Namespace、CR 实例等大量前缀。

按键删除(示意;务必人工审阅列表,禁止无过滤整库删除):

# 示例:对审阅后的键逐条或可控地批删
etcdctl del /registry/namespaces/kubevirt
# 以及对应 CRD、webhook、RBAC、kubevirt CR 等键

案例中曾用类似:

etcdctl get / --prefix --keys-only=true | grep virt | xargs -t -I{} etcdctl del {}

del 返回 1 表示删到键,0 表示当时已不存在。该写法风险极高:grep virt 可能误伤无关键,不建议在生产照搬;应导出列表、人工确认后再删。

验证

kubectl get ns
kubectl get crd | grep -i kubevirt || true

期望:kubevirt Namespace 消失;KubeVirt 相关 CRD 不再列出(若已删 CRD 键)。案例中清理后 kubectl get ns 不再出现 kubevirtkubectl get crd 中 KubeVirt 系列 CRD 消失,仅保留集群其它组件 CRD。

同集群上若还有 nginx-gatewayTerminating,需单独按同样方法看 conditions,不要假设同一根因。

验证结果

  • 在移除 etcd 中 KubeVirt 相关对象后,API 侧 Namespace 与 CRD 列表恢复干净(以当时 kubectl get 为准)。
  • 强制 kubectl delete ns --force 单独使用未能结束 Terminating。
  • 根因确认:webhook Service 缺失导致 ContentDeletion 失败,而非单纯“忘了 patch finalizer”。

注意事项

  • 先修 webhook / 官方卸载,再考虑 etcd。把 etcd 当默认清 NS 手段会埋下更难查的脑裂。
  • kubectl patch finalizers null 在仍有保护性 webhook 或未删净资源时可能无效或有副作用。
  • 删除 CRD 会级联影响该类型全部自定义资源;操作前确认无残留业务 VM / CR。
  • etcd 前缀因发行版、k8s 版本可能不同;以本集群 etcdctl get 为准。
  • 本文命令输出为历史排障记录的脱敏整理,不保证适用于当前 KubeVirt 版本的卸载流程。

参考资料