Skip to content

清理未使用的集群资源(kor 审计 → 备份 → 删除 → 恢复)

kor 以只读 exporter 形态常驻,把「没有任何工作负载引用」的资源暴露成 kubernetes_orphaned_resources 指标(见 specs/008-kor-monitoring/spec.md)。 本页说明怎么读这些结果、哪些能删、删之前怎么备份、删错了怎么恢复。

三条铁律

  1. GitOps 管理的资源一律不手动删。删了 ArgoCD 会重建,只会产生漂移与同步抖动; 确实不该存在的,请改 Git 或删对应的 Application。
  2. standard-rwo 的回收策略是 Delete。删 PVC 会连 Ceph RBD 镜像一起删, 数据当场消失;etcd 快照只含对象定义,救不回数据。
  3. etcd 快照的恢复窗口只有约 12–24 小时(见下文实测)。删完要立刻验证。

1. 数据来源与历史快照

来源 用途
指标 kubernetes_orphaned_resources(标签 kind / exported_namespace / resourceName) 全量发现结果(在线、始终最新)
Grafana Kor Dashboard(uid Zrue32P7Ik) 总量、按 namespace、按 kind 分布
scripts/kor-audit 把指标结果与集群实况交叉核对,输出 A/B/C/U/? 分级报告
scripts/kor-audit --out /tmp/kor-audit.md

本页出现的计数都是历史快照,会随集群漂移

下面的数字保留下来只为解释「过滤配置与分级规则怎么影响结果」,不要照抄; 任何一次清理前都以 scripts/kor-audit 的当次输出为准。

历史快照(2026-10-03,清理前):925 条,13 类。

kind 数量 kind 数量
ReplicaSet 486 Secret 90
Crd 127 ConfigMap 68
Job 40 Pvc 30
ClusterRole 26 Service 11
ClusterRoleBinding 10 RoleBinding 7
ServiceAccount 4 StorageClass 1

这个快照早于 exporter 的过滤配置,数字对不上是正常的

后来给 exporter 加了两个 --exclude-labels(见 system/kor/values.yaml 的注释), 以下对象不再出现在报告里:

  • app.kubernetes.io/managed-by=Helm:声明式对象由 Git 管理,不该按 Pod 使用情况判定。 副作用是 ReplicaSet 会从 Pod 模板继承该标签,Helm 部署产生的旧 RS 因此整批不再出现。 不带该标签的 RS(kubectl / operator 直接创建的工作负载)仍会被报出,所以这个 kind 的数量取决于工作负载怎么部署,不是恒为 0。
  • tailscale.com/managed=true:Tailscale operator 管理的 21 张 Ingress 证书与 8 个 ProxyGroup/operator 状态 Secret。这批对象无法用 kor/used=true 排除—— operator 会重写 Secret 元数据把外部标签冲掉(实测)。

快照里那 25 条 VolumeAttachment 已从上面的表里去掉:它们当时确实出现在指标里, 但 25/25 都是活跃挂载的假阳性(成因见 §2),不属于清理对象。

同口径实测(kor all -o json,过滤前后各跑一次):871 → 622, 其中 Crd 126 → 91、ReplicaSet 487 → 381、Secret 62 → 25、ConfigMap 62 → 21。 线上指标与之一致(918 → 622,918 里还包含后来被清理掉的 22 个 RBAC/PVC/Service 对象)。

CRD 的过滤只在 exporter/all 路径生效

processCrds 有两个调用者:pkg/kor/all.go 的 getUnusedCrds 会传入真实 filterOpts(exporter 走这条,CRD 会被正常过滤),而 pkg/kor/crds.go 的 GetUnusedCrds 把它丢弃、改用空 Options(独立子命令 kor customresourcedefinition 走这条,过滤器无效)。 用子命令去验证本配置会得到相反结论——排查时请用 kor all 或直接看指标。

--ignore-owner-references 则会把全部派生对象一起藏掉(带 ownerReferences 的对象 整批消失),不建议使用。

2. kor 的判定语义(决定哪些是假阳性)

不看这一节就会删错。kor 的判定是「有没有 Pod/EndpointSlice 引用」,而集群里大量资源 是被 CR、Ingress、sidecar、operator 按名字引用的,不上 Pod,于是被误报:

kind kor 的判据 主要假阳性
Crd 该 CRD 的所有 served 版本都查不到自定义资源 CRD 属于已安装的 operator/chart,删了会被重建;且删 CRD 会级联删掉其全部 CR
Secret 没被 Pod 挂载或作为环境变量引用 VolSync 备份仓库口令、Tailscale TLS 证书(被 Ingress 的 spec.tls 引用)、ESO/operator 生成的密钥、k3s etcd 快照凭据
ConfigMap 同上 Grafana dashboard(sidecar 读)、rook/kubevirt 的 operator 配置与 CA(被 CR/webhook 引用)
ClusterRole / Role 没有任何 RoleBinding/ClusterRoleBinding 引用 operator 的内置聚合角色、已卸载组件的遗留
Service 其 EndpointSlice 为空 prometheus-operator 的手工 Endpoints 型 headless Service;后端暂时不健康的服务;已 scale 到 0 的工作负载
Pvc 没有 Pod 挂载 停止中的 VM 的数据盘(VM 定义里按名引用)、VolSync 的 restic 缓存卷
ReplicaSet spec.replicas=0 且状态全 0 无(旧 revision 就是旧 revision)
Job 已完成 / 失败 / 挂起 无(但日志会随对象一起消失)
VolumeAttachment 它逐条 Get VA 引用的节点,看不到「有没有 Pod 在用」 几乎全是假阳性:RBAC 缺 nodes/csidrivers 时 Get 失败,于是每条 VA 都被报成 Node ... does not exist(2026-10-03 实测 25/25 都是活跃挂载,见 §4.2);补权限后该 kind 当前为 0 条
StorageClass 没有 PV/PVC 使用它 GitOps 管理,删了会被重建

两个最容易看错的地方

  • namespace ≠ exported_namespace。指标里的 namespace 是采集目标自己的 namespace(恒为 kor),资源真正所属的 namespace 在 exported_namespace。
  • RS 上的 ArgoCD 注解是继承来的。旧 ReplicaSet 上的 argocd.argoproj.io/tracking-id 是从 Deployment 的 Pod 模板继承的,不代表 ArgoCD 在管这个 RS(ArgoCD 管的是 Deployment,不会重建 RS)。 所以判「是不是派生对象」要看 ownerReferences,不能看标签。

3. 分级结果

分级完全由当次报告决定,本页不写死数量——先跑 scripts/kor-audit(见 §1)。 五个级别:

A  可直接批量清理   控制器不会重建,且不含数据
B  需人工确认       可能被停止中的工作负载引用,或属已卸载组件的遗留
C  禁止删除         GitOps/operator 管理、删除即丢数据,或属 CSI 运行时状态
U  脚本未覆盖       RESOURCES 表里没有这个 kind,核对不了集群实况,需人工核对
?  指标里还在、集群里已查不到   时序问题,等下一轮扫描刷新即可

三点容易误判的地方:

  • A 类不是恒为空。--exclude-labels 只挡掉带 app.kubernetes.io/managed-by=Helm 的旧 RS;不带该标签的旧 RS(kubectl / operator 直接创建的工作负载)仍会进 A 类。 2026-10-03 清理后 A 类一度为 0,之后随新部署又长回来——A 类为空同样正常。
  • U 类要当回事。kor 的扫描范围比脚本的 RESOURCES 表大(Deployment、StatefulSet、 DaemonSet、Ingress、PV、Role、Pod、HPA、PDB、NetworkPolicy、PriorityClass 等)。 这些 kind 一旦出现在指标里就会进 U 类,脚本给不出判据,必须按对象名人工核对。
  • ? 类通常不用管,下一轮扫描后自愈;持续不消失才说明 kubectl 侧与指标侧不一致。

A 类:可直接批量清理

对象 为什么安全
ReplicaSet(旧 revision) 由 Deployment 生成,spec.replicas=0 且状态全 0;删除后不会被重建,只少一条 kubectl rollout undo 历史
Job(已完成) CronJob 不重建历史运行;删了日志会跟对象一起消失,建议先留日志
PVC / Service(Woodpecker 流水线工作区与临时 headless Service) 无 Pod 挂载、无 owner 的孤儿流水线残留;删前确认当前没有流水线在跑

收益要如实说(下面数字是 2026-10-03 清理前的实测):

  • 当时那 557 个对象只占 etcd 元数据(DB 约 71 MiB),清理不会显著改变集群规模; 价值在于让 kor 的后续报告不再被历史噪声淹没。
  • 唯一有实际空间收益的是 7 个 Woodpecker PVC:名义申请 70 GiB,rbd du 实测 实际占用合计约 0.5 GiB(1 个 0 B,6 个约 80–84 MiB)。RBD 是精简置配, 所以别期待释放 70 GiB。
  • VolumeAttachment 不在清理范围内,见 §4.2。

B 类:需人工确认

对象 结论
kubevirt / cdi 的内置 ClusterRole、kubevirt-apiserver-auth-delegator、kube-apiserver-kubelet-admin 不删:属 kubevirt/CDI 安装的一部分
loft-cluster-* ClusterRole ×4、pod-impersonation-shell-* ×2(ClusterRole+Binding) 已卸载组件的遗留(集群中已无 loft/cattle 命名空间)。可删,但收益几乎为零,建议随下次大扫除一起处理
kubevirt-*-ca 等 ConfigMap ×5、default/scheduled-jobs、grafana/pcp-app-provisioning 不删:被 CR / webhook / 应用按名字引用
volsync-src-*-cache PVC ×17 无收益:VolSync 会重建
kubevirt-test/prod-vm-datadisk 不删:virtualmachine/prod-vm(当前 Stopped)的 datadisk 就指向它
kubevirt/virt-exportproxy Service 不删:kubevirt-operator 管理

C 类:禁止删除

C 类数量最大,报告里只给判据与计数(不逐个列对象)。2026-10-03 快照的构成是 Crd 127、Secret 90、StorageClass 1(standard-rwx,GitOps 管理且无 PVC 使用, 但删了会被重建)、GitOps 管理 85、operator/CR 拥有 24、DataVolume 生成的 VM 根盘 5; 当次构成以报告的 C 类小节为准。

其中最危险的三个具体对象,删掉直接出事:

对象 后果
kube-system/k3s-etcd-snapshot-s3-config 集群的 etcd 备份立刻停摆——这是最后一道防线
<app>/volsync-src-*、*-backup-repository VolSync 的 restic 仓库口令;删了对应卷的历史备份就再也解不开
tailscale/<域名> TLS Secret 该域名的 HTTPS 当场失效(Tailscale operator 会重新签发,但有中断)

4. 方案:备份 → 删除 → 恢复

4.1 备份(删除前必须完成)

三层,缺一层就有不可恢复的场景:

① etcd 快照(整集群对象定义) —— 已有自动化,实测可用:

# 凭据在 kube-system/k3s-etcd-snapshot-s3-config;bucket 在 NAS 上
# 本机没有 aws/mc,用 rclone 直接读同一份凭据(只列对象,不打印凭据)

实测状态:S3 端点 http://192.168.3.216:8010(NAS,明文 HTTP),bucket k3s-etcd-snapshot,每 12 小时(00:00 / 12:00)每台控制面各一份,当前共 8 个对象, 最新一份是当天 12:00。

恢复窗口很短

实测每台控制面在 S3 里只留最近 1–2 份快照。也就是说「误删 → 恢复」的窗口大约 只有 12–24 小时。删完必须立刻验证,不要隔天再看。

k3s 侧的备份配置见 Backup K3s Etcd Snapshot To S3; 在节点上核对可用 sudo k3s etcd-snapshot ls(本机 SSH 不可用,未从本机验证)。

② 待删对象的 YAML 导出 —— 针对 A 类(它们不在 Git 里,没有第二份定义):

mkdir -p ~/kor-backup/2026-10-03 && cd ~/kor-backup/2026-10-03
# 用 scripts/kor-audit 生成的清单逐条导出(保留 metadata,便于原样 apply 回去)
kubectl -n woodpecker get pvc wp-01kq... -o yaml > woodpecker-pvc-wp-01kq....yaml
kubectl -n woodpecker get svc wp-hsvc-7257 -o yaml > woodpecker-svc-wp-hsvc-7257.yaml
kubectl -n renovate get job renovate-1763392138332 -o yaml > renovate-job-....yaml

导出物不能入库

这里会包含 Secret 的内容。导出的目录不要放进仓库(宪法禁止提交凭据), 放在本机仓库之外,或用 age/gpg 加密后再写进备份介质。

③ PVC 数据(若确实要删有数据的卷):

# VolSync 已有现成通道:先备份,再删
./scripts/backup --action setup --namespace=<ns> --pvc=<pvc>
# 恢复时
./scripts/backup --action restore --namespace=<ns> --pvc=<pvc>

另有一条单对象保险:把 PV 的回收策略改成 Retain,删 PVC 后镜像仍在, 可以手工重建 PVC 指回去。

kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

本页涉及的 A 类 PVC 不需要这一层

7 个 Woodpecker PVC 是构建工作区,实测合计约 0.5 GiB,没有保留价值。 但如果你把 B/C 类里有数据的 PVC 纳入清理范围,这一层是必须的。

4.2 删除

前置检查(逐项打勾)

  • 目标对象不在 Git 中(scripts/kor-audit 判为 A 类,且不属于 C 类任一判据)
  • 当前没有工作负载在用(Woodpecker 场景:kubectl -n woodpecker get pods 只有 server/agent)
  • 已导出 YAML(4.1 ②),且导出文件可被 kubectl apply --dry-run=server -f 通过
  • etcd 快照在窗口内(上面验证过最新一份的时间)
  • 若涉及 PVC:已确认 rbd du 里的实际占用可以放弃,或已完成 VolSync 备份

分批执行,每批之后验证

下面的对象名都是 2026-10-03 快照里的例子

实际清单每次都要用当次 scripts/kor-audit 的输出替换,不要照抄这里的名字。 分批的意义是「每批只碰一类对象、删完立刻验证」,分批方式本身可以照用。

# 第 1 批:Ceph 上的真实占用(唯一有空间收益的一批)
# 这些对象没有任何 label,只能按名字删,所以必须显式列出来
kubectl -n woodpecker delete service --dry-run=server \
  wp-hsvc-7257 wp-hsvc-7409 wp-hsvc-7410 wp-hsvc-7411 wp-hsvc-7413 wp-hsvc-7414 wp-hsvc-7415
# 确认无误后去掉 --dry-run=server 执行;再删 7 个孤儿工作区 PVC
# (回收策略是 Delete,PV 与 Ceph 镜像会一并删除)
kubectl -n woodpecker delete pvc --dry-run=server \
  wp-01kqr81vtqk2t1dm9b666wce2h-0-default \
  wp-01krmt5vnn0y6pfqjye8j8ts52-0-default \
  wp-01krmt5vnn0y6pfqjye9zq9y13-0-default \
  wp-01krmt5vnt96s7cv2b5p11gb42-0-default \
  wp-01krmt5vnvp0bhbfy33pm1xvp9-0-default \
  wp-01krmt5vp1x4fprxac7eew4kwc-0-default \
  wp-01krmt5vp1x4fprxac7fe9x620-0-default

# 第 2 批:Job 历史(建议先留日志)
kubectl -n renovate logs job/renovate-1763392138332 > ~/kor-backup/2026-10-03/renovate-...log
kubectl -n renovate delete job renovate-1763392138332

# 第 3 批:旧 ReplicaSet(数量大,务必先导出清单再批量删)
# 清单里的条目是「ns/name」,每条都要按自己的 namespace 删;
# 固定一个 -n <ns> 会让别的 ns 静默 NotFound,而同名 RS 恰好存在时还会误删。
kubectl -n <条目所在 ns> get rs <条目名> \
  -o jsonpath='{.spec.replicas} {.status.readyReplicas}{"\n"}'
# 输出必须是 `0 0`(或 `0 `)才继续;非 0 说明它不是旧 revision,跳过
kubectl -n <条目所在 ns> delete rs <条目名>

不要删 VolumeAttachment

CSI 的 VolumeAttachment 是运行时状态,不是可清理的残留。删除活跃挂载的 VA 会让 external-attacher 对正在被 Pod 使用的卷执行 detach:I/O 中断,严重时数据损坏。 kor 报出 VA 也不代表引用已消失——RBAC 缺 nodes/csidrivers 时它会逐条误报 Node ... does not exist(2026-10-03 实测 25/25 都是活跃挂载)。

scripts/kor-audit 因此把该 kind 一律列为 C 类。确实要处理(例如带 deletionTimestamp 卡死的记录)请走 rook-ceph v1.20.0 升级故障处理 里的人工流程,并先核对 PV / 节点 / CSIDriver 是否仍然存在。

删除后立即验证

# 1) 没有新的异常
kubectl get pods -A | grep -vE "Running|Completed"
# 2) ArgoCD 全绿(A 类不在 Git 里,不应产生任何漂移)
kubectl get applications -A -o custom-columns=NAME:.metadata.name,SYNC:.status.sync.status,HEALTH:.status.health.status | grep -v "Synced.*Healthy"
# 3) kor 重新扫描后总数下降(扫描间隔 30 分钟,也可以看 exporter 日志确认已重扫)
#    注意:make smoke-test 只对 5 个 Ingress 发 HTTP 探测,与 kor 无关,不能用它验证这一步
curl -s --get --data-urlencode 'query=count(kubernetes_orphaned_resources)' \
  https://prometheus.west-beta.ts.net/api/v1/query | jq -r '.data.result[0].value[1]'
# 或重跑分级报告,确认目标对象已从 A 类清单里消失
scripts/kor-audit

4.3 误删除恢复

按对象的来源分路径,先看它是谁管理的:

对象来源 恢复方式 时效
ArgoCD / Helm 管理(C 类,原则上不该删) 什么都不用做——selfHeal 会在下一个同步周期自动重建;也可在 ArgoCD UI 点 Sync,或 git revert 后合入 约 3 分钟内自动
operator/CR 生成(Secret、RBAC、DataVolume 的 PVC) 删除/重建其父对象(如 ExternalSecret、CephCluster)触发重建;ESO 与 rook 会重新 reconcile 分钟级
声明式但不在 Git(A 类) 用 4.1 ② 的 YAML 导出 kubectl apply -f;旧 ReplicaSet 无需恢复(无意义) 即时
PVC 数据(回收策略 Delete) 只有 VolSync 一条路:./scripts/backup --action restore ...。etcd 快照救不回数据——它只有对象定义,Ceph 镜像已经没了 取决于备份周期
PVC(已先把 PV 改成 Retain) 手工重建同名 PVC 绑定回那个 PV(spec.volumeName),或直接用该 PV 重建工作负载 即时
整集群级事故(如误删 CRD 导致 CR 级联删除) 从 S3 取最近一份 etcd 快照,在一台控制面执行 k3s server --cluster-reset --cluster-reset-restore-path=<快照>,其余控制面清掉本地 etcd 数据后重新加入 见 k3s 官方文档;窗口 12–24 小时

etcd 快照能救什么、不能救什么

  • 能:所有 k8s 对象(含 Secret、RBAC、CRD 与 CR 的定义)、被级联删掉的对象定义。
  • 不能:已经随 PVC 一起删掉的 Ceph RBD 镜像里的数据。快照恢复后 PV/PVC 会 指向已经不存在的镜像,卷起不来——这种场景只能靠 VolSync。
  • 恢复 etcd 是整集群回滚:快照之后的所有变更都会丢。它只用于兜住灾难, 不用于修单个对象。

5. 复现与排障

scripts/kor-audit                                   # 重新生成 A/B/C/U/? 分级
kubectl -n kor logs deploy/kor-exporter --tail=20   # exporter 在跑整集群 LIST
症状 处理
报告里 C 类突然变多 多半是 GitOps 标记的判断变了;确认 scripts/kor-audit 的 gitops 判据与你删的东西一致
报告里出现 U 类 kor 报了脚本 RESOURCES 表没覆盖的 kind。按对象名人工核对;要纳入自动分级,就往脚本的 RESOURCES 与 live_meta() 里补映射
报告里出现 ? 类并持续不消失 说明指标与集群实况确实不一致(对象被删/改名,指标未刷新)。等一轮扫描仍不消失就看 exporter 日志
删完 ArgoCD 出现 OutOfSync 说明删到了 C 类对象(在 Git 里)。不要手工重建,用 Sync 让它自己修复
PVC 删了但 Ceph 里镜像还在 回收策略被改成 Retain 了;镜像需人工 rbd rm(谨慎)
想确认某 PVC 在 Ceph 里的真实占用 kubectl -n rook-ceph exec deploy/rook-ceph-tools -- rbd du -p standard-rwo <csi-vol-...>

6. 已知限制

  • scripts/kor-audit 的分级是保守规则 + 启发式,B 类必须人工看,U 类必须人工核对; 规则见脚本内 classify() 与 RESOURCES。
  • VolumeAttachment 不在清理范围:它是 CSI 的运行时状态,删除活跃挂载的 VA 会触发 卷 detach。脚本一律判 C 类;确需处理时按 §4.2 的人工流程核对引用后再动手。
  • kor 的判定本身有上游已知假阳性(见 specs/008-kor-monitoring/spec.md 与上游 README), 任何批量删除前都应保留 4.1 的 YAML 导出。
  • 本页不提供自动删除(kor 的 --delete 不启用:它需要写权限,且会照单全删)。 删除动作始终由人确认后执行。