1. 生产环境K8s节点下线的重要性与挑战
在Kubernetes生产集群运维中,节点下线是最常见但风险最高的操作之一。我经历过多次因节点下线不当导致的服务中断事故——某次直接下线Worker节点导致30%的Pod被强制终止,另一次因未清理本地存储造成数据丢失。这些教训让我意识到:节点下线不是简单的kubectl drain命令执行,而是需要系统化的流程设计。
生产环境节点下线主要面临三大挑战:
- 业务连续性保障:必须确保Pod优雅终止并重新调度,避免服务中断
- 数据完整性风险:处理有状态服务时,需确保存储卷正确迁移
- 资源回收验证:防止残留配置导致资源泄漏或后续节点加入冲突
2. 标准下线流程全景图
2.1 前置检查清单
在下线操作前,必须完成以下检查(以节点node-01为例):
# 检查节点状态 kubectl get node node-01 -o wide # 查看节点运行Pod列表(注意--ignore-daemonsets参数) kubectl get pods -A -o wide --field-selector spec.nodeName=node-01 | grep -v kube-system # 检查本地存储使用情况 ssh node-01 "df -h | grep -E 'local-volume|data'"关键检查项:
- 确认节点无运行关键业务Pod(如数据库主实例)
- 检查本地存储使用量是否超过其他节点剩余容量
- 验证集群剩余资源是否满足Pod重新调度需求
2.2 核心操作流程
2.2.1 驱逐Pod(Drain操作)
标准drain命令应包含以下参数:
kubectl drain node-01 \ --ignore-daemonsets \ --delete-emptydir-data \ --force \ --timeout=300s \ --pod-selector='app notin (redis-master,postgresql-primary)'参数解析:
--ignore-daemonsets:跳过DaemonSet管理的Pod(如日志收集器)--delete-emptydir-data:清理emptyDir临时数据--pod-selector:保护关键Pod不被驱逐(需提前打标签)
重要提示:对于StatefulSet Pod,必须确认已配置适当的PodDisruptionBudget(PDB),否则可能违反SLA
2.2.2 存储卷处理
针对不同存储类型需特殊处理:
| 存储类型 | 处理方案 | 检查命令 |
|---|---|---|
| PV/PVC | 自动随Pod迁移 | kubectl get pvc -A |
| Local PV | 需人工确认数据备份 | kubectl get pv -o json |
| HostPath | 必须手动迁移数据 | find /mnt/data -type f |
对于Local PV,建议先执行数据备份:
# 创建临时备份目录 ssh node-01 "mkdir -p /backup/$(date +%Y%m%d)" # 同步数据到NFS rsync -avz /mnt/data/ nfs-server:/backups/node-01/2.3 节点下线后验证
执行kubectl delete node node-01后,必须验证:
- 资源释放情况:
kubectl get leases -A | grep node-01 kubectl get volumeattachments | grep node-01- 服务恢复验证:
# 检查原Pod是否在新节点正常运行 for pod in $(kubectl get pods -A -o jsonpath='{range .items[?(@.spec.nodeName=="node-01")]}{.metadata.name}{"\n"}{end}'); do kubectl -n ${pod%%_*} get pod ${pod##*_} -o wide done3. 特殊场景处理方案
3.1 控制平面节点下线
控制节点下线需要额外步骤:
- 先迁移kube-apiserver等关键组件:
# 查看当前leader kubectl -n kube-system get endpoints kube-scheduler -o jsonpath='{.metadata.annotations.control-plane\.alpha\.kubernetes\.io/leader}' # 手动移除待下线节点 kubectl -n kube-system patch leases kube-scheduler --type='json' -p='[{"op":"remove", "path":"/spec/holderIdentity"}]'- 从kubeadm配置移除节点:
kubeadm init phase upload-config kubeadm --config /etc/kubernetes/kubeadm-config.yaml3.2 批量下线操作
当需要下线多个节点时,建议:
- 使用并行处理脚本:
#!/bin/bash for node in node-{01..03}; do kubectl drain $node --ignore-daemonsets --delete-emptydir-data & done wait # 验证所有Pod已迁移 kubectl get pods -A -o wide | grep -E "Pending|Evicted"- 遵循"滚动下线"原则:
- 每次最多下线集群节点的20%
- 间隔至少5分钟观察集群状态
- 优先下线非关键业务节点
4. 自动化方案实现
4.1 基于Cluster API的下线流程
现代集群推荐使用声明式API管理节点生命周期:
apiVersion: cluster.x-k8s.io/v1beta1 kind: Machine metadata: name: worker-node-01 spec: nodeDeletionTimeout: 30m nodeDrainTimeout: 15m remediationStrategy: maxRetry: 3 retryPeriod: 5m4.2 自定义控制器实现
通过编写控制器实现智能下线:
func (r *NodeReconciler) handleDrain(node *corev1.Node) error { // 检查PodDisruptionBudget if err := r.checkPDB(node); err != nil { return fmt.Errorf("PDB check failed: %v", err) } // 分批次驱逐Pod pods := r.getPodsOnNode(node) batchSize := len(pods)/5 + 1 for i := 0; i < len(pods); i += batchSize { end := i + batchSize if end > len(pods) { end = len(pods) } if err := r.evictPods(pods[i:end]); err != nil { return err } time.Sleep(30 * time.Second) } return nil }5. 故障排查手册
5.1 常见错误与解决方案
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
| Pod卡在Terminating状态 | Finalizer未清理 | kubectl patch pod <name> -p '{"metadata":{"finalizers":null}}' |
| 存储卷无法卸载 | 仍有进程占用设备 | ssh node-01 "lsof +D /mnt/data"终止相关进程 |
| 新节点加入后IP冲突 | 旧节点kubelet未完全清理 | 在旧节点执行kubeadm reset --force |
| 服务中断超过SLA | PDB配置不合理 | 调整minAvailable值:kubectl patch pdb my-pdb -p '{"spec":{"minAvailable":"60%"}}' |
5.2 监控指标检查清单
下线操作期间必须监控以下Prometheus指标:
# 待下线节点资源使用率 sum(rate(container_cpu_usage_seconds_total{node="node-01"}[5m])) by (pod) sum(container_memory_working_set_bytes{node="node-01"}) by (pod) # 集群整体资源余量 sum(kube_node_status_allocatable{resource="cpu"}) - sum(kube_pod_container_resource_requests{resource="cpu"})6. 最佳实践与经验总结
优雅终止超时设置: 所有Pod模板必须配置preStop钩子并设置合理的terminationGracePeriodSeconds:
lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 30; nginx -s quit"] terminationGracePeriodSeconds: 60节点下线时间窗口选择:
- 避免业务高峰时段(通过分析历史监控数据)
- 考虑依赖服务的维护周期(如数据库备份期间不下线)
自动化验证脚本示例:
#!/bin/bash function verify_drain() { local node=$1 local retries=3 while ((retries-- > 0)); do if kubectl get pods -A --field-selector spec.nodeName=$node | grep -v NAME; then echo "仍有Pod运行在节点$node上" sleep 10 else return 0 fi done return 1 }文档记录要点:
- 记录下线原因(硬件更换/维护/缩容)
- 保存操作时间点和影响评估
- 更新集群拓扑图和容量规划表