云原生生产实践:Kubernetes 微服务优雅停机(Graceful Shutdown)与零宕机发布

举报
yd_243452805 发表于 2026/10/24 18:11:24 2026/10/24
【摘要】 摘要:在云原生容器化环境中,许多团队在执行 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 的进程退出节奏,确保先断流、再停机:

  1. 缓冲等待(PreStop Hook):在 Kubelet 发送 SIGTERM 之前,利用 preStop 钩子让容器原地 sleep 几秒,给足 kube-proxy 和 Ingress Controller 刷新路由表的窗口期。

  2. 存量请求处理(应用层捕获 SIGTERM):应用框架(如 Spring Boot、Go HTTP Server)捕获 SIGTERM 信号后,停止接收新请求,但继续处理完内存中正在执行的存量请求及数据库事务。

  3. 兜底强杀控制(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 宽限期)

四、 落地避坑指南

  1. 算准时间账:务必满足公式 preStop 休眠时间 (5s) + 应用层优雅停机超时 (45s) < terminationGracePeriodSeconds (60s)。如果总耗时超出 K8s 宽限期,Kubelet 会直接发送 SIGKILL (-9) 强杀进程,导致未完成的事务中断。

  2. 一号进程(PID 1)信号传递陷阱:在编写 Dockerfile 时,务必使用 ENTRYPOINT ["java", "-jar", "app.jar"](Exec 格式),避免使用 Shell 格式启动。如果业务进程不是容器内的 PID 1,而外层包裹的 /bin/sh 又没有转发信号,应用将永远收不到 Kubelet 发出的 SIGTERM 信号,每次发布都会硬生生卡满 60 秒后被强杀。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。