云原生生产实践:Kubernetes 微服务优雅停机(Graceful Shutdown)与零宕机发布
【摘要】 摘要:在云原生容器化环境中,许多团队在执行 Kubernetes 滚动更新(Rolling Update)时,经常遇到网关偶发报 502 Bad Gateway 或 504 Gateway Timeout 的情况。本文从 Pod 销毁的底层异步机制出发,详解如何通过生命周期钩子(PreStop Hook)与就绪探针配合,实现真正的业务零感知平滑发布。
一、 为什么滚动更新还会触发 502 报错?
在 Kubernetes(如云容器引擎 CCE)中更新 Deployment 时,系统默认会先启动新 Pod,待其就绪后再销毁旧 Pod。但在高并发生产环境下,旧 Pod 销毁瞬间依然常出现请求报错,核心原因在于 K8s 控制面组件动作是异步并发的。
当一个 Pod 被标记为
Terminating 时,以下两条链路会同时触发,不存在先后阻塞关系:| 并发链路 | 执行组件 | 核心动作 | 潜在时间差隐患 |
| 链路 A:容器销毁 | Kubelet | 执行 preStop 钩子 $\rightarrow$ 发送 SIGTERM 信号 $\rightarrow$ 等待超时后发送 SIGKILL |
容器收到 SIGTERM 后若立即关闭监听端口,耗时通常在毫秒级。 |
| 链路 B:流量摘除 | EndpointController / kube-proxy / Ingress | 从 Endpoints 列表中移除 Pod IP $\rightarrow$ 各节点更新 iptables/IPVS 规则 $\rightarrow$ 网关同步配置 | 跨节点同步网络规则与网关摘除上游节点通常需要 1~3 秒。 |
故障本质:链路 A 比链路 B 快。当容器进程已经收到
SIGTERM 停止接收新连接时,Ingress 或负载均衡器上的转发规则尚未更新完毕,新的业务流量依然被路由到了正在关闭的旧 Pod 上,直接导致连接被拒绝(Connection Refused,触发 502)。二、 零宕机平滑下线的三步黄金法则
要彻底消除这个“时间差”,必须人为延缓链路 A 的进程退出节奏,确保先断流、再停机:
-
缓冲等待(PreStop Hook):在 Kubelet 发送
SIGTERM之前,利用preStop钩子让容器原地sleep几秒,给足 kube-proxy 和 Ingress Controller 刷新路由表的窗口期。 -
存量请求处理(应用层捕获 SIGTERM):应用框架(如 Spring Boot、Go HTTP Server)捕获
SIGTERM信号后,停止接收新请求,但继续处理完内存中正在执行的存量请求及数据库事务。 -
兜底强杀控制(terminationGracePeriodSeconds):合理设置宽限期,防止慢请求无限期卡死 Pod 退出流程。
三、 生产级 Deployment 配置实战
以下是一份可直接用于生产环境的 Kubernetes 微服务无损下线 YAML 配置模板:
YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: prod
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # 升级过程中最多可超出的 Pod 数量
maxUnavailable: 0 # 确保升级期间可用实例数不低于期望值
template:
metadata:
labels:
app: order-service
spec:
# 1. 设置总优雅停机宽限期为 60 秒(默认 30 秒)
terminationGracePeriodSeconds: 60
containers:
- name: order-service
image: registry.example.com/prod/order-service:v2.1.0
ports:
- containerPort: 8080
# 2. 配置就绪探针,确保新 Pod 真正具备处理能力后才接入流量
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 2
# 3. 核心:利用 preStop 预留 5 秒网络规则同步缓冲期
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
应用层配套配置(以 Spring Boot 为例)
仅有 K8s 层的延迟还不够,业务代码自身不能在收到
SIGTERM 时立刻暴力退出。在 Spring Boot 2.3+ 中,只需在 application.yml 中开启内置优雅停机支持:YAML
server:
shutdown: graceful # 开启优雅停机(默认为 immediate 立即终止)
spring:
lifecycle:
timeout-per-shutdown-phase: 45s # 预留 45 秒处理存量请求(需小于 K8s 的 60s 宽限期)
四、 落地避坑指南
-
算准时间账:务必满足公式
preStop 休眠时间 (5s) + 应用层优雅停机超时 (45s) < terminationGracePeriodSeconds (60s)。如果总耗时超出 K8s 宽限期,Kubelet 会直接发送SIGKILL (-9)强杀进程,导致未完成的事务中断。 -
一号进程(PID 1)信号传递陷阱:在编写
Dockerfile时,务必使用ENTRYPOINT ["java", "-jar", "app.jar"](Exec 格式),避免使用 Shell 格式启动。如果业务进程不是容器内的PID 1,而外层包裹的/bin/sh又没有转发信号,应用将永远收不到 Kubelet 发出的SIGTERM信号,每次发布都会硬生生卡满 60 秒后被强杀。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)