服务网格 E01:Linkerd 的代理、身份与策略
库存服务凭什么相信 orders
累计工程里 loadgen → frontend → orders → inventory 是只读调用;orders 在库存成功后才调用 external-stub。如果另一个服务也能直接请求库存,单看 inventory-v1 返回的报价,分辨不出调用来源。Linkerd 能在两端工作负载都正确入网、策略覆盖入站连接时,把“谁连进来”变成由身份凭据验证的条件;它不能凭 orders 这个 HTTP 字符串证明对端身份。
图的左边是出站代理,右边的入站代理验证来源并执行策略。Kubernetes API 和 Linkerd 控制面负责提供身份/目的地/策略信息,不是每个报价请求必经的同步授权网关。以 Linkerd 2.18 文档快照为基线(2026-10-03 核对);这里没有安装该版本,不能把文档中的默认值当作当前环境的配置。
两个代理,不是两个应用里的 TLS 库
Linkerd 数据面使用 Rust 编写的轻量 linkerd2-proxy,并非 Envoy 换一个名称。控制面的 destination 提供目的地及端点变化信息,identity 为合格的工作负载供应短期凭据,policy 将入站服务的授权条件交给代理;代理对入站连接验证并决策。Envoy 则是通用的可扩展代理,有丰富的 listener/filter/xDS 资源模型;第 03 篇的独立 Envoy 模板没有自动变成 Linkerd。这是实现职责差异,不是延迟或内存排名,尤其不能用两份未经功能对齐的请求曲线比较性能。
自动 mTLS 有边界:两端是否注入、协议探测是否适用、控制面是否信任各自身份、工作负载是否绕过代理,都影响加密与身份验证。目标端入站拒绝应检查认证出的服务账户身份与目标端 Server/AuthorizationPolicy 的作用范围;若某条路径从网格外直达未受保护端口,则“已配置策略”不等于“所有路径受保护”。证书失效、新代理无法取得身份以及已有连接仍在使用旧凭据也应分开观察,不能由短暂控制面故障推出所有流量立即失败(参见 29 篇)。
用同一个报价区分允许与拒绝
两次输入只改变经代理认证出的工作负载身份,不靠请求头伪造来源:
1 | |
| 观察点 | 允许路径 | 拒绝路径 |
|---|---|---|
| 身份 | orders 的服务账户 | 不在授权集合内的账户 |
| 执行位置 | 目标代理放行后 inventory 处理 | 目标代理拦截,inventory 不处理 |
| 判据 | 请求 ID 同时出现在代理和库存 | 客户端失败且库存无对应 ID |
实验在明确的隔离 Kubernetes context 中为 orders、inventory-v1 注入代理,分别固定 namespace、service account 和服务端口。先以源为 orders 的 GET /quote?sku=paper 取得版本与请求 ID;再从另一个独立服务账户发出相同请求,预期被目标代理拒绝,库存应用无对应请求 ID。撤掉策略或停止注入会改变安全前提,不能把“也返回 200”当作 Linkerd 放行。核验证据至少包括部署版本和镜像 digest、Server/AuthorizationPolicy、代理接收的策略状态、两边身份公开元数据、两条客户端原始响应/退出码、目标代理日志与库存处理日志;若拒绝请求在库存日志出现,应判实验失败。
本轮脚本在检查 kubectl 时退出 2,未创建 Kubernetes 资源。因此 mTLS、授权和绕过对照仍为 NOT_RUN;完整配置与复跑命令见下节,静态检查不替代代理实跑。
把服务账户约束写成资源
完整材料放在 examples/service-mesh/electives/E01/。这里故意用两个独立的 curl 客户端工作负载表示 orders 与未知调用方,库存使用 Python 标准库 HTTPServer。它们都是合成只读服务,不调用支付或外部系统,也不复用累计工程中的订单编排。应用对所有 GET 路径都返回 200,因而后续 403 可以定位到代理层。
workloads.yaml 创建 sm-e01 命名空间、两个 ServiceAccount、三个 Deployment、库存 Service 和内嵌 Python 的 ConfigMap。命名空间的 linkerd.io/inject: enabled 让注入器为新 Pod 加代理;已有 Pod 不会因为随后添加注解而自动重建。库存端口在 Pod 中命名为 http,Service 把 8080 映射到该端口。
身份和端口的连接由以下完整策略建立,内容对应仓库的 policy.yaml:
1 | |
2.18 授权资源文档规定,Server 选择同命名空间的 Pod 及声明过的端口;匹配它的流量默认需要授权。这里使用身份引用,避免手工拼接信任域字符串。orders 不是客户端随意填写的 HTTP 请求头,而是经过 mesh TLS 验证的服务账户身份。授权范围是整个库存 HTTP 端口,本例不声称实现逐路径授权;需要逐路径限制时才引入 HTTPRoute 并把 AuthorizationPolicy 指向它。
[PATTERN] 将授权条件拆成“受保护对象、可接受身份、二者关系”,能分别检查 selector、证书身份与授权引用。一个 YAML 被 API 接收,仅证明声明合法,不证明流量经过这个对象。
执行允许、拒绝和恢复
先在独立测试集群安装与 2.18 API 对应的 Linkerd 发行包及 CLI,按发行包的 Kubernetes 支持范围核对集群。商业稳定发行与 edge 包的版本命名不相同,不能把文档版本直接当作一个可下载的二进制标签。本例不自动修改已有集群的控制面。
1 | |
脚本要求显式 context,先创建工作负载和策略,再等待三个 Deployment。它把集群版本、Pod 的实际 imageID、资源 YAML 和双方日志写入输出目录;镜像标签固定为 Python 3.13.2 Alpine、curl 8.12.1,实际 digest 仍需看该次 pods.json,标签不等于不可变镜像摘要。
允许和拒绝的核心调用如下,ID 必须按每轮实验更换:
1 | |
脚本要求 orders 返回 200、unknown 返回 403,并且库存日志只出现允许 ID。DNS 失败、连接超时或两边均失败都不能通过。运行时还应执行 linkerd --context "$LAB_CONTEXT" -n sm-e01 check --proxy,确认两端代理有可用身份;这与 HTTP 判据共同限定 mTLS 路径,不能仅凭一个 403 推断证书验证过程。
故障注入可以删除唯一的 AuthorizationPolicy,保留 Server。此时 orders 也应被拒绝,因为目标端口仍被 Server 选中。恢复时重新 apply 策略,并等待新的请求恢复 200:
1 | |
这两个命令之间应重放允许请求并保存失败,恢复后再重放一次保存成功。不要删除 Server 来模拟同一故障:删除它会重新落入默认入站策略,可能放宽访问。实验结束只清理专用命名空间:kubectl --context "$LAB_CONTEXT" delete namespace sm-e01。
常见失败的定位顺序
如果所有来源都能访问,先检查 inventory 的容器列表有无 linkerd-proxy,再检查 Server selector 和端口名;没有命中的策略谈不上执行授权。若所有来源都被拒绝,检查 orders Pod 的 serviceAccountName、代理身份状态以及 MeshTLSAuthentication 所在命名空间。若允许请求成功但库存日志缺失,先检查请求是否命中另一个副本,收集该 Deployment 全部 Pod 日志,不能据单个 Pod 的空日志宣布安全。
[PATTERN] 授权失败必须和连接失败分开。使用一个确定允许的对照请求,能排除后端停机、DNS 故障等共同原因;再用被拒 ID 未进入应用日志证明拒绝发生在业务处理之前。
| 模式 | 使用位置 | 误用后果 |
|---|---|---|
| 对象、身份、关系分离 | Server、MeshTLSAuthentication、AuthorizationPolicy | 标签命中被误当成身份可信 |
| 正负请求共享业务输入 | 只改变服务账户 | 无法区分授权与应用逻辑 |
| 保留保护对象再撤授权 | 故障和恢复实验 | 删除 Server 导致保护边界一并消失 |
练习与参考解答
- 画出来源不是 orders 时,请求最远能到哪里。参考解答:若两端入网且入站策略精确匹配目标端口,来源代理发起连接,目标代理验证身份并拒绝;库存 handler 不执行,
external-stub更不应被库存调用。以入站代理日志与库存请求 ID 验证,而非仅看客户端错误码。 - 把来源服务账户换成同名但不同 namespace 的账户,还应允许吗?参考解答:不应仅按账户短名判断;检查授权身份是否包含 namespace/信任边界,实际证书身份、策略 selector 与认证结果必须一致。若放行,审查是否匹配了更宽的授权或流量绕过目标代理。
资料与系列导航
Linkerd 2.18 架构、自动 mTLS、Server policy;源码入口 linkerd2-proxy 固定查询提交(不是 2.18 发布标签,不作该版本的内部行为断言)。核验边界见 writing-plans/service-mesh/research/E01.md。
系列导航:19 工作负载身份 → E01 Linkerd(本文) → E02 Cilium。
