使用Golang实现集群资源自动化清理工具_过期镜像与孤儿POD

6次阅读

判断镜像是否“过期”应检查是否被任何pod引用,包括各命名空间中非running态pod、daemonset、job、cronjob及replicaset历史版本;优先用imageid匹配,次选digest,避免仅比对tag。

使用Golang实现集群资源自动化清理工具_过期镜像与孤儿POD

怎么判断一个镜像是否“过期”

不能只看 Created 时间戳——docker 镜像本身没生命周期,真正要盯的是它是否还在被任何 Pod 引用。kubernetes 里镜像可能被多个命名空间的 Pod 使用,也可能被 DaemonSetJobCronJob 持有,甚至存在未运行但仍在 ReplicaSet 历史版本里的残留引用。

实操建议:

立即学习go语言免费学习笔记(深入)”;

  • 遍历所有命名空间下的 Pod 列表(包括 phase != Running 的),提取 spec.containers[].imagestatus.containerStatuses[].imageID
  • 对每个节点执行 crictl images(或 docker images,取决于容器运行时),拿到本地镜像列表和 RepoDigests
  • imageID 匹配最可靠;若无 imageID,回退到比对带 @sha256: 的完整 digest;避免仅比对 tag,因为 tag 可能被覆盖重推
  • 注意:imagePullPolicy: Never 的 Pod 不会触发拉取,但其 image 字段仍应计入引用

如何安全识别并删除“孤儿 Pod”

“孤儿 Pod”不是 Kubernetes 官方概念,而是指那些不再属于任何控制器(DeploymentStatefulSet 等)管理、且 phaseSucceededFailed 的 Pod。它们常因 Job 完成、手动创建后忘记清理、或控制器被删但 Pod 残留而产生。

实操建议:

立即学习go语言免费学习笔记(深入)”;

  • clientset.CoreV1().Pods(ns).List() 获取 Pod 列表,检查每个 PodOwnerReferences 字段是否为空
  • 过滤掉 phase == "Running""Pending" 的 Pod——即使无 owner,正在运行也不能删
  • 特别注意 Job 类型:Job 控制器默认会保留成功完成的 Pod(ttlSecondsAfterFinished 未设时),这类 Pod 是“合法孤儿”,但可删;需区分是用户主动保留还是配置缺失
  • 删除前加 dry-run 模式,输出将删的 Pod 名称 + 命名空间 + age,人工确认阈值(比如只删 >72h 的)

golang 调 Kubernetes API 时怎么避免 429 Too Many Requests

Kubernetes API Server 对未认证/低权限客户端有默认限速(如 20 QPS),批量扫描节点镜像或全量 List Pod 时极易触发。错误信息是 429 Too Many Requests,响应头含 Retry-After: 1,但直接 sleep 会拖慢整个流程。

实操建议:

立即学习go语言免费学习笔记(深入)”;

  • rest.Config 显式设置 QPS: 5.0Burst: 10,比默认更保守;尤其在多节点轮询时,每个节点调 crictl 是本地操作,但 List Pod 是集中打 API Server
  • List 类请求加分页:用 Limit + continue 参数,避免单次拉几万条 Pod 导致超时或内存暴涨
  • 把“查 Pod 引用”和“查节点镜像”拆成两个阶段,并发控制分开:Pod 列表用 clientset 限速;节点镜像用 sshexeccrictl,走各自节点本地,不走 API Server
  • 不要在一个循环里连续 Get 数百个 Pod —— 改用 List + selector 过滤,例如 fieldSelector=spec.nodeName=xxx

清理镜像前为什么必须先 drain 节点上的 Pod

直接在节点上执行 crictl rmi 删除镜像,如果该镜像正被某个运行中容器使用,命令会失败(错误信息类似 cannot remove image: image is being used by running container)。但更危险的是:你删了镜像,而某个刚被调度的 Pod 正准备启动,此时 kubelet 拉取失败,Pod 卡在 ImagePullBackOff,形成雪球效应。

实操建议:

立即学习go语言免费学习笔记(深入)”;

  • 清理前必须确保目标节点处于 SchedulingDisabled 状态(即已 kubectl cordon),且所有非 DaemonSet Pod 已被驱逐(kubectl drain --ignore-daemonsets
  • DaemonSet Pod 无法驱逐,但它的镜像通常不该删——除非你明确知道该 DS 已下线且无残留实例;可用 clientset.AppsV1().DaemonSets(ns).List() 核对
  • 清理脚本里加入前置检查:调 nodes().Get(name)node.Spec.Unschedulablenode.Status.Conditions,确认不是 Ready 状态才继续
  • 删完镜像后,别忘了 uncordon——自动化工具最容易漏这步,导致节点长期不可调度

真正的难点不在代码怎么写,而在判断“这个镜像到底还能不能删”:它可能被一个 annotation 里藏着的旧 CronJob 引用,也可能被某次 kubectl run 临时 Pod 持有却没设 ownerRef。每次清理前,得有人盯着 diff 结果看三秒。

text=ZqhQzanResources