kubectl drain核心逻辑是分步执行:先驱逐pod(通过eviction api并校验pdb)、再标记节点为不可调度、最后轮询等待pod终止,三者缺一不可。

drain命令背后的核心逻辑是什么
Kubernetes 的 kubectl drain 本质是按顺序完成三件事:驱逐 Pod、标记节点为不可调度(node.Spec.Unschedulable = true)、等待 Pod 终止。Go 客户端实现时,不能只调用一次 API 就完事——它需要轮询确认所有可驱逐的 Pod 已终止或进入 Terminating 状态,同时跳过 DaemonSet 管理的 Pod(除非显式指定 --ignore-daemonsets=false)。
关键点在于:drain 不是原子操作,而是由多个独立 API 调用 + 客户端逻辑组合而成。K8s 官方 client-go 并未封装出一个叫 DrainNode 的函数,你得自己拼。
如何用 client-go 驱逐节点上的 Pod
驱逐动作必须对每个 Pod 单独发起 Eviction 子资源请求(POST 到 /api/v1/namespaces/{ns}/pods/{name}/eviction),不能 PATCH Pod 或直接删 Pod(否则绕过驱逐策略,比如 PDB 不生效)。
实操要点:
- 先 List 所有运行在目标节点上的 Pod(过滤
pod.Spec.NodeName == nodeName) - 排除被控制器忽略的 Pod:DaemonSet 管理的(检查
pod.OwnerReferences中是否有kind: DaemonSet)、镜像 PullBackOff/ErrImagePull 等失败状态的 Pod 默认不驱逐(kubectl drain默认行为) - 对每个待驱逐 Pod 构造
policyv1.Eviction对象,用clientset.PolicyV1().Evictions(ns).Create()发起请求 - 若返回
429 Too Many Requests或409 Conflict(如 Pod 正在被其他控制器删除),需重试或跳过
为什么直接 Patch Node 的 Unschedulable 字段还不够
设置 node.Spec.Unschedulable = true 只阻止新 Pod 调度,不影响已在运行的 Pod。真正的“排水”必须包含驱逐(Eviction)+ 不可调度标记 + 等待终止三个环节。
常见错误:
- 只改
Unschedulable就认为节点已 drain —— 实际上旧 Pod 还在跑,服务可能未迁移 - 用
clientset.CoreV1().Pods(ns).Delete()替代 Eviction —— 绕过 PodDisruptionBudget 校验,可能违反高可用约束 - 没等待 Pod 真正消失就返回成功 —— 下游流程(如下线物理机)可能出问题
等待 Pod 终止时要注意什么 驱逐后 Pod 进入 Terminating 状态,但实际退出时间取决于 preStop hook、terminationGracePeriodSeconds 和容器内进程响应速度。client-go 没有现成的 “wait for drain done” 函数。
建议做法:
- 轮询 List Pod,筛选出
pod.Spec.NodeName == nodeName且pod.DeletionTimestamp != nil的 Pod - 超时控制必须设(比如默认 600 秒),避免无限等待
- 对长时间卡在 Terminating 的 Pod,可考虑记录日志并继续 ——
kubectl drain默认也不强制等所有 Pod 彻底消失(除非加--force) - 注意区分 “无 Pod” 和 “只有 DaemonSet Pod” —— 后者通常视为 drain 完成(除非禁用了
--ignore-daemonsets)
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











