<p>client-go 中需三步原子操作等效 kubectl cordon/drain:1. Patch 设置 node.Spec.Unschedulable=true;2. Patch 向 /spec/taints/- 追加 maintenance 污点;3. 遍历并删除非 DaemonSet 且非本地存储依赖的 Pod。</p>

用 client-go 调用 cordon + taint + drain 三步等效操作
Go 里没有直接对应 kubectl cordon 或 kubectl drain 的封装函数,必须组合 API 调用模拟其行为。核心是三步原子性操作:标记不可调度 → 添加维护污点 → 驱逐 Pod。跳过任意一步都可能导致新 Pod 被调度进来,或旧 Pod 残留。
常见错误是只调 node.Spec.Unschedulable = true 就以为完成,但没加 taint,DaemonSet 或 toleration 配置宽松的 Pod 仍可能被调度过去。
- 先用
clientset.CoreV1().Nodes().Patch()设置/spec/unschedulable字段为true(JSON Patch) - 再用
clientset.CoreV1().Nodes().Patch()向/spec/taints追加维护用污点,例如{"key":"maintenance","value":"true","effect":"NoSchedule"} - 最后遍历该节点上所有非 DaemonSet、非镜像拉取失败的 Pod,逐个调
clientset.CoreV1().Pods(ns).Delete();注意要检查pod.Spec.NodeName == nodeName且!isDaemonSetPod(pod)
为什么不能直接 Update node.Spec.Taints
直接修改 node.Spec.Taints 后调 Update() 会覆盖整个污点列表,把节点上已有的其他关键污点(如 node-role.kubernetes.io/control-plane)一并清空,导致集群控制面异常。这是生产环境最常踩的坑之一。
正确做法必须用 Patch,且类型选 types.JSONPatchType 或 types.StrategicMergePatchType(后者需 client-go ≥ v0.25)。
立即学习“go语言免费学习笔记(深入)”;
- JSON Patch 示例:
{"op":"add","path":"/spec/taints/-","value":{"key":"maintenance","value":"true","effect":"NoSchedule"}} - 别漏掉末尾的
-,表示追加到数组末尾;用/spec/taints/0会覆盖第一个 - 如果要同时增删多个 Taint,StrategicMergePatch 更安全,但 payload 结构更复杂,需构造完整 map
驱逐 Pod 时绕不开的 DaemonSet 和本地数据处理
kubectl drain --ignore-daemonsets 的逻辑在 Go 里得自己实现:遍历 nodeList.Items 获取目标节点后,再 List 所有命名空间下的 Pod,过滤出 pod.Spec.NodeName == targetNode 且不属于任何 DaemonSet 管理的 Pod。
判断是否 DaemonSet Pod 的可靠方式是检查 pod.ObjectMeta.OwnerReferences 中是否存在 kind: "DaemonSet" 的引用;仅靠 label 匹配不保险。
- 本地存储(
emptyDir、hostPath)无法自动迁移,代码里得显式检查pod.Spec.Volumes是否含这类 volume,若存在且你业务允许丢数据,才执行 Delete - 加
--delete-local-data等效逻辑就是:不检查 volume 类型,直接删;但生产环境强烈建议先 warn 日志,人工确认 - 驱逐前最好加 5 秒 sleep,避免因 kubelet 上报延迟导致 List 到“已终止”但实际还在 Running 的 Pod
Label 键名合规性与维护状态可发现性
给节点打维护用 Label(比如 maintained-by: "my-operator")比纯污点更利于后续巡检,但 key 必须符合 DNS-1123 标准:只能含小写字母、数字、-、.,且不能以 - 或 . 开头或结尾。API Server 会静默拒收非法 key,返回 422 错误,但 client-go 默认不打印详细 reason,容易卡住。
建议在 Set Label 前用正则校验:^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$。
- 维护中节点推荐加两个标识:Label 表明“谁在维护”,Taint 表明“当前不可用”,二者语义分离,便于不同系统消费
- 别把维护时间戳写进 Label value(如
maintained-at: "2026-10-02T10:30Z"),Label 不适合存时序数据,应走 Annotation - 更新 Label 用
Update()安全,因为 client-go 对ObjectMeta.Labels是合并更新,不会覆盖已有 label


















