云上Spot抢占式实例优雅下线与Pod自动迁移:小白也能看懂的Node Draining指南

Spot实例能省钱但会被突然回收,如何避免服务中断?本文用零基础视角拆解优雅下线、Node Draining和Pod自动迁移的原理与实操,帮你兼顾成本与稳定性。

云上Spot抢占式实例优雅下线与Pod自动迁移:小白也能看懂的Node Draining指南
封面图:ZuCDN · ZuCDN 原创

一、为什么你的云服务器可能“说没就没”?

容易忽略的细节

折腾Spot Pod时,我发现最麻烦的往往不是安装,而是配置。你为了省钱买了一台“竞价型”云服务器(也就是Spot抢占式实例),它价格低得感人,但有个致命的缺点:云平台随时可能因为资源紧张把它收回去。你运行的Web服务、数据处理任务正干到一半,机器突然被关机,Pod全部失联,用户开始报错——这是很多初入云原生的小白的噩梦。

Spot实例的价格只有按量付费的20%-30%,特别适合批处理、视频转码、大数据分析、无状态微服务等场景。但回收机制是硬性的:云平台会给你一个短暂的通知(通常是30秒到2分钟),然后强制关机。如果不能在这段时间内把任务安全迁移到其他节点,损失就大了。

那么,如何让Pod在被“抢走”之前体面地离开?答案就是优雅下线(Graceful Shutdown)和Node Draining

二、传统下线方式为什么不够好?

配置前的检查

假设你在Kubernetes集群中运行着10个Pod,节点A是个Spot实例。云平台通知你“节点A即将回收”。如果没有优雅下线,通常的做法是:直接执行 kubectl delete node nodeA,或者干脆等云平台强制关机。这时候会发生什么?

  • 正在处理的HTTP请求被中断,用户收到502错误。
  • 正在写入数据库的数据可能只写了一半,导致数据损坏。
  • Pod被强制删除,Kubernetes控制器会尝试在其他节点重建Pod,但重建过程中服务仍然不可用。

这种方法相当于“拔电源”,对业务影响极大。而优雅下线要做的是:先通知Pod准备离开,等它完成当前请求、关闭连接、保存状态,再真正关机。

三、Node Draining到底是什么?

实际操作要点

Node Draining(节点排水)是Kubernetes提供的一种机制。你可以把它想象成在修水管前先把管子里剩余的水放干净。kubectl drain 命令会让节点上的所有Pod被安全驱逐(Eviction),并自动调度到其他可用节点上。排水过程中,节点会被标记为不可调度(Unschedulable),新的Pod不会分配过来。

命令很简单:kubectl drain [node-name] --ignore-daemonsets --delete-emptydir-data。但对于Spot实例,我们不能手动敲命令,必须由云平台的节点组管理工具自动触发。

四、Spot实例的优雅下线流程

容易忽略的细节

完整的优雅下线包含三个关键动作:

1. 收到回收通知并标记节点
云平台(如AWS、阿里云、Azure)会通过实例元数据或事件通知你节点即将被回收。Kubernetes节点控制器或自定义控制器(如AWS Node Termination Handler)检测到后,会立即给节点加上污点(Taint):spot-instance-terminating:NoSchedule,阻止新Pod调度到该节点上。

2. 执行Node Drain
控制器随后执行 kubectl drain。这个命令会:

  • 优雅关闭 Pod:向Pod内的进程发送SIGTERM信号,等待指定的优雅终止时间(terminationGracePeriodSeconds,默认30秒)。
  • 遵守PodDisruptionBudget(PDB):如果Pod设置了PDB(例如“最少保持2个副本运行”),drain会等待直到满足PDB条件,不会一次性把所有Pod都赶走造成服务降级。
  • 跳过DaemonSet:DaemonSet通常需要运行在每个节点上,drain会忽略它们,或者你可以通过 --ignore-daemonsets 参数跳过。

3. Pod自动迁移
Pod被驱逐后,控制器(如Deployment、StatefulSet)发现实际副本数少于期望副本数,会在剩余的健康节点上创建新Pod。如果集群资源不足,可以触发Cluster Autoscaler来扩容新的节点(可以是按量付费的实例)。整个过程对用户来说几乎是透明的,只要Pod配置了合适的存活探针和就绪探针。

五、Pod如何优雅地退出?与Spot Pod

配置前的检查

仅仅执行drain还不够,Pod内部必须配合优雅退出。你需要为容器配置 preStop hookterminationGracePeriodSeconds

preStop hook会在SIGTERM发送前执行一个自定义脚本,比如通知负载均衡器移除本Pod、关闭数据库连接池、刷新缓存等。然后Kubernetes等待进程退出,如果超时则强制SIGKILL。

示例配置:

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10 && /usr/local/bin/deregister-from-elb"]
terminationGracePeriodSeconds: 30

这样,你给Pod留出了“擦屁股”的时间,避免客户端请求被粗暴中断。

六、云厂商的自动化工具

故障定位思路

不同云平台都提供了现成的方案来自动响应Spot回收:

  • AWS EKS:使用AWS Node Termination Handler,它监听EC2的实例状态变化,自动给节点打Taint并执行drain。配合Karpenter或Cluster Autoscaler,可以实现瞬间替换。
  • 阿里云ACK:节点池支持“抢占式实例”,当ECI(弹性容器实例)或ECS Spot被回收时,系统会自动触发节点排水。你可以在节点池配置中启用“自动排水”功能。
  • Google GKE:预占容器节点(Preemptible VM)在24小时后可能回收,有55秒的终止通知。GKE自带节点池管理,会自动排空Pod并在其他节点上重建。

如果你使用的是自建Kubernetes,可以自己写一个小脚本监听云API的回收通知,然后调用 kubectl drain。社区有开源项目如 spot-termination-handler 可以直接部署。

Spot Pod:七、小白的常见陷阱与避坑指南

先看关键判断

陷阱1: 不设置PDB
没有PodDisruptionBudget,drain会同时驱逐所有Pod,导致服务完全不可用。一定要给关键服务设置PDB,比如 minAvailable: 2

陷阱2: DaemonSet没正确处理
DaemonSet通常是基础组件(如日志采集、监控代理),drain时会直接忽略它们,导致这些Pod留在被删除的节点上。你应该在drain命令中加上 --ignore-daemonsets,或者单独处理DaemonSet的清理。

陷阱3: 只考虑Spot不考虑整体资源
当多个Spot节点同时回收时,可能突然需要大量新节点。如果你没有留足够的按量付费节点作为缓冲,或者没有启用Cluster Autoscaler,新的Pod可能调度失败。建议混合使用Spot和按量实例,把关键Pod固定在按量节点上。

陷阱4: 忽视日志和监控
优雅下线过程中可能会出错,比如Pod的preStop hook超时,或者新节点启动慢。你需要记录drain的日志,监控Pod重建时间。可以使用Prometheus监控Pod启动延迟,设置告警。

八、总结:成本与稳定的平衡术

故障定位思路

Spot抢占式实例是省钱利器,但你必须为它的“不稳定”做好准备。优雅下线不是可选项,而是必备的基础设施能力。通过Node Draining、PodDisruptionBudget、preStop hook和云平台的原生工具,你完全可以做到Spot实例被回收时业务零感知。

对于小白来说,理解底层原理比配置参数更重要:不是所有Pod都适合跑在Spot上,也不是所有故障都能靠自动drain解决。先在小规模灰度验证,逐步扩大Spot的使用比例,才是稳健的上云姿势。

现在,你可以打开你的Kubernetes集群,检查一下:有没有给Deployment设置PDB?preStop hook写了吗?节点组有没有开启自动排水?如果没有,今天就可以动手改进。后续只要定期检查关键指标,Spot Pod就不会变成维护负担。

延伸阅读