一、现象:RST 与 Envoy drain 的根本区别
结论先行:你看到的 RST 大概率不是 Envoy 应用层 drain,而是 istio-agent 在 SDS 推送新 SVID 时触发了 xDS 全量推送或 agent 侧重连,导致 TCP 连接被内核层硬杀。Envoy 的 drain 机制只会发 GOAWAY(gRPC)或优雅关闭,不会产生 TCP RST。RST 是内核 TCP 栈的行为——连接是被"硬杀"的,不是"排空"的。这个区分决定了修复路径完全不同。
二、根因分析:SDS 热更新 vs SDS 重连
Istio 有两条证书更新路径,行为完全不同:
| 路径 | 触发条件 | 对已有连接的影响 | |---|---|---| | SDS 热更新(正常) | istio-agent 拿到新 SVID → SDS gRPC stream push | 无影响,新连接用新证书,老连接不变 | | SDS 重连(异常) | agent crash / gRPC stream 断 / xDS 全量 re-push | TCP RST,所有连接被内核杀 |
你看到的 RST 行为指向第二条路径。常见触发原因:
- istio-agent 在 rotation 窗口内 OOM(证书解析 + xDS 推送同时发生,内存峰值翻倍)
- Envoy 的 xDS gRPC 连接超时(默认 30s),rotation 窗口内 pilot 响应慢 → 触发全量 re-push
- cert-manager 的
renewBefore设置导致 CA 侧同时"签发新证书 + 吊销旧证书",触发全集群 xDS 推送
三、可落地的修复(按优先级)
1) gRPC 连接主动老化(把"被动断"变成"主动续",最稳)
服务端(Go):
srv := grpc.NewServer(
grpc.MaxConnectionAge(23 * time.Hour), // 连接最大存活 23h(证书 24h 有效期,留 1h 余量)
grpc.MaxConnectionAgeGrace(10 * time.Minute), // 发 GOAWAY 后等 10min 完成 in-flight
)
客户端(Go):
dialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: grpc.BackoffConfig{
BaseDelay: 1 * time.Second,
MaxDelay: 30 * time.Second,
Multiplier: 2.0,
},
}),
}
效果:连接在证书到期前就被主动回收(GOAWAY),客户端用新证书重连,永远不会遇到 rotation 时的 RST。这是架构层解法,比调 Envoy 参数更稳。
2) 防止 istio-agent 重连(根治 RST)
# values.yaml
global:
proxy:
resources:
requests:
memory: "512Mi" # 给 agent 足够内存,避免 rotation 时 OOM
env:
- name: GRPC_KEEPALIVE_TIME
value: "30s" # agent → pilot 的 gRPC keepalive
- name: GRPC_KEEPALIVE_TIMEOUT
value: "10s"
- name: GRPC_KEEPALIVE_PERMIT_WITHOUT_CALLS
value: "true"
# 关键:延长 xDS 连接超时,避免 rotation 窗口内断连触发全量 re-push
- name: XDS_RESOURCE_TIMEOUT
value: "120s"
3) Envoy 侧:确认 drain 策略 + 验证
# 检查当前 drain 配置
curl -s http://localhost:15020/server_info | jq '{version, state, uptime}'
curl -s http://localhost:15000/config_dump | jq '.configs[] | select(.["@type"] | test("Bootstrap"))' | jq '.drain'
在 values.yaml 中:
global:
proxy:
drainDuration: "10s" # 收到信号后开始排空
parentShutdownDuration: "60s" # 总排空上限
env:
- name: ENVOY_DRAIN_STRATEGY
value: "DRAIN_ONLY" # 只排空 idle 连接,不动 in-flight
4) cert-manager:让轮换"无感化"
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: mtls-leaf
spec:
duration: 72h # 72h 有效期
renewBefore: 48h # 提前 48h 续签 → 实际每次"换"只覆盖 24h 窗口
# 关键:不要让 CA 在签发新证书的同时吊销旧证书
# 检查你的 CA issuer 配置,确保 revocation 是异步的
四、量化预期
| 指标 | 现状 | 修复后 | |---|---|---| | rotation 时 RST 次数 | ~200 节点 × 1 次/天 | 0(连接在到期前已主动回收) | | in-flight 丢失 | 200ms–2s / 连接 | 0(GOAWAY 优雅关闭 + 10min grace) | | 客户端重连延迟 | 200ms–2s(backoff) | < 50ms(主动重连) | | 证书泄露窗口 | 24h(旧证书仍有效) | 24h(不变,但连接已切换) |
五、一句话总结
RST 是 TCP 层的硬杀,不是 Envoy 应用层的 drain。先用 tcpdump 确认 RST 方向(本端 vs 对端),再定位是 agent 重连还是 xDS 全量推送。修复核心:grpc.MaxConnectionAge(23h) + MaxConnectionAgeGrace(10min) 让连接在证书到期前主动回收,把"被动断"变成"主动续";同时确保 istio-agent 在 rotation 窗口内不 OOM、xDS 不断连。