凌晨2点,生产环境的告警突然响起,提示一台Kubernetes节点意外宕机。
你的第一反应会是什么?业务是否会中断?运行其上的Pod和数据会不会丢失?需要手动介入迁移吗?
许多人误以为Kubernetes能够完全自动化地处理一切,但当真遇到节点需要下线时,超过80%的人可能并不清楚其底层的完整处理流程。节点优雅下线应该使用drain命令,直接关机更像是在“赌运气”。今天,我们就通过一次实战演练,来对比“主动优雅下线”与“突发宕机”两种场景下,Kubernetes究竟是如何处理的,并掌握正确的操作姿势。
1. 了解 kubectl drain 命令
在进行任何操作前,充分了解工具是关键。kubectl drain 命令用于安全排空一个节点,为维护或下线做准备。
# 使用说明
[root@k8s-node02 ~]# kubectl drain --help
#...
--ignore-daemonsets=false: #忽略daemonset资源,默认关闭
Ignore DaemonSet-managed pods.
#...
Usage:
kubectl drain NODE [options]
Use "kubectl options" for a list of global command-line options (applies to all commands).
2. 资源驱逐流程详解
2.1 驱逐前的资源检查
首先,我们查看目标节点 k8s-node02.dinginx.org 上正在运行的Pod,做到心中有数。
[root@k8s-node02 ~]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
stress-test-755885757-64fzq 1/1 Running 11 (55m ago) 11h 10.244.2.90 k8s-node02.dinginx.org <none> <none>
stress-test-755885757-pzhsq 1/1 Running 11 (38m ago) 11h 10.244.2.92 k8s-node02.dinginx.org <none> <none>
2.2 禁止调度(cordon)
在开始驱逐前,先使用 cordon 命令将节点标记为不可调度。这可以防止在驱逐过程中有新的Pod被调度到该节点上。
kubectl cordon k8s-node02.dinginx.org
# 一句话来讲,执行cordon后,节点不再接受新Pod调度,但已有的Pod仍会继续运行。
👉 简单理解:cordon只是“锁上门”,并不“赶走屋里的人”。
2.3 执行驱逐(drain)
现在,执行核心的 drain 命令。我们使用了两个常用参数:--ignore-daemonsets=true 用于忽略DaemonSet管理的Pod(如网络插件、日志收集器),--delete-emptydir-data 表示删除使用 emptyDir 卷的Pod的数据。
[root@k8s-node02 ~]# kubectl drain k8s-node02.dinginx.org --ignore-daemonsets=true --delete-emptydir-data
node/k8s-node02.dinginx.org already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-flannel/kube-flannel-ds-h55jz, kube-system/filebeat-tvxxs, kube-system/kube-proxy-x7g7x, metallb-system/speaker-h95xf
evicting pod metallb-system/controller-66bdd896c6-lz59q
evicting pod kube-system/coredns-7699bffdb6-5crvp
evicting pod default/stress-test-755885757-64fzq
evicting pod default/stress-test-755885757-pzhsq
evicting pod kube-system/coredns-7699bffdb6-zwvkr
evicting pod kube-system/metrics-server-68d7bc6495-fqslz
pod/controller-66bdd896c6-lz59q evicted
pod/metrics-server-68d7bc6495-fqslz evicted
pod/coredns-7699bffdb6-5crvp evicted
pod/coredns-7699bffdb6-zwvkr evicted
pod/stress-test-755885757-pzhsq evicted
pod/stress-test-755885757-64fzq evicted
node/k8s-node02.dinginx.org drained
从输出可以看到,命令依次驱逐了除DaemonSet外的所有Pod,并最终显示节点已排空。这个过程是可控的,Kubernetes会遵循Pod的terminationGracePeriodSeconds设置,允许Pod进行优雅终止。
2.4 驱逐后资源检查
再次检查Pod分布,确认它们已被成功调度到其他可用节点(本例中是 k8s-node01)。
[root@k8s-node02 ~]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
stress-test-755885757-7gkz8 1/1 Running 0 3m14s 10.244.1.83 k8s-node01.dinginx.org <none> <none>
stress-test-755885757-fbp9g 1/1 Running 0 3m14s 10.244.1.80 k8s-node01.dinginx.org <none> <none>
#查看资源已被成功驱逐至node01节点上
2.5 查看节点状态
此时,节点的状态已变为 Ready,SchedulingDisabled,并且被自动添加了 node.kubernetes.io/unschedulable:NoSchedule 污点,确保了不会有新Pod被调度上来。
[root@k8s-master01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01.dinginx.org Ready control-plane 137d v1.35.0
k8s-node01.dinginx.org Ready <none> 137d v1.35.0
k8s-node02.dinginx.org Ready,SchedulingDisabled <none> 137d v1.35.0
#查看被驱逐的节点被打了污点
[root@k8s-master01 ~]# kubectl describe nodes k8s-node02.dinginx.org |grep Taints
Taints: node.kubernetes.io/unschedulable:NoSchedule
3. 下线节点
当节点上的负载被安全驱逐后,我们就可以开始物理或虚拟层面的节点下线操作了。
3.1 停止 kubelet 服务
在目标节点上,停止并禁用 kubelet 服务。
[root@k8s-node02 ~]# systemctl disable --now kubelet.service
Removed /etc/systemd/system/multi-user.target.wants/kubelet.service.
#查看集群状态,node02状态更新为NotReady
[root@k8s-node02 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01.dinginx.org Ready control-plane 137d v1.35.0
k8s-node01.dinginx.org Ready <none> 137d v1.35.0
k8s-node02.dinginx.org NotReady,SchedulingDisabled <none> 137d v1.35.0
3.2 从集群中移除节点
如果该节点计划报废或重装,需要从集群控制面移除其信息。请注意,此操作不可逆,且需在节点上执行kubeadm reset等清理操作。
[root@k8s-node02 ~]# kubeadm reset -f #清除节点上的K8s组件
[root@k8s-master01 ~]# kubectl delete node k8s-node02.dinginx.org #从集群API Server中删除节点信息
3.3 安全建议
对于永久下线且不再使用的节点,强烈建议执行重装系统操作,以彻底清除可能残留的敏感数据(如ServiceAccount token、容器镜像等),避免潜在的数据泄露风险。这属于运维安全的最佳实践。
4. 模拟直接宕机场景
现在,我们回到开头那个令人紧张的凌晨场景:如果节点是直接断电或宕机,Kubernetes又会如何反应?
4.1 模拟宕机
我们在节点上直接执行关机命令来模拟突发故障。
#模拟直接宕机
[root@k8s-node02 ~]# init 0
4.2 观察集群状态变化
在另一个节点(如k8s-node01)上,使用kubectl get -w命令持续观察节点和Pod状态。
[root@k8s-node01 ~]# kubectl get nodes -w
NAME STATUS ROLES AGE VERSION
k8s-master01.dinginx.org Ready control-plane 137d v1.35.0
k8s-node01.dinginx.org Ready <none> 137d v1.35.0
k8s-node02.dinginx.org Ready <none> 137d v1.35.0
k8s-master01.dinginx.org Ready control-plane 137d v1.35.0
k8s-node01.dinginx.org Ready <none> 137d v1.35.0
k8s-node02.dinginx.org NotReady <none> 137d v1.35.0
k8s-node02.dinginx.org NotReady <none> 137d v1.35.0
k8s-node02.dinginx.org NotReady <none> 137d v1.35.0
# 观察pod变化,pod状态无变化
[root@k8s-master01 ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
stress-test-755885757-7gkz8 1/1 Running 0 45m
stress-test-755885757-fbp9g 1/1 Running 0 45m
# 此时,原节点上的Pod可能仍显示Running状态,但实际上已无法提供服务。
# 最终等待 Node Controller 处理(默认约40秒心跳超时后),节点状态变为NotReady。
# 再过一段时间(取决于Pod驱逐等待时间,默认5分钟),这些Pod的状态会变为Unknown,随后被删除并由其控制器(如Deployment)在其他健康节点上重建。
4.3 底层机制解析
当节点突发宕机时,Kubernetes内部的处理流程是滞后的、被动的:
- kubelet 停止发送心跳:节点失联。
- Node 状态变为 NotReady:控制面的Node Controller检测到心跳超时(默认40秒)。
- Node Controller 标记不可调度:自动为节点添加
node.kubernetes.io/unreachable 污点。
- 等待 Pod 驱逐超时:对于
NotReady节点上的Pod,会有一个容忍期(默认为5分钟)。
- 删除 Pod:容忍期过后,控制面会删除这些
Unknown状态的Pod。
- 控制器重建 Pod:Pod的控制器(如Deployment)检测到Pod被删除,在其他
Ready节点上创建新的Pod副本。
关键点:在长达数分钟的等待期内,原节点上的Pod虽然显示Running,但实际已宕机,这会导致服务中断,除非应用本身具备多副本和跨节点的高可用部署。这正是直接关机风险所在。
5. cordon, drain 与直接宕机对比
为了更清晰地理解三者的区别,可以参考下表:
| 操作 |
是否停止调度 |
是否迁移 Pod |
是否可控 |
对服务影响 |
| cordon |
✅ |
❌ |
完全可控 |
无,现有Pod不受影响 |
| drain |
✅ |
✅ |
完全可控 |
优雅迁移,近乎零中断 |
| 直接关机 |
❌ (延迟标记) |
✅ (延迟重建) |
不可控 |
服务必然中断,中断时长取决于故障发现与重建时间 |
总结
通过以上对比演示,我们可以清晰地看到:
kubectl drain 是节点计划内维护和下线的标准且推荐的操作。它实现了Pod的优雅驱逐和平滑迁移,是保障云原生应用服务连续性的关键运维动作。
- 直接关机/宕机 是一种故障场景,Kubernetes的恢复机制是滞后的,会导致明确的服务中断时间窗口,应极力避免在生产环境中发生。
因此,请将“先drain,后操作”作为一条运维铁律。理解这些底层机制,能帮助我们在面对真正的集群运维问题时,做出更从容、正确的决策。希望这篇实战指南能对你有所帮助,如果你在实践过程中遇到其他问题,欢迎在技术社区交流探讨。