CCE 里 Ingress 转发 gRPC 长连接
CCE 里 Ingress 转发 gRPC 长连接
结论先行:滚动更新时客户端成片 UNAVAILABLE,根因通常不是网络不通,而是 HTTP/2 长连接遇上 Pod 硬退出——连接被 RST,在途流全部失败,而 gRPC 客户端默认不重试。要修的是四件事:就绪探针先摘流、preStop 争取排空时间、终止宽限期大于最长流时长、客户端显式开重试。
一、先确认故障形态
把下面三条对齐,基本就能把问题钉死在连接生命周期上:
· 错误码是 UNAVAILABLE,message 常见 "Stream closed with RST_STREAM"、"Connection closed with unknown cause" 或收到 GoAway;
· 报错时间与滚动发布窗口完全重合,每次发布死一批,新 Pod 就绪后自愈;
· 范围不是单实例,而是发布瞬间成片出现。
如果报错是 DeadlineExceeded 或业务码,方向完全不同,别按本文改。
二、根因:HTTP/2 的连接不是请求级的
gRPC 走 HTTP/2,一条 TCP 连接上复用成百上千条流,连接生命周期远长于单个请求。Ingress 侧与后端 Pod 维持的也是长连接。Pod 被终止时:
1. Endpoints 先把 Pod 从 Service 摘除,但已经建立的连接不会自动断开; 2. 容器进程若被 SIGKILL,TCP 直接 RST,这条连接上的全部在途流瞬间失败; 3. gRPC 客户端默认对 UNAVAILABLE 不重试,错误直接透传给调用方。
所以治理思路是:让 Pod 在真正退出前把存量连接用完,再让客户端兜底重试。
三、改法(按优先级)
<strong>1. 就绪探针用 gRPC 探针</strong>,保证真正能服务了才进 Endpoints:
readinessProbe:
grpc:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
<strong>2. preStop 钩子争取排空时间</strong>。先 sleep 再退出:这段时间 kubelet 已把 Pod 从 Endpoints 摘掉,Ingress 不再打新流量,旧连接上的流自然结束:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 15"]
sleep 时长按最慢的一条流估,常见 10 到 30 秒。
<strong>3. terminationGracePeriodSeconds 给足</strong>。默认 30 秒,preStop 15 秒加应用收尾 10 秒就要把宽限期调到 60 秒以上,否则到上限照样 SIGKILL,前面的排队白做。
<strong>4. 应用收到 SIGTERM 主动停收新流</strong>。grpc-go、grpc-java 的优雅停机一般自带:收到 SIGTERM 后停止接收新流、存量流跑完再退出。确认自己没有把 SIGTERM 当成立即退出。
<strong>5. 客户端开重试兜底</strong>(以 grpc-go 的 service config 为例):
{
"methodConfig": [{
"name": [{"service": "pkg.Greeter"}],
"retryPolicy": {
"MaxAttempts": 4,
"InitialBackoff": "0.5s",
"MaxBackoff": "5s",
"BackoffMultiplier": 2,
"RetryableStatusCodes": ["UNAVAILABLE"]
}
}]
}
注意:查询类接口放心开;写操作要先确认幂等,否则重试可能造成重复提交。
四、验证
发布窗口期内持续压测:客户端统计 UNAVAILABLE 计数,配置正确后应只在秒级抖动且重试后全部成功;再抽查 Pod 退出日志,确认从 SIGTERM 到进程退出之间存在完整的收尾间隔,而不是瞬间被杀。
排查顺序总结:错误形态 → 是否与发布窗口重合 → preStop 与宽限期 → 就绪探针 → 客户端重试。每一层都能单独验证,不要同时改。
- 点赞
- 收藏
- 关注作者
评论(0)