K8s运维实战:给Node节点“放假”的三种姿势(cordon/drain/delete保姆级对比)
K8s节点管理艺术:从优雅隔离到彻底移除的实战指南
节点管理的核心逻辑与场景
在Kubernetes集群的日常运维中,节点管理就像一支交响乐团的指挥工作——需要精确控制每个乐器的入场与退场时机。当某个节点需要维护、升级或退役时,如何确保服务不中断、数据不丢失,同时又能高效完成操作?这正是cordon、drain和delete这三个命令存在的意义。它们分别对应着三种不同级别的节点隔离策略:
- 礼貌暂停:像音乐会中场休息,暂时停止新任务分配但保留现有演出(cordon)
- 有序撤离:类似乐手轮换,先安全转移工作再停止服务(drain)
- 彻底离场:相当于乐团成员离职,完全移除所有关联(delete)
实际运维中最常见的三大场景包括:计划内维护(如内核升级或硬件更换)、节点故障紧急处理以及集群资源优化调整。选择哪种"休假方式",取决于你对节点未来去向的规划。
重要提示:任何节点操作前务必先执行
kubectl get pods -o wide | grep <节点名>,确认该节点运行的关键工作负载
1. 礼貌暂停:cordon的优雅隔离术
1.1 基础原理与操作
cordon相当于给节点挂上"请勿打扰"的牌子。执行后,Kubernetes调度器会将该节点标记为SchedulingDisabled状态,但不会对现有Pod产生任何影响。这就像让节点进入"调休"模式——虽然不出勤,但手头工作照常进行。
典型操作流程:
# 查看节点状态 kubectl get nodes # 标记节点为不可调度 kubectl cordon <node-name> # 验证节点状态 kubectl describe node <node-name> | grep -i schedulable1.2 适用场景与注意事项
这种模式特别适合以下情况:
- 预检维护:在实施可能影响稳定性的操作前,先阻止新Pod调度
- 问题排查:隔离疑似故障节点,避免问题扩散
- 灰度发布:配合节点选择器实现分批更新
但需要注意:
- 已有Pod会继续运行,可能影响维护操作
- DaemonSet管理的Pod仍可能被调度到该节点
- 需要后续配合drain才能安全下线
参数对比表:
| 特性 | cordon | drain | delete |
|---|---|---|---|
| 新Pod调度 | × | × | × |
| 现有Pod处理 | 保留 | 驱逐 | 驱逐 |
| 节点元数据 | 保留 | 保留 | 删除 |
| 恢复难度 | 简单 | 简单 | 复杂 |
2. 有序撤离:drain的安全驱逐方案
2.1 完整驱逐流程解析
drain是运维人员最常用的"标准休假流程"。它实际上是cordon的增强版——先禁止调度,然后按照优雅终止策略逐步驱逐Pod。整个过程就像安排员工交接工作:
- 通知Pod准备终止(发送SIGTERM)
- 等待优雅终止期(默认30秒)
- 强制终止未响应Pod(SIGKILL)
- 在其他节点重建Pod
完整命令示例:
# 安全驱逐节点所有Pod(忽略DaemonSet和管理本地存储) kubectl drain <node-name> \ --ignore-daemonsets \ --delete-emptydir-data \ --timeout=300s2.2 关键参数深度解读
--ignore-daemonsets:必须参数,因为DaemonSet Pod需要保持节点级服务--delete-emptydir-data:清理使用emptyDir的Pod数据--timeout:设置整个驱逐过程的超时时间--pod-selector:选择性驱逐特定标签的Pod
经验之谈:生产环境建议配合PodDisruptionBudget使用,通过
minAvailable设置确保关键服务始终有足够副本
2.3 常见报错解决方案
问题1:无法驱逐本地存储Pod
cannot delete Pods with local storage (use --delete-emptydir-data to override)解决:评估数据重要性后添加--delete-emptydir-data参数
问题2:DaemonSet Pod阻止操作
cannot delete DaemonSet-managed Pods解决:必须添加--ignore-daemonsets参数
问题3:Pod驱逐超时
error: timed out waiting for pod xxx to terminate解决:
- 检查Pod是否配置了合理的terminationGracePeriodSeconds
- 适当增加
--timeout值 - 确认kubelet工作正常
3. 彻底离场:delete的完全移除
3.1 核心理念与风险控制
delete是节点管理的"终极手段",相当于直接解除劳动合同。它不仅会驱逐所有Pod,还会从API Server中彻底移除节点对象。这种操作具有不可逆性,通常只在以下场景使用:
- 永久性节点退役
- 节点异常且无法恢复
- 云环境节点被直接销毁
基本操作流程:
# 从集群删除节点定义 kubectl delete node <node-name> # 节点侧需要清理kubelet相关数据 ssh <node-ip> "sudo kubeadm reset -f" # 重新加入集群需要重新生成join命令 kubeadm token create --print-join-command3.2 与drain的本质区别
虽然两者都会驱逐Pod,但delete会额外:
- 从etcd删除节点对象
- 需要重新加入才能恢复
- 可能导致监控数据断裂
- 影响节点关联的PV/PVC
操作影响对比:
| 影响维度 | drain | delete |
|---|---|---|
| 节点对象 | 保留 | 删除 |
| 证书信息 | 保留 | 失效 |
| 监控连续性 | 保持 | 中断 |
| 恢复方式 | uncordon | 重新join |
4. 实战决策树与进阶技巧
4.1 命令选择决策流程图
开始 │ ├─ 需要临时维护? → cordon → 维护完成 → uncordon │ ├─ 需要长期下线? → drain → 节点保留 → 未来uncordon │ └─ 永久移除? → drain → delete4.2 大规模集群管理实践
对于超过50个节点的大型集群,建议:
- 使用节点亲和性分散关键Pod
- 设置合理的PDB(PodDisruptionBudget)
- 采用分批次滚动维护策略
- 配合Cluster Autoscaler自动补偿容量
示例滚动维护脚本:
#!/bin/bash for node in $(kubectl get nodes -l worker=yes -o name); do kubectl drain ${node#node/} --ignore-daemonsets --grace-period=900 perform_maintenance kubectl uncordon ${node#node/} sleep 300 # 等待集群稳定 done4.3 监控与验证要点
操作后必须检查:
- 所有Pod是否重建成功:
kubectl get pods -o wide | grep -v Running - 节点资源压力:
kubectl top nodes - 服务可用性指标:
- 请求成功率
- 响应延迟
- 错误日志量
5. 特殊场景处理手册
5.1 有状态服务处理方案
当节点运行着StatefulSet或带本地存储的Pod时:
- 提前确认存储复制状态
- 使用
--pod-selector避开关键Pod - 手动控制迁移顺序
- 验证数据一致性
Cassandra等特殊中间件可能需要自定义驱逐钩子:
lifecycle: preStop: exec: command: ["/bin/sh", "-c", "nodetool drain"]5.2 云环境特殊考量
主流云平台的特殊处理:
| 云平台 | 节点终止行为 | 建议操作 |
|---|---|---|
| AWS | 实例终止自动触发drain | 直接终止实例 |
| GCP | 节点池自动修复 | 使用托管实例组API |
| Azure | 虚拟机SSD本地存储 | 提前迁移有状态Pod |
5.3 内核升级实战案例
典型内核升级流程:
# 1. 标记节点不可调度 kubectl cordon node-01 # 2. 安全驱逐Pod(给30分钟优雅退出时间) kubectl drain node-01 --ignore-daemonsets --timeout=1800s # 3. SSH登录节点执行升级 ssh node-01 "sudo apt update && sudo apt install linux-image-new" # 4. 重启节点 ssh node-01 "sudo reboot" # 5. 等待节点恢复 watch kubectl get nodes # 6. 重新允许调度 kubectl uncordon node-016. 自动化运维集成
6.1 与CI/CD管道结合
在GitOps工作流中添加节点健康检查:
steps: - name: node-drain-check image: kubectl:latest commands: - kubectl get nodes -o json | jq '.items[] | select(.spec.unschedulable == true)' - test $(kubectl get pods --field-selector=status.phase!=Running -A | wc -l) -eq 06.2 自定义控制器实现
通过Kubernetes Operator实现智能排水:
func (r *NodeMaintenanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { node := &corev1.Node{} if err := r.Get(ctx, req.NamespacedName, node); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } if node.Labels["maintenance"] == "true" { if !node.Spec.Unschedulable { if err := r.cordonNode(ctx, node); err != nil { return ctrl.Result{}, err } } pods, err := r.getPodsOnNode(ctx, node.Name) if err != nil { return ctrl.Result{}, err } if len(pods) > 0 { if err := r.evictPods(ctx, pods); err != nil { return ctrl.Result{}, err } return ctrl.Result{RequeueAfter: 30 * time.Second}, nil } } return ctrl.Result{}, nil }6.3 Prometheus告警规则示例
节点排水状态监控:
groups: - name: node-maintenance rules: - alert: LongRunningDrain expr: time() - kube_node_spec_unschedulable_change_timestamp_seconds > 3600 for: 15m labels: severity: warning annotations: summary: "Node {{ $labels.node }} has been draining for over 1 hour" description: "Check pod eviction issues on {{ $labels.node }}"