前 13 篇建立的心智模型——istiod 是通用 xDS
生产者,Envoy/ztunnel/waypoint
是消费者,资源按类型(LDS/RDS/CDS/EDS/Address/…)订阅——不能直接套到
Linkerd 上。Linkerd 的控制面根本不实现
xDS,linkerd2-proxy(Rust
微代理)也不是 Envoy。把 Linkerd 理解成”另一个 xDS
实现”会在第一步就走错方向。
本篇只做一件事:用官方文档钉住 Linkerd 控制面的机制形状,与前 13 篇建立的 istiod/xDS 心智模型逐点对照。不判定两者优劣——那需要具体工作负载、组织规模与运维能力下的权衡,本篇不做无实测支撑的”谁更好”结论。
本文是「Istio 控制面内核」系列第 14 篇(共 16 篇)。→ 系列目录
版本锚定:Linkerd 官方文档(
linkerd.io/docs/reference/architecture/、linkerd2-proxy-api仓库说明),未钉具体 Linkerd 小版本号——本篇只谈架构层机制,不涉及版本相关的字段/flag 细节。Istio 侧对照仍锚定 1.30.3。
一、控制面组件:从”合并”到”拆分”的相反选择
Istio 自 1.5 起把 Pilot、Citadel(CA)、Galley、sidecar 注入 webhook 配置等能力合并进单一的 istiod 进程——discovery、CA、injection 的边界划分,都是同一个二进制内部的职责划分(见系列第 2 篇)。
Linkerd 走的是相反方向:官方架构文档把控制面明确拆成三个独立组件,各自是控制面命名空间里的独立 Deployment:
| 组件 | 职责 | 与 istiod 对照职责 |
|---|---|---|
| destination | 服务发现(端点+目标 TLS 身份)、策略下发、Service Profile(路由/重试/超时) | Pilot 的发现与翻译职责 |
| identity | 接受代理 CSR,签发 mTLS 证书,充当集群内 CA | Citadel/istiod CA 职责 |
| proxy-injector | Kubernetes 准入 webhook,Pod 创建时注入
linkerd-init 与 linkerd-proxy
容器 |
istiod 的注入 webhook 配置生成职责 |
linkerd2 仓库的构建说明把这三者列为
controller
下的独立子目录(controller/api/destination、controller/identity、controller/proxy-injector),对应到运行时是可以独立扩缩、独立故障的组件——destination
挂了不代表 identity
签证书的能力受影响,反之亦然。这与 istiod
把这些能力揉进一个进程(共享内存态、共享故障域)是两种不同的机制选择,各自的运维含义也不同:istiod
简化了部署拓扑,Linkerd
的拆分让单个组件的故障域更小,但多了组件间协作的复杂度。
官方 2026 年的一篇 linkerd-destination
深度拆解博客补充了内部细节:destination
部署实际是一个 Pod 里跑四个容器——Go 写的 destination
控制器(基于 client-go 的
Informer/Reflector,事件驱动而非轮询)、Service Profile 校验
webhook、独立的 policy 判定容器,以及一个反身注入的
linkerd-proxy(让控制面自身流量也被 mTLS
保护)。
flowchart TD
subgraph istiod_box ["istiod (single process)"]
disco["Discovery / Pilot"]
ca["CA (Citadel lineage)"]
inject["Injection webhook config"]
end
subgraph linkerd_box ["Linkerd control plane (three Deployments)"]
dest["linkerd-destination<br/>(destination + sp-validator + policy)"]
ident["linkerd-identity"]
inj["linkerd-proxy-injector"]
end
两种拓扑对故障隔离的含义不同:istiod 进程内的一个
goroutine panic 理论上可能影响同进程内其他职责(工程上有
recover 与观测兜底,但共享故障域是拓扑事实);Linkerd
的三组件即便共享同一控制面命名空间,也是独立的 Kubernetes
对象,一个组件的 OOMKilled
不会直接拖垮另外两个的进程内存状态。这不代表 Linkerd
整体可用性更高——组件间的 gRPC 依赖(例如 destination 需要
identity
已经工作)仍然存在耦合,只是耦合发生在网络调用层而不是进程内函数调用层。
二、API 模型:通用资源类型 vs 代理专用 gRPC 接口
这是与本系列前 13 篇联系最紧密的一点。xDS 从设计上是一套与具体实现解耦的协议:Envoy 文档反复强调 xDS 是通用的资源类型(Listener/Route/Cluster/Endpoint/Secret……),理论上任何实现了该协议的数据面都可以消费——这也是本系列反复出现的”资源类型 + 订阅 + ACK/NACK”心智模型的来源。
Linkerd 的控制面 API 定义在独立仓库
linkerd2-proxy-api 里,官方说明写得很直接:
“This repo contains the gRPC bindings that the Linkerd Proxy uses to communicate with the Linkerd control plane.”
这套
API(destination、identity、inbound、tap
等服务)是为 linkerd2-proxy
量身定制的,不追求成为通用协议标准。以
destination
服务为例,语义是”给定一个目标,告诉我协议、是否负载均衡、每个端点的
mTLS
身份、遥测标签”——这是一个按具体目标查询、随后持续推送更新的模型,而不是
xDS
那种”声明我订阅某个类型的哪些资源名,服务端按类型推全量/差量”的模型。两者都是”watch
式长连接 +
服务端主动推送”,但资源寻址的方式不同:xDS
按”类型 + 资源名集合”寻址,Linkerd destination API
按”目标服务名”逐个查询。
flowchart LR
subgraph istio ["Istio: type-based subscription"]
envoy["Envoy sidecar"] -->|"subscribe CDS/EDS/LDS/RDS by type+names"| istiod["istiod"]
istiod -->|"DiscoveryResponse per type"| envoy
end
subgraph linkerd ["Linkerd: per-target query stream"]
proxy["linkerd2-proxy"] -->|"destination.Get(service-name)"| destination["destination controller"]
destination -->|"streamed updates for that target"| proxy
end
工程含义:xDS 的通用资源类型体系是它能被
Envoy、ztunnel、waypoint、乃至第三方数据面共同消费的前提(本系列第
13 篇讨论的多客户端场景);Linkerd 的 API 与
linkerd2-proxy
是一对一设计,不追求多数据面兼容——这是两条控制面协议设计哲学的根本分叉,不是功能缺失。
三、身份签发:CSR 直连 vs SDS 资源推送
两者都用 mTLS,且都把身份和 Kubernetes ServiceAccount 绑定,但签发路径的机制不同:
- Istio:证书材料通过 SDS(Secret Discovery Service)以 xDS 资源的形式推送给 Envoy——见 Envoy 第 11 篇。SDS 是通用 xDS 资源类型体系里的一员,理论上任何 xDS 客户端都能消费。
- Linkerd:官方架构文档描述为——代理启动时向
identity 服务发送 CSR,identity 校验后签发绑定到该 Pod
ServiceAccount 的证书。这是一条专用的 identity gRPC
API
调用,不是通用资源类型推送。官方文档同时说明:Linkerd
自动轮转工作负载证书(短期证书由代理自主发起续期,无需重启);但
identity
签发者证书与信任锚的轮转是两件更重的操作——签发者证书轮转通常无需重启(代理侦测到
linkerd-identity-issuerSecret 更新后自动切换),信任锚轮转则必须重启控制面与全部代理(因为要求双方在过渡期同时信任新旧锚点)。
这与 Istio 侧的分层(Envoy 第 11 篇:SDS 未就绪 vs
取证失败两种状态机)是不同粒度的问题:Istio
的证书失败模式内建在 xDS 的通用状态机里;Linkerd
的证书失败模式绑定在 identity 这一个专用 API 及其 Secret
侦测逻辑上,出问题时的排障入口是
linkerd-identity
组件日志,而不是某种通用协议层面的 ACK/NACK。
四、订阅粒度对照表
| 维度 | Istio / istiod | Linkerd / destination |
|---|---|---|
| 协议基础 | xDS(gRPC,多资源类型,v3 API,非 Linkerd 专属) | linkerd2-proxy-api(gRPC,专用于
linkerd2-proxy) |
| 资源寻址 | 类型 + 资源名集合(可用 SotW 全量或 Incremental 差量,见系列第 10 篇) | 按目标服务名逐个
Get(),流式接收该目标的后续更新 |
| 控制面进程拓扑 | 单一 istiod 进程(discovery+CA+injection 配置) | destination / identity / proxy-injector 三个独立 Deployment |
| L7 路由信息来源 | VirtualService/HTTPRoute → RDS(系列第 6 篇) | Service Profile → destination 返回的路由/重试/超时字段 |
| 身份签发 | SDS 资源推送(通用协议内) | identity 专用 gRPC CSR 接口 |
| 数据面引擎 | Envoy(C++,也被 waypoint 复用,见第 13 篇) | linkerd2-proxy(Rust,仅服务于
Linkerd) |
五、协议通用性的代价:ACK/NACK 语义不是免费的
本系列第 11 篇花了整整一篇讲 istiod 如何处理 NACK、如何用
PushOrder 降低黑洞概率——这套复杂度的根源是 xDS
作为通用协议必须应对”任意符合规范的客户端,可能以任意方式拒绝任意一种资源”。Linkerd
的专用 API 不需要一般化的 ACK/NACK
状态机:destination.Get()
是一个按目标查询的流,linkerd2-proxy
要么能建立这个流并持续接收更新,要么连接失败——错误处理落在标准
gRPC 状态码与重连逻辑上,而不是一套独立于 gRPC
之上的”资源级别接受/拒绝”协议层。
这不是”Linkerd 更简单所以更好”的论断,而是一个可验证的机制事实:协议通用性(一个协议服务多种数据面)与协议内建的资源级协商复杂度(ACK/NACK/nonce/version)是同一个设计选择的两面。Linkerd 放弃前者,换掉了后者的复杂度;Istio 要保留”同一协议可插拔多种数据面”的能力(第 13 篇 sidecar/waypoint 共享同一套 xDS 栈),就必须承担 ACK/NACK 状态机的工程成本。
六、不做的判断
本篇刻意不回答”Istio 还是 Linkerd 更好”。两套机制解决的是同一个问题(服务网格控制面:发现、路由、身份),选择了不同的协议通用性与组件拓扑权衡:Istio 押注 xDS 作为可被多数据面复用的通用协议(第 13 篇已经看到 sidecar/waypoint 两种 Envoy 客户端共享同一协议栈的收益);Linkerd 押注专用 API 与专用轻量代理的强耦合换取更小的 Rust 数据面攻击面与更简单的单一实现路径。两条路径各有对应的运维模型,评判需要具体组织的 Envoy 生态依赖程度、Rust/C++ 运维能力、以及是否需要 waypoint 那种”复用同一数据面引擎服务多种身份”的能力——这些都是本篇未展开、需要读者按自身场景判断的变量。
七、参考资料
规范 / 官方文档(A)
- Linkerd,
Architecture,
linkerd.io/docs/reference/architecture/(destination/identity/proxy-injector 职责划分)。 - Linkerd, Automatically Rotating Control Plane TLS
Credentials,
linkerd.io/docs/tasks/automatically-rotating-control-plane-tls-credentials/(工作负载证书自动轮转 vs 签发者/信任锚轮转的机制差异)。 linkerd/linkerd2-proxy-api仓库 README(destination/identity/inbound/tap gRPC API 说明)。linkerd/linkerd2仓库BUILD.md(控制面组件拓扑)。
博客 / 维护者文章(B)
- Linkerd 官方博客,Deep Dive: How linkerd-destination works in the Linkerd Service Mesh(2026,destination Pod 内部容器分工)。
站内对照
实验台账
- 本篇为官方文档机制对照;无本机 Linkerd 环境实测,不写吞吐/延迟/资源占用对比数字。
八、小结
- Linkerd 不是另一个 xDS 实现:它的控制面
API 是为
linkerd2-proxy量身定制的专用 gRPC 接口,不是通用资源协议。 - 组件拓扑相反:Istio 把发现/CA/注入合并进 istiod 单进程;Linkerd 拆成 destination/identity/proxy-injector 三个独立组件,故障域更细但协作路径更多。
- 资源寻址方式不同:xDS 按类型+资源名订阅;Linkerd destination 按目标服务名逐个查询+流式更新。
- 协议通用性与 ACK/NACK 复杂度是同一枚硬币的两面:Linkerd 放弃通用协议换掉了资源级协商状态机;Istio 保留通用协议就要承担这套状态机的工程成本。
- 身份签发路径不同:Istio 走 SDS 通用资源推送;Linkerd 走 identity 专用 CSR 接口,且工作负载证书与签发者/信任锚证书的轮转机制分层不同。
- 本篇不判定优劣——协议通用性与专用轻量化是两种合法的工程取舍,选择依赖具体组织约束。
→ 系列目录 · 上一篇:Ambient:ztunnel / waypoint · 下一篇:生产排障
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Istio 控制面】控制面全景:从 CRD 到 xDS 的翻译与推送内核
定位 Istio 控制面内核相对 Envoy 消费侧、Service Mesh 税文与 Gateway API 资源模型的缺口;给出五条坐标系、16 篇地图与本系列明确不写的范围,钉住 istiod 1.30.3 为主线。
【Istio 控制面】istiod 进程模型:discovery、config、CA 与注入的边界
拆解 pilot-discovery 二进制内部的组件化启动顺序:CA 为何先于 Discovery 初始化、注入 Webhook 与配置校验为何是独立 HTTPS 服务、以及哪些组件真正位于 xDS 推送热路径上。
Istio / 网格控制面内核:从 CRD 到 xDS
补齐 Envoy 数据面 xDS 消费侧与 Mesh 选型叙事之间的控制面内核:istiod 推送图、CRD/HTTPRoute 翻译、Ambient 多客户端、NACK/warming 责任与排障对齐。
【Istio 控制面】代理身份与订阅:sidecar、ztunnel、waypoint、Gateway 谁在订阅什么
拆解 istiod 如何从节点 ID 与 mTLS/JWT 得到一个 model.Proxy 身份,以及 NodeType 如何决定该连接会订阅哪一套 xDS 资源类型:sidecar/Router 走标准 LDS/RDS/CDS/EDS,ztunnel 走自定义 Address/Authorization,waypoint 是 Envoy 但推送触发条件不同。