服务网格 00:一次服务调用需要哪些基础条件
从一个可定位的请求开始
loadgen 请求 frontend 查询合成商品 paper,后者调用 orders,orders 再读取 inventory-v1 和 external-stub。要让这次调用成功,客户端至少需要知道地址、能解析目标、连接正确的端口、找到可用后端,并获得与请求对应的响应。先把代理和控制面排除在外,后续才能判断新增的网格层究竟改变了哪一段。
本文要求读者能区分 HTTP 响应与 TCP 连接失败,并认识 Kubernetes 的 Pod 和 Service。实验代码使用 Go 标准库;本篇只有本机进程实测,尚无 Kubernetes、Envoy 或 Istio 的运行证据。
图中 orders 是 frontend 必须连到的上游:域名不正确,连接甚至不能发起;目标是空集合,应用没有可以选择的地址;端口不对,即使 IP 正确也可能被拒绝。图里的地址都是本机监听地址,不是 Kubernetes Service 的 ClusterIP。
把“找到服务”拆成四次判断
- 名称解析:
orders.invalid无法转换为可连接的地址;程序不会因为另一个进程叫orders就自动找到它。Kubernetes 内部通常通过 Service DNS 名称提供稳定入口,但 DNS 回答并不包含“请求一定能成功”的保证。 - 端口与连接:
127.0.0.1是明确的目标地址,连接:1时没有监听者,TCP 建连遭拒。端口错误与域名解析失败发生在不同阶段;此时还没有订单业务响应。 - 后端候选:
frontend的上游地址留空时,本例直接返回 503,并没有进行 DNS 或 TCP 尝试。在 Kubernetes 中,Service 与 EndpointSlice 用来表达入口和端点;“Service 存在但没有可用后端”必须在实际集群中另验,不能拿这个应用分支当作其实现或错误码。 - 响应与副作用:成功建立连接也只说明能和某个监听者通信。必须核对请求标识、业务输入、后端版本和结果;读请求后还要查看合成账本,排除意外写入。
这些判断有先后关系,却不是所有失败都会暴露在同一层。例如调用者可能拿到 HTTP 502,但它不说明是 DNS、TCP、上游响应解析还是中间代理产生:必须结合谁返回 502及该处日志判断。本实验的 502 来自示例 frontend,不是 Envoy。
运行同一组进程并保留失败
在仓库的 examples/service-mesh/ 目录执行,需 Go 1.25.1、Node 22 与 curl。脚本只启动本机 127.0.0.1 上的合成服务,不连接 Kubernetes context,结束时只终止自己启动的进程:
1 | |
若 Go 已在 PATH,可运行 bash scripts/run-00.sh。脚本每次在 evidence/00/<UTC 时间>-<PID>/ 新建证据目录;其中 *.command 是完整启动和请求命令,*.exit 是原始退出码,*.stdout/*.stderr 是响应及每跳调用日志,assertions.txt 保存断言结论,environment.txt 保存环境。完整工程说明见同仓库的 examples/service-mesh/README.md,源码见 examples/service-mesh/apps/mesh-demo/main.go。
2026-10-01 的一次进程实验记录在 examples/service-mesh/evidence/00/20261001T121421Z-2487240/。断言结果如下;数字是该次实验客户端的退出码,不是 HTTP 状态:
| 请求场景 | 客户端退出码 | frontend 响应 | 可区分的本地原因 |
|---|---|---|---|
sm-00-success |
0 | 200,inventory_version=v1、external=external-stub |
orders 的两个下游均回答,结果携带同一 request_id |
sm-00-wrong_domain |
1 | 502 | lookup orders.invalid … no such host,尚未建立 TCP 连接 |
sm-00-wrong_port |
1 | 502 | 127.0.0.1:1: connect: connection refused,地址存在但端口无监听者 |
sm-00-empty_endpoints |
1 | 503 | no endpoints configured for orders,frontend 的配置分支没有上游候选 |
inventory-v1 的 GET /ledger 在这次只读实验返回 []。一个空账本只能约束当前运行、当前这个内存实例;它不能证明别的实例、别的时间没有业务副作用。检查 sm-00-success 在四个服务 stderr 中的记录,可以将调用者输入、每跳处理和最后响应串起来;这不是对代理路径的证明。
如果改成实际 Kubernetes Service,需要另外保存 Service、EndpointSlice、DNS 查询、Pod/节点网络配置、实际请求的后端记录,以及断言失败时的退出码。当前宿主未安装容器运行时、kind 或 kubectl,也没有现成集群:空 EndpointSlice、CNI 和 Service 数据面路径均为 NOT_RUN。复跑约束和步骤见 writing-plans/service-mesh/research/00.md。不要把“静态 YAML 可解析”或本站 Hexo 构建当作这一步已完成。
反例与适用范围
如果把 frontend 的 orders URL 从正确端口换成 127.0.0.1:1,服务名即使写成正确名字,TCP 仍不能凭空找到监听端口。反过来,即使端口可连,orders 返回的请求标识与输入不匹配时,本例会拒绝该上游结果;“网络通了”不等于“完成了这次调用”。本例没有 Service 的 DNS、EndpointSlice 收敛、代理注入或 xDS,因此无法推论那些组件的默认行为。下一篇用同一进程链讨论治理应放在 SDK、网关、代理还是业务代码。
练习与参考解答
- 某请求得到本例的 502,frontend 日志显示
lookup orders.invalid … no such host。能否判定 inventory 已写入?参考解答:不能。frontend 尚未连到 orders,不能从这个 502 推导 inventory 的任何操作;应核对同一请求 ID 的下游记录与合成账本。更不能由此推导其他并发请求的结果。 - 把 frontend 的 orders 地址改成
http://127.0.0.1:1,或清空 orders 地址,各应在哪一步失败?参考解答:前者解析/地址输入阶段已得到 loopback IP,TCP 连接被拒,示例 frontend 生成 502;后者应用发现候选为空,直接生成 503,不发生对 orders 的 DNS 或连接。若把这个反例迁移到 Kubernetes,必须另外查实际 Service、EndpointSlice 与运行时网络路径,不能假设其 HTTP 码与此处相同。
官方资料与系列导航
- Kubernetes Service 与 EndpointSlice:区分入口对象与端点集合;本次未验证 Kubernetes 数据面。
- Kubernetes DNS for Services and Pods:Service 名称解析的语境。
- Go
net/http:本例客户端连接与请求上下文使用的标准库。
系列导航:00 一次服务调用需要哪些基础条件(本文)→ 01 SDK、网关与服务网格分别处理什么。

