如何在 Kubernetes 部署中实现零停机滚动更新

配置 Kubernetes 滚动更新,包括就绪探针、maxSurge、maxUnavailable 和优雅关闭。

如何在 Kubernetes 部署中实现零停机滚动更新

Kubernetes 滚动更新可以在不造成明显中断的情况下替换 Pod,但前提是你的 Deployment 和应用程序在 Pod 何时就绪以及如何关闭上达成一致。默认策略有所帮助,但它无法避免糟糕的就绪探针、不兼容的版本或丢失的正在处理的请求。

然而,实现真正的零停机需要的不只是默认的 Kubernetes 配置。它要求 Deployment 清单、应用程序的健康端点和优雅终止过程之间进行仔细协调。本指南提供了一种全面、逐步的方法来配置 Kubernetes Deployment,确保应用程序更新对最终用户来说是无缝且不可见的。

本指南涵盖了就绪探针、maxSurgemaxUnavailable 和优雅终止,以便你的服务在滚动更新期间保持容量。

零停机的前提条件

在配置 Kubernetes 清单之前,底层应用程序必须遵循某些原则以支持零停机部署:

  1. 应用程序向后兼容性: 在旧版本和新版本应用程序同时运行的短暂期间,它们必须与共享资源(数据库、队列、缓存)兼容。
  2. 幂等性: 可能由两个版本处理的操作必须是可重复的,且没有负面影响。
  3. 优雅终止: 应用程序必须能够识别 Kubernetes 发送的 SIGTERM 信号,并在退出前优雅地停止接受新连接,同时完成正在处理的请求。

理解 Kubernetes 滚动更新策略

Kubernetes Deployment 默认使用 RollingUpdate 策略。此方法确保旧应用程序版本不会在新版本运行之前完全下线,它使用两个主要参数来管理过渡:

参数 描述 对零停机的影响
maxSurge 定义可以超过所需副本数创建的 Pod 的最大数量。可以是绝对数字或百分比(默认:25%)。 控制滚动更新的速度,并确保容量临时增加。
maxUnavailable 定义在更新期间可以不可用的 Pod 的最大数量。可以是绝对数字或百分比(默认:25%)。 对零停机至关重要。 将此值设置为 0% 意味着在新 Pod 完全 Ready 之前,不会终止任何正在提供服务的 Pod。

推荐的零停机策略

为了最高可用性,最佳配置通常是确保零停机容量损失:

  • maxUnavailable: 0(确保容量永不下降)。
  • maxSurge: 125%(允许容量短暂超过目标,确保新 Pod 就绪后再杀死旧 Pod)。

步骤 1:实现就绪探针

就绪探针 是确保零停机更新最重要的机制。Kubernetes 依赖此探针来确定新 Pod 是否准备好接收用户流量,以及旧 Pod 是否仍在积极提供流量。

存活探针与就绪探针

  • 存活探针: 告诉 Kubernetes 容器是否健康且功能正常。如果失败,容器将被重启。
  • 就绪探针: 告诉 Kubernetes 容器是否准备好处理请求。如果失败,Pod 将从关联的 Service 端点中移除,将流量从它那里转移开,直到它变得就绪。

对于滚动更新,就绪探针 用于控制过渡。Kubernetes 不会继续终止旧 Pod,直到新创建的 Pod 成功通过其就绪检查。

# deployment.yaml 摘录
spec:
  containers:
  - name: my-app
    image: myregistry/my-app:v2.0
    ports:
    - containerPort: 8080
    
    # --- 就绪探针配置 ---
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080
      initialDelaySeconds: 15  # 首次探测前的等待时间
      periodSeconds: 5         # 执行检查的频率
      timeoutSeconds: 3
      failureThreshold: 3      # 连续失败次数,标记 Pod 为未就绪

    # --- 存活探针配置(标准健康检查) ---
    livenessProbe:
      httpGet:
        path: /health/live
        port: 8080
      initialDelaySeconds: 60
      periodSeconds: 10

提示: 确保你的应用程序的 /health/ready 端点在初始化、数据库连接和其他外部依赖完全运行时返回成功代码(HTTP 200-299)。

步骤 2:配置 Deployment 策略

为了强制实现真正的零停机,我们显式配置滚动更新策略,以防止可用副本数量下降。

在此配置中,Kubernetes 将首先创建一个新 Pod(maxSurge: 1)。一旦新 Pod 通过其就绪探针,Kubernetes 才会终止一个旧 Pod。由于 maxUnavailable 为 0,服务容量永远不会低于目标副本数。

# deployment.yaml 摘录
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-deployment
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      # 确保容量永远不会低于所需副本数 (4)
      maxUnavailable: 0 
      # 允许在滚动更新期间创建一个额外的 Pod
      maxSurge: 1 
  template:
    # ... 容器规范 ...

步骤 3:确保优雅终止

即使有强大的就绪探针,如果应用程序在收到终止信号后立即关闭,它也可能丢失正在处理的请求。

Kubernetes 遵循特定的终止序列:

  1. Pod 被标记为 Terminating
  2. Pod 从 Service 端点中移除(流量停止路由到它)。
  3. 执行 pre-stop 钩子(如果定义了)。
  4. 容器收到 SIGTERM 信号。
  5. Kubernetes 等待 terminationGracePeriodSeconds 定义的时间(默认:30 秒)。
  6. 如果容器仍在运行,它将收到不可协商的 SIGKILL

为了确保优雅关闭,应用程序必须处理 SIGTERM,并且 terminationGracePeriodSeconds 必须足够长,以便应用程序完成现有请求。

# deployment.yaml 摘录,位于 Pod 模板规范内
spec:
  terminationGracePeriodSeconds: 45 # Pod 级别设置
  containers:
  - name: my-app
    image: myregistry/my-app:v2.0
    lifecycle:
      preStop:
        exec:
          # 给端点更新和外部负载均衡器时间进行排空。
          command: ["/bin/sh", "-c", "sleep 10"]

最佳实践: 你的应用程序在收到 SIGTERM 时应停止接受新工作,然后在退出前完成正在处理的请求。稍长的 terminationGracePeriodSeconds,例如 45 或 60 秒,有助于防止对较慢请求的强制杀死。

步骤 4:执行和监控更新

一旦你的 Deployment 清单包含了优化后的策略和强大的探针,执行更新就很简单了。

  1. 更新镜像标签: 修改你的部署清单以反映新的镜像版本(例如,从 v2.0v2.1)。

  2. 应用配置:

    kubectl apply -f deployment.yaml
    

    或者,你可以直接修补镜像:

    kubectl set image deployment/my-web-deployment my-app=myregistry/my-app:v2.1
    
  3. 监控滚动更新状态: 观察 Kubernetes 在各个阶段的进展,验证就绪 Pod 的数量从未低于所需数量。

    kubectl rollout status deployment/my-web-deployment
    
  4. 验证 Pod 可用性: 观察 Pod 状态,确认旧 Pod(v2.0)仅在新 Pod(v2.1)完全就绪后才被优雅终止。

    kubectl get pods -l app=my-web-deployment -w
    

高级考虑

使用 Pod 中断预算 (PDB)

虽然部署策略管理滚动更新,但 Pod 中断预算 (PDB) 限制了自愿中断,例如节点排空和一些集群维护操作。它不能防止每个计划外故障,但它为 Kubernetes 和自动化工具提供了一个要遵守的最低可用性目标。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: 75%  # 确保至少 75% 的副本始终可用
  selector:
    matchLabels:
      app: my-web-deployment

初始延迟的重要性

如果你的应用程序需要时间来预热,请调整 initialDelaySecondsperiodSecondsfailureThreshold,以便就绪状态反映真实的启动行为。失败的就绪探针会使 Pod 脱离 Service 端点;失败的存活探针则可能导致容器重启并创建崩溃循环。

安全地滚动更新

在 Kubernetes 中实现真正的零停机滚动更新是稳健的平台配置和严格的应用程序开发的结合。通过正确利用就绪探针来指示操作状态,调整 Deployment 策略(maxUnavailable: 0)以维持容量,并实现优雅终止处理程序,你可以确保应用程序更新可靠地执行,而不会中断对用户的服务。始终在预发布环境中彻底测试你的更新过程,以验证终止宽限期和探针逻辑。