深色模式
优雅终止与连接排空
面向被"发布时总有少量 502/连接重置"困扰的服务负责人与 SRE。覆盖终止时序、异步 Endpoint 传播这个核心坑、以及可验证的修复手段。
适用版本与前提
- Kubernetes:v1.28+
- 前提:Deployment + Service + Ingress 的基本链路已跑通
- 涉及:kubelet、EndpointSlice、kube-proxy、Ingress Controller
背景与问题
滚动更新、节点驱逐、缩容,都会让 Pod 被删除。如果 Pod 在收到终止信号时立刻停止接收新请求并关闭连接,而此时上游(kube-proxy / Ingress / 客户端长连接)还认为它是可用后端,就会出现短时间的 5xx 或连接重置。
典型现象:
- 每次发布监控上出现一小撮 502/503 尖刺。
- 客户端报
connection reset by peer。 - 灰度/回滚时问题更明显。
根因通常不是代码没处理 SIGTERM,而是Endpoint 移除是异步传播的:kubelet 开始终止 Pod 时,其他组件还没更新转发表。
核心概念与时序
Pod 删除的完整时序:
关键点:
- Endpoint 移除与 kubelet 杀容器是并行发生的,不是"先摘流量再杀"。
- kube-proxy 更新 iptables/IPVS 规则需要时间;Ingress Controller(如 ingress-nginx)也有自己的同步周期。
- 因此从"开始终止"到"上游不再转发",存在一个窗口期。若容器在这个窗口内就关闭监听,请求就会失败。
生产实践
1. 正确处理 SIGTERM
应用必须捕获 SIGTERM,触发:
- 就绪探针立即返回失败(促使 Endpoint 更快摘除,
[版本相关])。 - 停止接收新请求,但先排空正在处理的请求。
- 关闭连接池、刷盘、释放锁。
text
反例:收到 SIGTERM 直接 os.Exit(0) → 正在进行中的请求被切断。
正例:收到 SIGTERM → 关闭监听 → 等待 in-flight 请求完成(有上限)→ 退出。1
2
2
2. 用 preStop 覆盖传播窗口
最常用、最有效的手段是在终止前"故意等一会儿",让上游完成摘除:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 4
template:
spec:
terminationGracePeriodSeconds: 60 # 给足总时长
containers:
- name: web
image: registry.example.com/web:1.8.0
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"] # 覆盖 Endpoint 传播窗口
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 2
failureThreshold: 21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
建议
preStop的sleep时长要大于"上游摘除该 Endpoint 所需时间"。经验上从 5s 起步,用监控验证后收敛;15s是常见保守值。[未实测]
注意
preStop的 sleep 会计入终止总时长。必须保证terminationGracePeriodSeconds > preStop 时长 + 应用排空时长,否则容器会被 SIGKILL 强杀,优雅终止失效。
3. 让就绪探针更快失败
终止期间就绪探针快速失败,可促使 EndpointSlice 尽快更新:
yaml
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 2 # 缩短探测间隔
failureThreshold: 2 # 缩短判定失败次数1
2
3
4
2
3
4
4. 长连接场景(gRPC/WebSocket)
长连接不会被"新连接不进来"自动排空,需要显式处理:
- 收到 SIGTERM 后主动发送 GOAWAY(gRPC)或关闭帧(WebSocket)。
- 客户端需支持重连到其他后端。
- 设置
maxConnectionAge/maxConnectionAgeGrace(gRPC 服务端参数)强制周期性断连重连,避免流量长期粘在老 Pod 上。
版本相关
原生 Sidecar 容器(initContainer 带
restartPolicy: Always)在 v1.29+ 的行为会保证 sidecar 在主容器之后终止,避免"代理先死、主容器还在收流量"。若你的服务网格/日志代理是 sidecar,需确认其终止顺序,否则优雅终止会被 sidecar 破坏。
5. 节点驱逐场景
节点 drain 会同时驱逐多个 Pod,冲击更大:
bash
# 先用 PDB 限制并发中断,再 drain
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --grace-period=601
2
2
配合 PDB:
yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 3
selector:
matchLabels:
app: web1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
验证
不要只看"Pod 变成 Terminating 后消失了",要验证零丢包:
bash
# 1) 持续打流量,观察发布期间的错误率
while true; do curl -s -o /dev/null -w "%{http_code}\n" http://web.example.com/healthz; sleep 0.2; done
# 2) 发起滚动更新
kubectl set image deployment/web web=registry.example.com/web:1.9.0
# 3) 同时观察 Endpoint 变化与 Pod 终止时序
kubectl get endpointslices -l kubernetes.io/service-name=web -o yaml
kubectl get pods -l app=web -w1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
判定标准:整个发布过程中,外部观测到的 5xx 数量为 0(或在你允许的错误预算内)。
回滚与清理
bash
# 发布中发现问题,立即回滚
kubectl rollout undo deployment/web
kubectl rollout status deployment/web
# 若 preStop sleep 设置过长导致发布变慢,缩短它(需重新验证零丢包)
kubectl patch deployment web -p '{"spec":{"template":{"spec":{"containers":[{"name":"web","lifecycle":{"preStop":{"exec":{"command":["/bin/sh","-c","sleep 8"]}}}}]}}}}'1
2
3
4
5
6
2
3
4
5
6
故障排查
| 现象 | 可能原因 | 定位 |
|---|---|---|
| 发布时少量 502 | preStop 缺失或太短 | 加 sleep 并验证 |
| 发布时大量 502 | terminationGracePeriodSeconds 太短被 SIGKILL | 看 Pod 是否被强杀(Exit 137) |
| 长连接断开 | 未处理 GOAWAY / 未主动关闭 | 客户端日志 + 服务端连接数指标 |
| sidecar 场景仍报错 | sidecar 先于主容器终止 | 确认原生 sidecar / 服务网格终止顺序 |
| drain 时服务掉量 | 无 PDB,并发驱逐过多 | 配置 PDB |
bash
# 看是否被强杀
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
# exitCode 137 = SIGKILL,说明优雅终止超时被强杀1
2
3
2
3
安全与合规
preStop脚本应以最小权限运行,避免在容器内执行任意 shell 逻辑被滥用。- 终止期间若持有分布式锁,必须释放,否则会造成业务阻塞。
性能、容量与成本
preStopsleep 会拉长单次发布的时长(每个 Pod 多 5~15s),批量发布时总时长增加明显。可用maxSurge提高并行度对冲。- 过长的
terminationGracePeriodSeconds会拖慢节点 drain,影响故障节点的快速腾空。
替代方案与权衡
| 手段 | 效果 | 代价 |
|---|---|---|
preStop sleep | 简单有效,覆盖传播窗口 | 发布变慢 |
| 就绪探针快速失败 | 加速摘除 | 探针抖动可能误摘 |
| 服务端主动 GOAWAY | 长连接场景必需 | 需客户端配合 |
| 服务网格层排空 | 集中控制 | 引入网格复杂度 |
参考资料
- Kubernetes 官方文档:Pod 生命周期 - Pod 终止,访问日期:2026-10-09。
- Kubernetes 官方文档:容器生命周期回调,访问日期:2026-10-09。
- Kubernetes 官方文档:配置存活/就绪/启动探针,访问日期:2026-10-09。
- Kubernetes 官方文档:Pod Disruption Budgets,访问日期:2026-10-09。