一、当节点需要“休假”时,我们面临什么?

想象一下,你管理的Kubernetes集群就像一座繁忙的数据中心大厦,每个节点(Node)就是大厦里的一个机房。有时候,机房需要维护——比如升级电力系统、更换空调,或者干脆要搬走几台旧服务器。在K8s里,这就叫“节点维护”或“节点排水”。

直接让机房断电会怎样?里面所有正在运行的应用程序(在K8s里叫Pod)会像突然被拔掉电源的电脑一样,“咔嚓”一下停止工作。对于用户来说,就是服务突然中断、请求失败,这绝对是线上事故。

因此,我们的核心目标变得非常清晰:在节点安全下线的过程中,确保其上运行的服务平稳过渡到其他健康节点,实现用户感知上的“零中断”。这整个过程,就是“优雅处理”。实现它,主要依靠两个黄金搭档:PodDisruptionBudget(PDB)和Pod的优雅终止机制。

二、第一道保险:用PDB设定驱逐“安全线”

PDB,中文叫Pod中断预算,它不是什么复杂的编程,而是一个简单的“规则声明书”。它不关心Pod具体在哪里运行,只关心某一类应用(通常通过标签选择器来定位)同时能有多少个实例不可用。

它的核心作用是防止因维护操作导致服务容量瞬间跌穿底线。比如,你有一个Deployment管理着10个副本的Web服务器,你认为同时最多只能有2个副本不可用,否则服务就会卡顿或出错。那么,你就可以设置一个PDB。

技术栈: Kubernetes YAML 清单

# 示例:为一个名为 `my-web-app` 的应用创建PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-web-app-pdb  # PDB资源本身的名称
spec:
  # 选择器:指明这个PDB要保护哪些Pod。这里选择带有 `app=my-web-app` 标签的Pod
  selector:
    matchLabels:
      app: my-web-app
  # 最大不可用(maxUnavailable):设定在维护期间,允许同时不可用的Pod的最大数量或比例。
  # 这里设置为1,意味着在驱逐Pod时,必须保证至少有(当前副本数 - 1)个Pod是健康的。
  maxUnavailable: 1
  # 你也可以使用 `minAvailable` 来指定必须保持可用的最小Pod数量或比例。
  # 例如:`minAvailable: 90%` 表示必须保证至少90%的Pod可用。
  # maxUnavailable 和 minAvailable 二选一即可。

这个PDB是如何工作的? 当你对节点执行kubectl drain <node-name>命令时,K8s并不会蛮干。它会先“咨询”所有相关的PDB。以我们的例子来说,如果my-web-app有10个Pod在运行,PDB要求maxUnavailable: 1。那么,驱逐程序会:

  1. 先尝试从目标节点上驱逐1个属于my-web-app的Pod。
  2. 等待这个Pod在新节点上成功启动并变为“就绪”状态。
  3. 确认整个应用满足“最多只有1个不可用”的条件后,才会去驱逐目标节点上的第二个my-web-app Pod。
  4. 如此循环,直到节点上所有Pod都被安全驱逐。如果某一步会违反PDB规则(比如一下子驱逐两个会导致同时有2个不可用),驱逐操作就会暂停并等待,直到条件被满足。

这就好比大厦经理在清空一个机房前,会确保其他机房有足够的空位和资源,并且每次只搬运一台关键服务器,等它在新机房启动ok了,再搬下一台,绝不让整体服务能力低于安全水位。

三、第二道保险:让Pod学会“善终”

PDB保证了全局的稳定性,而“优雅终止”则保证了单个Pod的体面退场。一个Pod被通知终止时,K8s会发送一个SIGTERM信号(可以理解为“请关闭”的通知),而不是直接发送SIGKILL(相当于“立即枪毙”)。

优雅终止的流程如下:

  1. API Server将Pod状态标记为“Terminating”。
  2. Kubelet开始执行“停止前钩子”(preStop Hook,如果配置了的话)。 这是一个在容器内执行命令或HTTP请求的机会,用于完成收尾工作。
  3. 向Pod内的主进程发送SIGTERM信号。
  4. 等待“终止宽限期”(terminationGracePeriodSeconds,默认30秒)。 在这个期限内,容器应完成清理并自行退出。
  5. 如果宽限期结束后容器仍未退出,则发送SIGKILL信号强制杀死。

要让这个过程真正“优雅”,需要应用和配置的配合。

技术栈: Kubernetes YAML 清单(Pod/Deployment部分)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-web-app
  template:
    metadata:
      labels:
        app: my-web-app
    spec:
      containers:
      - name: web-server
        image: nginx:latest
        ports:
        - containerPort: 80
        # --- 优雅终止关键配置开始 ---
        lifecycle:
          preStop:
            # 定义容器停止前的钩子。这里执行一条shell命令。
            # 这条命令让Nginx优雅停止,停止接收新连接,处理完已存在连接后再退出。
            exec:
              command: ["/usr/sbin/nginx", "-s", "quit"]
        # 设置终止宽限期为60秒,比默认的30秒更长,给处理中的请求更多时间。
        terminationGracePeriodSeconds: 60
        # --- 优雅终止关键配置结束 ---
        # 就绪探针(Readiness Probe):用于告诉K8s容器是否已准备好接收流量。
        # 在停止时,Kubelet会将容器标记为未就绪,从而将其从服务的负载均衡中移除。
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

在这个例子中,当Pod被驱逐时:

  1. Kubelet会先执行preStop钩子,即nginx -s quit命令。Nginx会停止监听端口,但会继续处理已经接受的请求。
  2. 随后,SIGTERM信号也会被发送给Nginx进程(不过nginx -s quit本身也会触发优雅退出)。
  3. 由于readinessProbe会很快失败(因为Nginx不再响应健康检查),该Pod会立即从Service的端点列表中移除,新的流量不会再被导入到这个即将关闭的Pod。
  4. Pod有最多60秒的时间来完成所有已接收请求的处理,然后安静退出。如果60秒后还没退出,才会被强制终止。

四、实战演练:一次完整的节点排水

现在,我们把PDB和优雅终止结合起来,看一次标准的节点维护操作。假设节点node-01需要重启内核。

步骤1:检查与规划 首先,查看节点上运行了哪些Pod,并确认关键应用都已配置PDB。

kubectl get pods --field-selector spec.nodeName=node-01 -o wide

步骤2:执行优雅排水 使用kubectl drain命令,它会自动利用我们前面讲的机制。

# --ignore-daemonsets 通常需要,因为DaemonSet的Pod(如日志收集器)一般不允许被驱逐,会忽略它们。
# --delete-emptydir-data 如果Pod使用了emptyDir卷,这个选项会清除数据。
# --force 在遇到未被PDB覆盖的Pod(如裸Pod)时,可以强制驱逐(慎用)。
kubectl drain node-01 --ignore-daemonsets --delete-emptydir-data --timeout=300s

执行这个命令后,你会看到K8s开始工作:

  • 它将node-01标记为SchedulingDisabled(禁止调度新Pod)。
  • 它开始逐个驱逐节点上的Pod,但会严格遵守每个Pod所属的PDB规则。
  • 对于每个Pod,都会触发其优雅终止流程(preStop钩子、SIGTERM、等待宽限期)。
  • 被驱逐的Pod会在其他可用的node上被重新创建和调度。

步骤3:维护节点drain命令成功完成(所有Pod已被安全驱逐)后,你就可以安全地对node-01进行维护了,比如重启、升级、关机。

步骤4:恢复节点 维护完成后,将节点重新纳入集群。

# 取消节点的不可调度标记
kubectl uncordon node-01

之后,新的Pod就可以被调度到这个节点上运行了。

五、深入理解:场景、优劣与避坑指南

应用场景:

  1. 计划性维护:节点操作系统升级、硬件更换、机房迁移。
  2. 集群缩容:安全地移除集群中的节点以节省成本。
  3. 故障隔离:怀疑某个节点硬件有问题,需要将其隔离检修而不影响服务。
  4. 应用升级:有时与应用配置(如需要节点标签变更)耦合的Pod也需要通过驱逐来重建。

技术优缺点:

  • 优点
    • 服务高可用:最大程度保证服务连续性和数据完整性。
    • 自动化与标准化:将复杂的运维流程抽象为声明式的K8s资源配置和标准命令。
    • 资源高效利用:通过控制驱逐节奏,避免因大量Pod同时重启导致其他节点资源瞬间过载。
  • 缺点/局限
    • 无法应对所有中断:PDB只对“自愿中断”(如drain、集群自动伸缩)有效,对节点硬件故障、掉电等“非自愿中断”无能为力。
    • 可能延长维护窗口:如果PDB设置非常严格(如maxUnavailable: 0),或者Pod启动速度很慢,排水过程会耗时很久。
    • 增加配置复杂度:需要为每个关键应用正确配置PDB和优雅终止逻辑。

注意事项与最佳实践:

  1. PDB不是万能的:务必理解PDB仅针对自愿中断。确保你的应用本身具备高可用能力(多副本、跨节点分布)。
  2. 合理设置PDB值maxUnavailableminAvailable的设置需要基于业务容忍度。对于无状态服务,可以设置得宽松一些(如maxUnavailable: 50%)以加快排水速度;对于有状态或关键服务,则要严格(如maxUnavailable: 1)。
  3. 优雅终止需要应用配合:你的应用程序必须能够正确处理SIGTERM信号,实现请求排干和连接关闭。preStop钩子是一个强大的补充工具。
  4. 调整宽限期:默认30秒可能不够。对于处理长连接、大数据量作业的应用,需要适当增加terminationGracePeriodSeconds
  5. 与就绪探针配合:确保配置了有效的readinessProbe,这样Pod在终止初期就能被移出流量池,是优雅终止的关键一环。
  6. 监控排水过程:在排水时,密切监控应用的监控仪表盘(如QPS、错误率、Pod状态),确保一切按预期进行。

六、总结

Kubernetes节点维护不再是令人夜不能寐的运维挑战。通过PodDisruptionBudget(PDB)Pod优雅终止机制的协同工作,我们可以像指挥一场精心编排的芭蕾舞一样,让Pod们从容地从需要维护的节点上“谢幕”,并在新的节点上“登场”,整个过程对用户而言几乎无感。

记住这个组合拳的精髓:PDB负责宏观的流量保卫战,控制着同时下线的Pod数量,守护服务的整体水位;而优雅终止机制则负责微观的个体善后工作,确保每个Pod都能处理完手头的事情,清清白白地离开。 将这两者纳入你的K8s应用部署清单和运维手册,是构建真正 resilient(有弹性)云原生服务的关键一步。从此,面对节点维护,你可以更加从容自信。