如何在 Kubernetes 部署中执行零停机滚动更新
简介
在现代微服务架构中,在应用程序更新期间保持持续可用性是一项不容谈判的要求。Kubernetes 部署通过提供自动化的滚动更新简化了此过程,这是一种旨在逐步用新 Pod 替换旧版本 Pod 的策略。
要实现真正的零停机,需要的不仅仅是默认的 Kubernetes 配置。它需要部署清单、应用程序的健康检查端点以及优雅终止过程之间的仔细协调。本指南提供了一个全面的分步方法,用于配置 Kubernetes 部署,以确保应用程序更新是无缝的,并且对最终用户不可见。
我们将介绍就绪性探针的关键作用,如何调整部署策略参数(maxSurge 和 maxUnavailable),以及应用程序终止的最佳实践,以消除部署转换期间的服务中断。
零停机的先决条件
在配置 Kubernetes 清单之前,底层应用程序必须遵守某些原则,才能支持零停机部署:
- 应用程序向后兼容性: 在新旧版本的应用程序同时运行的短暂时间内,它们必须与共享资源(数据库、队列、缓存)兼容。
- 幂等性: 可能被两个版本处理的操作必须可重复,且不会产生负面影响。
- 优雅终止: 应用程序必须被编程为识别 Kubernetes 发送的
SIGTERM信号,并在退出前优雅地停止接受新连接,同时完成正在进行的请求。
理解 Kubernetes 滚动更新策略
Kubernetes 部署默认为 RollingUpdate 策略。此方法确保在将旧应用程序版本完全关闭之前,新版本已经可以运行,它使用两个主要参数来管理转换过程:
| 参数 | 描述 | 零停机影响 |
|---|---|---|
maxSurge |
定义可以超出所需副本数的 Pod 的最大数量。可以是绝对数字或百分比(默认值:25%)。 | 控制滚动的速度,并确保容量暂时增加。 |
maxUnavailable |
定义在更新期间可以不可用的 Pod 的最大数量。可以是绝对数字或百分比(默认值:25%)。 | 对零停机至关重要。将其设置为 0% 意味着在新的 Pod 完全 Ready 之前,不会终止任何正在服务的 Pod。 |
零停机的推荐策略
为了获得最高的可用性,最佳配置通常是确保零停机容量损失:
maxUnavailable: 0(确保容量永不下降)。maxSurge: 1或25%(允许容量短暂超过目标,确保在杀死旧 Pod 之前新 Pod 已经准备就绪)。
步骤 1:实现就绪性探针
就绪性探针 (Readiness Probe) 是确保零停机更新的最重要机制。Kubernetes 依靠此探针来确定新 Pod 是否已准备好接收用户流量,以及旧 Pod 是否仍在积极提供流量。
存活性 vs. 就绪性
- 存活性探针 (Liveness Probe): 告诉 Kubernetes 容器是否健康和正常工作。如果失败,容器将被重启。
- 就绪性探针 (Readiness Probe): 告诉 Kubernetes 容器是否已准备好为请求服务。如果失败,该 Pod 将从关联的服务端点中移除,将流量从其分流,直到它准备就绪为止。
对于滚动更新,就绪性探针用于控制转换的门槛。在新的 Pod 成功通过其就绪性检查之前,Kubernetes 不会继续终止旧的 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:配置部署策略
为了强制实现真正的零停机,我们明确配置滚动更新策略,以防止可用副本数量出现任何下降。
在此配置中,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 遵循特定的终止顺序:
- Pod 被标记为 Terminating(终止中)。
- Pod 从服务(Service)端点中移除(流量停止路由到它)。
- 执行 pre-stop hook(如果已定义)。
- 容器接收到
SIGTERM信号。 - Kubernetes 等待
terminationGracePeriodSeconds定义的时长(默认值:30 秒)。 - 如果容器仍在运行,它将收到不可协商的
SIGKILL。
为确保优雅关机,应用程序必须处理 SIGTERM,并且 terminationGracePeriodSeconds 必须足够长,以便应用程序完成现有请求。
# deployment.yaml 摘录,位于容器规范内
terminationGracePeriodSeconds: 45 # 增加优雅关机时间
containers:
- name: my-app
image: myregistry/my-app:v2.0
lifecycle:
preStop:
exec:
# 示例:运行一个脚本来立即清空连接
command: ["/bin/sh", "-c", "sleep 10"]
最佳实践: Pod 失败就绪性探针(步骤 2)和接收
SIGTERM(步骤 4)之间的时间至关重要。如果您的应用程序正确处理SIGTERM,设置稍长的terminationGracePeriodSeconds(例如 45 或 60 秒)可以防止硬性终止。
步骤 4:执行和监控更新
一旦您的部署清单包含了优化的策略和强大的探针,执行更新就变得简单明了。
-
更新镜像标签: 修改部署清单以反映新的镜像版本(例如,
v2.0到v2.1)。 -
应用配置:
bash kubectl apply -f deployment.yaml或者,您可以直接修补镜像:
bash kubectl set image deployment/my-web-deployment my-app=myregistry/my-app:v2.1 -
监控滚动状态: 观察 Kubernetes 逐步执行各个阶段,验证就绪 Pod 的数量从未低于所需数量。
bash kubectl rollout status deployment/my-web-deployment -
验证 Pod 可用性: 观察 Pod 状态,确认旧 Pod (v2.0) 仅在新 Pod (v2.1) 完全准备就绪后才被优雅终止。
bash kubectl get pods -l app=my-web-deployment -w
高级注意事项
使用 Pod 驱散预算 (PDB)
虽然部署策略管理自愿更新,但Pod 驱散预算 (PDB) 确保即使在计划外的中断(例如节点维护、集群升级)期间,也始终有最少数量的 Pod 保持可用。尽管 PDB 不直接控制滚动更新的速度,但它们充当安全网。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 75% # 确保至少 75% 的副本始终可用
selector:
matchLabels:
app: my-web-deployment
就绪性初始延迟的重要性
如果您的应用程序需要时间预热(例如加载大型配置文件或建立缓存),请确保就绪性探针中的 initialDelaySeconds 足够长。如果探针检查得太早而失败,Pod 将被标记为不健康,并可能陷入崩溃循环,从而停止整个部署。
结论
在 Kubernetes 中实现真正的零停机滚动更新是稳健的平台配置与有纪律的应用程序开发的结合。通过正确利用就绪性探针来指示运行状态、调整部署策略(maxUnavailable: 0)以维持容量,以及实施优雅终止处理程序,您可以确保可靠地执行应用程序更新,而不会中断对用户的服务。始终在暂存环境中彻底测试您的更新过程,以验证终止宽限期和探针逻辑。