7AA7 博客

滚动更新与回滚:K8s 健康检查的细节

凛川 Wave UID:74
2026-09-25 14:16
43 1 1

Kubernetes 的滚动更新能逐步替换副本,但“Pod 已启动”与“业务可接流量”并不是同一状态。更新能否不断流,取决于探针语义、替换节奏、流量摘除和进程退出能否在一个时间窗口内配合,而不只是 Deployment 使用了 RollingUpdate。

readiness 与 liveness 不能互换

readinessProbe 表示当前容器是否准备好接收流量。探针失败时,容器不会被重启,Pod 的 Ready 条件变为 false,并从 Service 的可用端点中移除。它适合检查应用是否完成初始化、关键线程是否可工作,以及本实例能否处理请求。

livenessProbe 表示容器是否陷入无法自行恢复的状态。连续失败达到阈值后,kubelet 会重启容器。它应检测进程内部的致命卡死,而不是所有外部依赖是否健康。

把数据库、缓存或远端接口连通性写进 liveness 是危险的:共享依赖短暂故障时,大量健康应用实例同时重启;重启带来的初始化、连接重建与流量抖动又会加重依赖压力,形成雪崩。外部依赖异常可反映到 readiness,使实例暂时不接流量,但也要评估“全部实例同时 NotReady”是否比降级服务更糟。应用若能返回缓存或部分功能,readiness 不应因为非关键依赖失败而整体关闭。

readinessProbe:
  httpGet:
    path: /ready
    port: http
  periodSeconds: 5
  failureThreshold: 2
livenessProbe:
  httpGet:
    path: /live
    port: http
  periodSeconds: 10
  failureThreshold: 3

探针端点要轻量,不能每次执行昂贵查询。超时、周期与失败阈值共同决定检测速度;阈值太敏感会因瞬时暂停抖动,太迟钝又会让坏实例长期接流量。HTTP 探针返回码、端口名和监听地址也应在与生产一致的容器中验证。

健康端点还要覆盖真正的处理路径。若 /live 由独立管理线程响应,而业务线程池已经死锁,探针仍可能持续成功;可加入事件循环心跳或关键队列是否推进的本地状态。readiness 则不应只返回“进程已启动”,应确认路由、配置和必要的本地预热完成。探针处理器必须绕过认证跳转,并限制只返回状态码,不在响应中暴露依赖地址与异常细节。

排查探针失败时,可从 Pod 事件看到超时、连接拒绝或非成功状态,但还要在容器内验证端口与路径。命令探针会在容器中反复创建进程,执行脚本过重可能自身消耗资源;TCP 探针只能证明端口可连接,无法判断应用协议是否能正常处理请求。

startupProbe 保护慢启动

启动时间较长的应用如果直接启用 liveness,可能在初始化完成前被反复重启,永远无法进入可用状态。startupProbe 在成功前会屏蔽 readiness 与 liveness 的判定;一旦成功,后续不再执行,由另外两类探针接管。

startupProbe:
  httpGet:
    path: /startup
    port: http
  periodSeconds: 5
  failureThreshold: 24

示例给出约两分钟的启动窗口,但窗口应来自启动耗时分布,不应照搬。若超过窗口,容器会按失败策略重启。startupProbe 不是把 liveness 的初始延迟调大:它能让启动完成后立即进入常规检测,也能适应不同实例的启动速度。

maxSurge 与 maxUnavailable

Deployment 的滚动策略控制更新时最多增加多少新副本、最多允许多少期望副本不可用:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

假设期望副本为 4,maxSurge: 1 允许更新期间最多存在 5 个 Pod,maxUnavailable: 0 要求至少 4 个可用。这个组合偏向可用性,但集群必须有额外容量,新旧版本也可能短暂并行访问数据库或队列。

参数可以是绝对值或百分比。百分比会按规则取整,小副本服务尤其需要查看渲染后的实际数字。maxUnavailable 设为 1 并不保证用户一定感知不到错误,如果剩余容量无法承载流量或 readiness 误报,仍会失败。两个参数也不能同时为零,否则控制器没有替换空间。

minReadySeconds 可要求新 Pod 连续就绪一段时间后才计为 available,减少刚就绪便崩溃的实例推动更新继续。progressDeadlineSeconds 用于识别更新长期无进展,但超时本身不会自动保证业务回滚,需要发布系统明确处置。

优雅停机的完整链路

删除 Pod 后,端点摘除与容器停机是并行传播的。kubelet 开始终止流程时,优雅期计时已经开始;若配置了 preStop,钩子在发送 TERM 信号前执行,耗时也占用 terminationGracePeriodSeconds。钩子完成后,运行时向容器主进程发送 TERM,超时仍未退出则发送 KILL。

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 40

短暂 preStop 等待可给 Service 端点变化和外部负载均衡传播留时间,但它不是通用答案。更可靠的应用流程是:收到 TERM 后立即把 readiness 置为失败,停止接受新连接,继续处理在途请求,关闭消费者拉取,提交必要状态,再在优雅期内退出。PID 1 必须能收到并处理信号;用不转发信号的 shell 包裹应用,会让优雅逻辑失效。

HTTP keep-alive、WebSocket、gRPC 流和消息消费者都有各自的排空语义。服务器应停止接收新会话,并为长连接设置最大排空时间。优雅期需要覆盖端点传播、最长可接受请求与清理时间,但不能无限放大,否则故障实例删除会拖慢更新。

不断流的三个条件

滚动期间没有可感知中断,至少依赖三项同时成立:

  1. 容量足够:maxSurge 带来的新副本能被调度,剩余 Ready 副本能承载峰值流量。
  2. 就绪准确:readiness 只在实例真正可服务时通过,Service 与入口只向 Ready 端点发送新连接。
  3. 退出可排空:旧实例在端点摘除传播完成前不突然关闭,并在优雅期内处理完在途请求。

还需保证新旧版本在短暂共存时兼容。数据库变更宜采用先扩展、后切换、再收缩的方式;若新代码启动后立刻执行破坏性迁移,即使 Pod 更新节奏正确也无法安全回滚。

可在预发布环境持续发请求并触发 rollout,按请求 ID 检查错误与延迟,同时观察 Ready 副本、EndpointSlice 和终止中 Pod。只看 rollout status 成功无法证明期间没有短暂错误。

回滚依赖不可变镜像

kubectl rollout history deployment/api
kubectl rollout undo deployment/api --to-revision=12
kubectl rollout status deployment/api --timeout=180s

Deployment 的历史 ReplicaSet 保存旧 Pod 模板,rollout undo 会把模板恢复到选定修订。如果模板引用 app:latest 或被覆盖的版本标签,旧修订可能拉到新内容,回滚名义成立而二进制没有恢复。镜像应以不可变版本或摘要固定,并保留对应制品。

回滚还受数据库、消息格式和外部配置约束。不可逆迁移完成后,旧应用可能无法读取新结构;配置中心若只保留当前值,恢复旧模板也未必可运行。发布前应写清应用、配置与数据的兼容窗口,并把回滚作为一次完整变更验证,而不是只执行一条命令。

出现新 Pod 长期 NotReady 时,不要反复重启所有副本。应暂停 rollout,查看探针响应、事件、资源调度与新旧版本日志;确认旧版本仍兼容后再回滚。健康检查提供的是控制信号,只有信号真实、更新有容量、停机能排空,不断流发布才具备工程基础。

1.9k浏览量
43赞
1评论
1参与用户
9 分钟阅读时间

评论(1)

  • 硅屿 · 2026-09-25 16:42

    这个坑我上周才踩,看到这篇有种被精准打击的感觉。

    回复