容器在运行,不代表第一条请求能成功

前面 第 04 篇解释新 Pod 注入代理与网络捕获。应用和代理在同一 Pod 内启动时,即使 orders 进程已监听,也不能据此断言它依赖的代理 listener、cluster、证书或后端端点都已准备好。退出时也一样:Pod 开始终止不代表此前正在处理的库存请求自动完成;客户端超时更不说明服务端没有执行。需要在时间轴上分别记录应用状态、代理状态、端点状态和请求结果。

启动与终止时间轴标出应用就绪、端点撤离、SIGTERM、长请求与代理排空的不同位置

左侧先满足服务准备条件,右侧按终止顺序撤离新流量并给已开始的请求留时间。本篇实测只覆盖 Go 应用自身的 SIGTERM 和长请求;图中 Kubernetes readiness、EndpointSlice 和 Envoy drain 阶段是待在真实 sidecar Pod 验证的机制。

把可用性拆成四个状态

应用监听:socket 可接受连接。应用就绪:其 readiness 检查表达“应接收新业务请求”。代理就绪:相关 listener、上游配置及可用连接具备处理条件。Service 端点已更新:调用方所在节点可以把新连接送到此 Pod。这些状态不可能用一个容器的 Running 值代替;readiness 也不等于全链路成功。

在注入 sidecar 的 Pod 中,应用过早发出请求可能撞上代理尚未可用的窗口。Istio 不同版本和安装配置可能提供启动顺序及代理就绪方面的选项,需要固定版本后以新 Pod 的状态转换、代理当前配置和第一批带请求 ID 的调用验证。不能因为某一轮没有失败就推断所有启动窗口都无错误;还要区分入口请求和应用向 external-stub 发起的出口连接。

终止时需要先限制新请求流入,再让已经建立的连接或请求排空。Kubernetes 的 Pod 终止宽限时间要覆盖可用的 preStop、应用收到终止信号后的处理以及代理排空;实际端点撤离向其他节点传播并非瞬时。即便代理停止接纳新连接,客户端仍可能持有旧连接。上游服务、中间代理、应用三处的“排空完成”不是同一个事件;超出宽限期后强制结束与客户端取消会改变结果,不能预先承诺零中断。

先验证应用进程自己的边界

累计工程 examples/service-mesh/apps/mesh-demo/main.go 的 inventory 接口增加 -quote-delay 与 -shutdown-grace:收 SIGTERM 时先标记 readiness 不可用,再用 Go http.Server.Shutdown 等待在途请求(最多等待配置的宽限时间)。脚本直接访问 inventory-v1,没有 Envoy 或 Kubernetes;这使应用层能先得到可检验的时间顺序。

在 examples/service-mesh/ 执行:

1
GO_BIN=/tmp/service-mesh-tools/go/bin/go bash scripts/run-05.sh

脚本在库存处理一个故意延迟 800 ms 的 sm-05-long 读请求期间发送 SIGTERM,等待请求和服务退出,随后再次尝试连接同一端口。examples/service-mesh/evidence/05/20261001T131952Z-2556495/ 的原始日志显示:开始处理 → shutdown_received → HTTP 200 与相同请求 ID → shutdown_complete;长请求 curl 退出 0、进程退出 0,新连接 curl 退出 7(connection refused),读场景的合成账本 []。四个自动断言均通过,命令、进程版本、源码 SHA、原始输出和退出码存于该运行目录。与第 00、01 篇同工程的回归复跑也通过,证据分别在 evidence/00/20261001T131956Z-2556718/ 和 evidence/01/20261001T132002Z-2556366/。

这只证明本机这一轮应用在 2 秒宽限时间内完成了 800 ms 的读请求,以及进程退出后新连接失败。它不证明 Pod readiness 已经传播、Service 不会再选中终止实例、Envoy 不会中断长连接,也不能外推为生产零失败。如果把 grace 缩短到不足以覆盖请求,或服务端没有调用 Shutdown,预期结果须重新测量;在未运行之前不能填入失败比例。

Sidecar 联合实验如何取证

完成第 04 篇的隔离集群 sidecar 注入后,以相同 Go 应用和固定镜像重复实验:持续以请求 ID 调用 frontend → orders → inventory;在长请求进入 inventory 时,仅对实验 Deployment 滚动更新或删除指定 Pod(先验证 kubectl config current-context),按时间采集 Pod conditions、EndpointSlice、应用的 SIGTERM/完成日志、Envoy 代理访问日志和客户端响应。分别断言正常长请求与边界路径;若其中一项预期未达成,测试进程必须返回非零。对比旧连接与新连接,不把控制器声明或一次成功探测当作最终请求证据。

当前环境没有 Kubernetes/kind/kubectl 或已安装 sidecar,应用+代理的启动、就绪、排空、滚动重启均 NOT_RUN。要先固定 Istio/Kubernetes 兼容版本和镜像 digest,再记录真实效果;不把本节单进程 SIGTERM 日志改名为“Envoy drain 结果”。

练习与参考解答

  1. 单进程日志顺序为“开始处理 → SIGTERM → HTTP 200 → 退出”。是否足以证明 sidecar Pod 零中断?参考解答:不能。这里只知道应用这一条在途连接完成,尚缺注入代理的排空状态、EndpointSlice 撤离、新连接结果、旧连接复用以及多次请求的时间线;Service/代理可能在其他窗口出错。
  2. 将 -shutdown-grace 缩短到 100 ms、保留 -quote-delay=800ms,预测现有四项断言中哪项可能失败,如何防止假阳性?参考解答:在途请求可能没等到完整响应,long_completes 及 graceful_exit 不应仍被标为通过;断言必须逐条核对请求 ID、HTTP 码、curl/服务进程退出码,而不是只检查进程确实消失。程序当前在 Shutdown 超时返回错误时退出非零;具体连接结果仍需运行验证。

官方资料与系列导航

系列导航:00 一次服务调用需要哪些基础条件 → 01 SDK、网关与服务网格分别处理什么 → 02 Service 地址怎样到达 Pod → 03 Envoy 怎样转发请求 → 04 Sidecar 怎样接管流量 → 05 应用与代理如何启动和退出(本文)→ 06 路由规则怎样变成代理配置。