影子请求失败,为什么主请求仍成功

把 orders → inventory-v1 的只读报价复制一份给 inventory-v2,主链路可能照常返回 v1 的结果,影子服务也记录一条请求。这并不矛盾:流量镜像(mirroring/shadowing)复制请求,但主请求不等待影子目标返回。若被复制的是 POST /reserve,影子库存即使不参与主响应,也可能单独写入副作用账本;“不影响主响应”绝不等于“不会执行写入”。

主链路、镜像影子链路与故障注入位置

隔离写入不是只给 v2 改个名字。两个实例都执行同一业务副作用时,必须使用不会触达真实库存、账务和通知的独立账本;测试输入也要与生产数据隔离。镜像尽力而为,影子响应和耗时不能替代主请求的成功判据,影子收到的请求数也不能不经对照就当作主请求数。

故障发生在哪一侧,决定了能观察到什么

Istio VirtualService 可在客户端侧为 HTTP 流量指定延迟或 abort(注入错误响应),也可以定义镜像目标和比例。延迟、直接 abort 与应用提交后丢失响应有不同的执行路径:代理若在发往上游前直接返回错误,上游账本可能没有记录;应用已写账本再延迟,则客户端失败也可能对应真实写入。不能用一个代理注入的 503 来模拟“操作已经提交但响应丢失”;应由应用控制提交后的延迟/断开,随后核对账本。

还要注意组合边界:Istio VirtualService 字段说明,客户端侧启用 fault 时,该侧超时或重试不会启用。不能一边在同侧加故障注入,一边将“未发生重试”归因于业务策略。对重试放大的验证应优先保留应用注入故障,另以不组合 fault 的代理配置测试重试;每组记录发往上游的尝试数,而不是只看客户端最终状态码。第 14 篇的合成账本正是应用提交后延迟响应的另一类故障。

正常与失败路径:先跑进程基线,再补代理

在 examples/service-mesh/ 可从只读调用和合成写入基线开始:

1
2
bash scripts/run-00.sh
bash scripts/run-01.sh

run-00.sh 的成功报价是主链路基线,但工程中 inventory-v2 的存在不表示已有镜像;run-01.sh 在库存提交后延迟响应、人工重发同一操作 ID,能检验“客户端失败不等于没有副作用”,但这仍不是代理注入或影子请求。要做本机模拟,可在两个隔离的库存进程上用同一合成输入分别执行 POST /reserve,再分别查 GET /ledger;它只模拟两个业务副本会各自写账,不能据此声称观察到 Envoy 的异步镜像。错误路径还应分别检验“影子写入被隔离”与“错误地指向主账本导致重复写入”;只在完全隔离的测试账本里演示后者。

真正的代理实验须在隔离环境记录已生效的路由/镜像/fault 配置、代理出站请求、v1/v2 各自账本和主客户端退出码;分别测试正常镜像、影子不可用、注入 abort、应用提交后丢失响应。主链路的判据看主响应,影子副作用看隔离账本,故障位置由代理与应用同一请求 ID 的日志对齐。当前故障注入、真正的镜像和影子不可用对照均为 NOT_RUN;下表不填写假设结果。

待验收场景 主客户端结果 主账本 影子隔离账本 代理注入/镜像证据
正常镜像 待运行 待运行 待运行 待运行
影子不可用 待运行 待运行 待运行 待运行
注入 abort 待运行 待运行 待运行 待运行
应用提交后响应丢失 待运行 待运行 待运行 待运行

把镜像观察和故障注入分两次执行

以下命令在 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-16/$(date -u +%Y%m%dT%H%M%SZ)"; mkdir -p "$OUT"

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

mainline/16/mirror.yaml 只镜像 GET /quote,比例为 100%,主目标 v1、影子 v2。POST /reserve 落入不镜像的后备路由,避免把写入复制到另一份账本。比例只描述选择,不保证影子目标每次都收到请求;镜像仍是尽力而为。

1
2
3
4
5
6
7
8
9
10
11
k apply -f mainline/16/mirror.yaml
k exec deploy/sm-probe -c curl -- curl -fsS -H 'x-request-id: ch16-shadow' 'http://inventory:8080/quote?sku=paper' > "$OUT/primary.json"
k logs deploy/inventory-v1 -c app > "$OUT/primary.log"
k logs deploy/inventory-v2 -c app > "$OUT/shadow.log"
k apply -f mainline/16/abort.yaml
k exec deploy/sm-probe -c curl -- curl -sS -o /dev/null -w '%{http_code}\n' -H 'x-request-id: ch16-abort' -H 'x-lab-fault: yes' 'http://inventory:8080/quote?sku=paper' > "$OUT/abort-code.txt"
test "$(cat "$OUT/abort-code.txt")" = 503
k exec deploy/sm-probe -c curl -- curl -fsS -H 'x-request-id: ch16-control' 'http://inventory:8080/quote?sku=paper' > "$OUT/control.json"
k logs deploy/inventory-v1 -c app > "$OUT/after-abort.log"
k apply -f deploy/kubernetes/inventory-route.yaml
k exec deploy/sm-probe -c curl -- curl -H 'x-request-id: ch16-recovered' -fsS -H 'x-lab-fault: yes' 'http://inventory:8080/quote?sku=paper' > "$OUT/recovered.json"

镜像组要求主响应库存版本是 v1,同时 v2 日志出现 ch16-shadow;响应不能因影子结果变成 v2。abort 组要求源代理产生 503,而 v1 不应出现 ch16-abort 的 handler 记录;未带故障头的控制组和恢复组均成功。如果错误请求实际进入应用,要检查规则是否被更宽泛的路由抢先匹配。日志缺失本身不够,必须先证明同一采集窗口内正常组的应用日志完整。

abort.yaml 替换镜像配置,而不是叠加在同侧 timeout/retry 上;这样故障注入的交互限制不会被误判成重试实现故障。

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

练习与参考解答

  1. 请求经代理收到注入的 503,库存没有该请求 ID,能证明“服务端执行后丢失响应”吗?参考解答:不能。先核对代理注入点;如果请求根本没有转发到库存,就无法推出应用提交。要验证结果未知,应在应用写入后制造响应延迟,再检查客户端与账本。
  2. 主请求 200、影子服务返回 500,如何修改实验让影子操作可安全观察?参考解答:影子独立部署并隔离账本、外部写入与通知,给两条路径带可关联的请求 ID,各自读取服务端结果;不把影子 500 当主请求 500,也不把 200 当影子没有副作用的证明。

官方参考与系列导航

  • Istio VirtualService 参考:fault、mirror 及客户端侧 fault 与 timeout/retry 的组合限制。
  • Istio 流量镜像:影子请求在关键响应路径之外;示例模板是否可直接复制须以固定版本与实际渲染核验。

系列导航:15 限制与摘除 → 16 故障与镜像(本文)→ 17 入口网关。