库存实例变慢,该拒绝请求还是换一台

同一批 orders → inventory 调用中,inventory-v1 响应变慢,而 inventory-v2 仍能处理请求。“连接数满了”与“v1 连续失败”需要两种不同的判断:前者保护某个代理指向上游集群的连接、排队请求或活跃请求等资源;后者依据目标实例的错误信号,暂时从该代理的健康负载均衡集合中摘除异常端点。把两者笼统叫作“熔断”,会让排障时把局部资源耗尽误判为实例不健康。

每个代理局部的资源阈值与集群内某个异常端点的摘除边界

Envoy 的 circuit breaking 是分布式但不集中协调的代理限制:最大连接数、待处理请求、活跃请求、重试等分别计数,边界由各代理所见的 cluster、优先级及配置决定。达到待处理请求上限可能让新请求溢出,但不会据此断言整个服务的全局每秒请求数受到了精确限制。尤其是 HTTP/2 复用下,多请求不必对应多连接;连接阈值也不等于并发请求上限。实际连接数在实现与连接池边界下不宜写成严格永不越过设定值,需核对所用版本文档和代理计数器。

异常实例检测(outlier detection)是被动健康检查:根据代理观测到的连续错误、时间窗口成功率等信号,把某个上游端点从健康集合暂时摘除,并受最大摘除比例、时长及执行开关等配置约束。主动健康检查则定期探测目标,即使没有业务请求也可得到信号;两者可以组合,但不能说“给 v1 开个健康接口就等于已开启被动摘除”。如果只有一个可用实例,策略和门槛可能使错误继续暴露而不是神奇地换到 v2;恢复也应核对重新纳入的条件,不仅看客户端偶然一次 200。

把两个实验分开,才有可比较的结果

先用 examples/service-mesh/ 的无代理调用确认 inventory-v1、inventory-v2 和失败分支确实可区分:

1
bash scripts/run-00.sh

基线正常请求由 orders 指向 v1;-quote-delay 可在库存只读报价前引入人工延迟,错误端口则会导致应用无法连接。它们只说明服务处理速度或连接目标错误,没有 Envoy circuit breaker,也没有 outlier ejection;run-00.sh 启动 v2 不意味着代理正向它切换。不能把应用返回的 502/503 改写成代理溢出计数或实例被摘除。

待 Envoy 和隔离环境可用时需做两组正反对照。资源组:固定同一健康上游,先以低并发证明请求可达,再增加并发/排队请求,保存客户端非零结果、代理 upstream_cx_overflow 或相应 pending/request overflow 计数和后端实际请求数;降低负载后确认恢复。实例组:维持足够资源阈值,把 v1 的可控失败与 v2 的正常响应分别记录,核对代理的 ejection 事件/计数、当时可选端点和后续恢复;再关闭摘除做反例。主动健康检查须另存探测请求和状态,不能借用业务请求日志冒充。

应用故障桩的实测记录在 examples/service-mesh/evidence/verification/mainline-13-25/fault-rerun.log,退出码见同目录 fault-rerun.exit。它覆盖正常 200、应用 503、延迟 200 和 HTTP 响应前断连;只证明输入故障能够复现,两个代理组的资源限制与摘除实验仍 NOT_RUN。须等实际代理配置和请求记录齐备后再填写计数、错误来源与恢复时间。最终 200 也可能掩盖先前被重试的失败。

待验收组 正常基线 故障/过载客户端结果 代理计数或摘除事件 后端实际请求
资源溢出与降载 待运行 待运行 待运行 待运行
v1 失败、v2 正常与恢复 待运行 待运行 待运行 待运行
关闭摘除的反例 待运行 待运行 待运行 待运行

用不同信号分别验证资源限制与摘除

以下命令在 examples/service-mesh 目录执行,使用 Kubernetes 1.33.4、Istio 1.27.0 的 sidecar 实验基线。先完成 04 的安装与累计应用部署;容器名为 app,Service 端口为 8080。先恢复上一章策略,再执行本章,避免超时、授权或出口规则互相污染。所有预期结果都是验收条件,未运行集群时保留 NOT_RUN。

1
2
3
4
5
6
export SM_CONTEXT=kind-service-mesh-sidecar
k() { kubectl --context="$SM_CONTEXT" -n service-mesh-lab "$@"; }
k get deploy frontend orders inventory-v1 inventory-v2
k apply -f mainline/13/probe.yaml
k rollout status deploy/sm-probe --timeout=120s
OUT="evidence/mainline-15/$(date -u +%Y%m%dT%H%M%SZ)"; mkdir -p "$OUT"

sm-probe 使用 orders ServiceAccount,明确注入 sidecar;它提供 curl,不依赖 scratch 应用镜像里不存在的 shell。每次请求同时保存客户端结果、来源代理配置和目标应用日志。

mainline/15/fault_server.py 是隔离的 Python 3.12.11 测试桩,支持 ok、fail、slow、reset;两个 Deployment 共用 fault-lab Service。MODE 环境变量让一个实例持续失败,查询参数可让单次请求变慢。它不替换累计库存应用,也没有真实库存副作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
k create configmap fault-code --from-file=mainline/15/fault_server.py --dry-run=client -o yaml | k apply -f -
k apply -f mainline/15/workloads.yaml
k rollout status deploy/fault-a --timeout=120s
k rollout status deploy/fault-b --timeout=120s
k apply -f mainline/15/outlier.yaml
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch15-normal' -fsS 'http://fault-lab:8080/quote?sku=paper' > "$OUT/normal.json"
k set env deploy/fault-a MODE=fail
k rollout status deploy/fault-a --timeout=120s
k exec deploy/sm-probe -c curl -- sh -c 'for i in $(seq 1 30); do curl -H "x-request-id: ch15-outlier-requests-$i" -sS -w "\n%{http_code}\n" "http://fault-lab:8080/quote?sku=paper;" sleep 0.2; done' > "$OUT/outlier-requests.txt"
k exec deploy/sm-probe -c istio-proxy -- pilot-agent request GET 'stats?filter=outlier_detection' > "$OUT/outlier-stats.txt"
k set env deploy/fault-a MODE=ok
k rollout status deploy/fault-a --timeout=120s
k apply -f mainline/15/limits.yaml
k exec deploy/sm-probe -c curl -- sh -c 'for i in $(seq 1 20); do curl -H "x-request-id: ch15-overload-$i" -sS -w "\n%{http_code}\n" "http://fault-lab:8080/quote?mode=slow&sku=paper" & done; wait' > "$OUT/overload.txt"
k exec deploy/sm-probe -c istio-proxy -- pilot-agent request GET 'stats?filter=overflow' > "$OUT/overflow-stats.txt"
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch15-recovered' -fsS 'http://fault-lab:8080/quote?sku=paper' > "$OUT/recovered.json"
k delete destinationrule fault-lab

outlier.yaml 允许摘除比例 100%,关闭 minHealthPercent 回退,并设置连续 2 次 5xx、基础摘除 10s;这是为了两实例实验能观察摘除,不是生产容量建议。先保存零故障计数,再要求失败实例出现 ejection 增量,后续请求偏向另一实例。Pod rollout 会换地址,因此这一步恢复证明新健康端点可用;要研究同一个端点到期重新纳入,需要保持 Pod 不变,用独立控制接口切换模式,本桩没有实现该接口,不能把 rollout 叫作自然恢复。

limits.yaml 替换同名 DestinationRule,移除 outlier,只限制本来源代理的连接、排队和活跃请求。并发组应出现 overflow 增量,串行恢复组应成功;只看到 503 而计数没变,资源限制验收不通过。清理时再删除 workloads.yaml 和 fault-code;23/25 要复用测试桩时可以保留,必须恢复 MODE=ok。

固定源码入口:Istio 1.27.0 pilot/pkg/networking/core/cluster_builder.go。该链接用于核对配置生成或执行机制;完整资源和命令保存在 examples/service-mesh/mainline/15/。静态检查通过不等于集群实验通过。

练习与参考解答

  1. 两个 sidecar 的连接限制各为 10,能断言整个库存服务同时最多 10 条连接吗?参考解答:不能。限制由各代理对目标 cluster 的连接池独立执行;有多个来源代理、连接复用和连接池差异时应分别读取各代理计数,不能把局部阈值当全局配额。
  2. v1 发生连续 503 后,v2 开始收到请求,能否直接认定是主动健康检查摘除了 v1?参考解答:不能。核对负载均衡池中 v1 的状态、outlier 摘除事件、主动探测日志和请求尝试;路由变更或客户端重试也可能改变最终看到的目标。关闭 outlier 后重试相同受控失败,是可证伪的对照。

官方参考与系列导航

系列导航:14 重试与副作用 → 15 连接限制与异常摘除(本文)→ 16 故障注入与镜像。