服务网格 02:Service 地址怎样到达 Pod
orders 这个名字解析成功后,请求到了哪里
前两篇已经建立了本地进程链:loadgen → frontend → orders → inventory-v1 / inventory-v2,并让 orders 调用 external-stub。第 00 篇中的 orders.invalid 在解析阶段失败;本篇换一个问题:即使 orders 的 Service 名称能够解析,哪些条件决定某次 TCP 连接落到哪个 Pod?读者需能区分 DNS 查询、TCP 连接、HTTP 请求和 Pod readiness。
图左侧是调用者看到的稳定入口,右侧是会变化的就绪后端集合。两侧不能画等号:对普通 ClusterIP Service,DNS 回答的是入口地址而不是“此次请求的 Pod IP”;实际端点的选择依赖集群的数据面实现及当时的端点状态。Istiod 和 Envoy 都不在这里的无网格数据路径中。
从 DNS 到后端的四个环节
以 frontend 在同一 namespace 访问 http://orders:8080/quote 为例,以下是应该逐层检查的对象,而非在没有集群时声称实际发生的抓包结果。
- DNS 名称:Kubernetes 的 DNS 约定会使同一 namespace 内的短名
orders指向相应 Service;跨 namespace 应用显式使用完整名称。查到的 ClusterIP 不等于某个 Pod IP;使用无头 Service 等不同类型时,DNS 的答案规则又不同。诊断时先保存实际查询名、搜索域与响应,而不是先猜测后端。 - Service 声明:
spec.ports[*].port是入口端口,targetPort是传到后端容器的目标端口,可为数字或具名端口。Service 选择器与标签必须匹配,且后端状态须允许接收新流量;DNS 成功只说明名称层面有答案,不证明端口与后端就绪。 - EndpointSlice 与数据面:EndpointSlice 记录地址、端口和条件等信息,Service 的网络实现读取这些信息,将访问稳定入口的连接导向某个后端。实现可能使用 kube-proxy 的具体模式,也可能由集群中的其他 Service 数据面完成;不要把某一种 iptables 链的细节写成所有集群的必经路径。CNI 的职责主要是为 Pod 建立网络连接能力,不能把 CNI 和 Service 负载均衡、DNS 视作同一个组件。
- 已经建立的连接:一次连接选中了某个后端后,在 HTTP/1.1 keep-alive 或 HTTP/2 多路复用下,多次请求可能沿旧连接到相同实例。EndpointSlice 更新与数据面收敛会影响后续选择,却不会自动把所有现存连接的每个请求都切去新实例;连接被关闭、重连以及具体实现决定实际转移时机。
对应的逻辑路径是 名称 → Service 入口 → Service 数据面选择后端 → Pod 网络 → orders 应用。调用方仍要确认 X-Request-ID 是否被传给应用、实际响应里的后端版本,以及有无副作用;只在 kubectl get svc 看到正确 ClusterIP,证据链还未到请求结果。
为什么扩容后“权重”不一定立刻变
设旧版 Pod 已承接一条长连接,扩容新增 inventory-v2 并让它就绪。即使新的 EndpointSlice 已列出 v2,仍需要相关节点的数据面拿到更新;旧连接内的请求可能持续到 v1。若测试只发送大量并发请求而每次都新建连接,得到的分布与复用一条长连接的分布可能不同。这里没有承诺某个百分比或收敛秒数——应用连接池、就绪变化和网络实现都必须纳入测量条件。
反例是“Service 有 ClusterIP,EndpointSlice 却没有可用端点”:DNS 查询能成功,连接仍可能无法到达业务进程。第 00 篇的 -orders-url '' 则是应用没有配置任何上游,不是这个 Kubernetes 反例;它返回的 503 也不能冒充某个 Service 数据面的状态码。
创建无网格基线,再移除全部端点
工程的 scripts/render-kubernetes.mjs 生成五个 Deployment:frontend、orders、external-stub、inventory-v1/v2。容器和 Service 均用 8080;inventory 聚合 Service 选择 app=inventory,两个版本由 version=v1/v2 区分。下面从 examples/service-mesh/ 执行,要求 kind 0.30.0、kubectl、Node 和可用的容器运行时;setup 会拒绝覆盖同名集群。
1 | |
数据路径为 frontend 内的 loadgen → frontend Service → orders Service → inventory Service → 一个库存 Pod;DNS 查询则由 frontend 容器内的 -role dns -dns-host inventory 单独完成。脚本先保存 Pod、Service 和 EndpointSlice,然后把两个库存 Deployment 都缩到 0,等待 Pod 删除。
空端点阶段 DNS 查询应仍返回 inventory 的入口地址,而业务调用必须非零退出。脚本再恢复两个 Deployment 各 1 副本,等待 available 并轮询业务成功。这样可以排除“入口名称消失”的解释,直接观察稳定名称与变化端点的边界。失败时 EXIT 清理也会尝试恢复副本数,清理日志不能代替恢复后的业务探测。
evidence/02/<时间>-<PID>/ 包含 dns、dns-empty、no-endpoints、recovered 的命令、输出和退出码,以及三个时刻的资源 JSON。当前集群运行仍为 NOT_RUN;这些完整命令是复现材料,不能从语法检查推导 Service 转发成功。
[PATTERN] 稳定名称与可变成员集合的间接层:Service 提供入口,EndpointSlice 描述成员,调用者不硬编码 Pod IP。
| 原则 | 反例约束 |
|---|---|
| 名称存在不代表可用 | 空端点时 DNS 成功、请求失败 |
| 声明与观测分离 | 保存实际 EndpointSlice,而非只读 Deployment |
| 故障可恢复 | 副本恢复后重新验证业务响应 |
练习与参考解答
orders的 DNS 回答指向 ClusterIP,kubectl get endpointslices列出两个就绪 Pod,却只观察到一个版本。应补哪些信息?参考解答:确认实际请求发起者、查询名与 DNS 响应、Service 的入口/目标端口及选择器、端点在请求时刻的条件、发起者是否复用同一 TCP/HTTP2 连接、相关节点数据面是否同步,以及每个请求 ID 的后端响应。两个端点存在不意味着按 HTTP 请求逐次轮换。- 构造“DNS 成功但调用失败”的对照,同时避免把进程模拟当作集群结果。参考解答:在隔离集群内先采集正确 Service 和就绪 EndpointSlice,发送带标识请求保存成功响应;随后仅在实验 namespace 把后端缩至无就绪端点,保存新 EndpointSlice、原 DNS 响应、客户端的实际错误与非零失败断言。旧连接需要单独处理,不能拿其结果代表新连接;当前环境未运行这个对照,故不得填写状态码或比例。
官方资料与系列导航
- Kubernetes Service:稳定入口、端口与选择器的语义。
- Kubernetes EndpointSlice:端点集合及其条件。
- Kubernetes DNS for Services and Pods:Service DNS 规则与例外。
- Kubernetes Cluster Networking:Pod 网络和 Service 网络的职责区分。以上动态文档只作概念入口,待选定集群版本后需核对对应版本。
系列导航:00 一次服务调用需要哪些基础条件 → 01 SDK、网关与服务网格分别处理什么 → 02 Service 地址怎样到达 Pod(本文)→ 03 Envoy 怎样转发请求。

