土法炼钢兴趣小组的算法知识备份

【Istio 控制面】代理身份与订阅:sidecar、ztunnel、waypoint、Gateway 谁在订阅什么

文章导航

分类入口
networkkubernetes
标签入口
#istio#xds#spiffe#ztunnel#waypoint#ambient#node-type#agentgateway

目录

前两篇默认了一件事:「一个代理连上 istiod,订阅一套 xDS 资源」。这个默认在 sidecar 模式下成立,但 Istio 1.30.3 的 pilot/pkg/xds 至少要同时服务四类连接:per-pod 的 sidecar、per-node 的 ztunnel(Rust 实现)、per-namespace 的 waypoint(Envoy 实现)、以及作为独立 L4/L7 路由器的 Gateway。它们连上同一个 DiscoveryServer,但订阅的资源类型、承载的身份语义、甚至证书申请方式都不同。把它们当成「同一种 Envoy,只是部署位置不同」去排障,会在 Ambient 环境下直接卡住——因为 ztunnel 根本不理解 Listener/Cluster 这两个类型。本文钉住:身份从哪来、身份如何决定订阅范围、四类客户端具体订阅什么。

本文是「Istio / 网格控制面内核」系列第 3 篇(共 16 篇)。→ 系列目录

上接 第 2 篇 · istiod 进程模型;下接 第 4 篇 · 推送图

版本锚定:Istio 1.30.3。源码:pkg/model/proxy.goNodeType/Proxy)、pilot/pkg/model/context.goParseServiceNodeWithMetadata)、pilot/pkg/bootstrap/discovery.goInitGenerators)、pilot/pkg/xds/workload.goarchitecture/ambient/ztunnel.md 官方设计文档。


一、身份从哪来:节点 ID、元数据、验证后的 SPIFFE 身份

一次 ADS 连接建立时,istiod 从 gRPC 请求里的 core.Node 解析出两样东西:一个四段式节点 ID,一段结构化元数据。ParseServiceNodeWithMetadatapilot/pkg/model/context.go,1.30.3)按 ~ 切分节点 ID:

// pilot/pkg/model/context.go(Istio 1.30.3,节选)
const serviceNodeSeparator = "~"

func ParseServiceNodeWithMetadata(nodeID string, metadata *NodeMetadata) (*Proxy, error) {
    parts := strings.Split(nodeID, serviceNodeSeparator)
    // parts[0] = NodeType, parts[1] = IP(元数据里没有时的兜底),
    // parts[2] = ID, parts[3] = DNSDomain
    if !pm.IsApplicationNodeType(NodeType(parts[0])) {
        return out, fmt.Errorf("invalid node type ...")
    }
    out.Type = NodeType(parts[0])
    ...
}

一个典型 sidecar 的节点 ID 长这样:sidecar~10.42.1.7~reviews-v1-6c77b-x9k2q.default~default.svc.cluster.local——四段分别对应类型、IP、工作负载实例 ID、DNS 域。这个格式本身就是身份的最小单元:istiod 不relies于连接来源 IP 判断身份,而是要求客户端在协议层显式声明「我是谁、什么类型」。

除了节点 ID,NodeMetadatapkg/model/proxy.go)携带更丰富的信息:NamespaceServiceAccountClusterIDNetworkIstioVersionGenerator(自定义生成器插槽)等。这些字段共同构成 model.Proxy 结构体:

// pkg/model/proxy.go(Istio 1.30.3,节选)
type Proxy struct {
    Type            NodeType
    IPAddresses     []string
    ID              string
    Metadata        *NodeMetadata
    SidecarScope    *SidecarScope
    VerifiedIdentity *spiffe.Identity
    XdsResourceGenerator XdsResourceGenerator
    WatchedResources map[string]*WatchedResource
    ...
}

VerifiedIdentity *spiffe.Identity 是身份链上最关键的一环:节点 ID 和 NodeMetadata 都是客户端自己声明的,只有在 mTLS 或 JWT 认证通过之后pilot/pkg/xds/ads.go 里注册的 security.Authenticator 链,默认包含 authenticate.ClientCertAuthenticator),istiod 才会把证书里的 SPIFFE URI(spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>)写进 VerifiedIdentity。后续任何依赖身份做授权判断的逻辑(例如该连接能否请求某个命名空间的资源),都应该以 VerifiedIdentity 为准,而不是节点 ID 里自称的 Namespace——节点 ID 和普通 NodeMetadata 字段在协议层是未经验证的自述,只有过了认证器的部分才可信。


二、身份如何决定订阅范围:先看资源类型,再看代理类型

一个容易想当然的假设是「model.Proxy.Type 直接决定这个连接能订阅哪些 xDS 类型」。源码里更准确的说法是:订阅范围首先由客户端请求的资源类型 URL 决定,Type 主要影响这一类型内部该怎么生成、要不要推送InitGeneratorspilot/pkg/bootstrap/discovery.go,1.30.3)把类型 URL 和生成器一一注册:

// pilot/pkg/bootstrap/discovery.go(Istio 1.30.3,节选)
generators[v3.ClusterType]  = &xds.CdsGenerator{ConfigGenerator: cg}
generators[v3.ListenerType] = &xds.LdsGenerator{ConfigGenerator: cg}
generators[v3.RouteType]    = &xds.RdsGenerator{ConfigGenerator: cg}
generators[v3.EndpointType] = edsGen
generators[v3.SecretType]   = xds.NewSecretGen(...)
generators[v3.ExtensionConfigurationType] = ecdsGen        // ECDS
generators[v3.NameTableType] = &xds.NdsGenerator{...}       // NDS,DNS 名表
generators[v3.ProxyConfigType] = &xds.PcdsGenerator{...}    // 动态 ProxyConfig 下发

workloadGen := &xds.WorkloadGenerator{Server: s}
generators[v3.AddressType] = workloadGen
generators[v3.WorkloadType] = workloadGen
generators[v3.WorkloadAuthorizationType] = &xds.WorkloadRBACGenerator{Server: s}

一个标准 Envoy(sidecar/Router/waypoint)在 bootstrap 里配置的是 ADS 上的 Cluster/Listener/RouteConfiguration/ClusterLoadAssignment/Secret 这几种类型 URL,天然落到 CdsGenerator/LdsGenerator/RdsGenerator/EdsGenerator/SecretGen;ztunnel 请求的类型 URL 是 Address/Workload/WorkloadAuthorization,天然落到 WorkloadGenerator/WorkloadRBACGenerator——两条路径在 s.Generators 这张表里从不相交,不需要靠 if proxy.Type == Ztunnel 这种分支去阻止 ztunnel 拿到 ClusterfindGeneratorpilot/pkg/xds/xdsgen.go)的查找顺序是 Metadata.Generator+"/"+typeURLstring(proxy.Type)+"/"+typeURL → 纯 typeURL,前两级主要是给 grpc(proxyless gRPC 客户端)、api(MCP 风格消费者)这类需要按客户端族群重载同一类型的场景用的。

Type 确实在该不该推这一步起作用。xdsNeedsPushpilot/pkg/xds/xdsgen.go)显式写了一条捷径:

// pilot/pkg/xds/xdsgen.go(Istio 1.30.3,节选)
func xdsNeedsPush(req *model.PushRequest, proxy *model.Proxy) (needsPush, definitive bool) {
    if proxy.Type == model.Ztunnel {
        // Not supported for ztunnel
        return false, true
    }
    ...
}

ztunnel 走的是完全独立的推送判定路径(基于 ambient 索引的 Address 变化,而不是通用的 ConfigsUpdated 匹配);waypointNeedsPush 则是另一条专属逻辑:waypoint 的 Listener/Cluster 构建依赖「哪些 Service/Workload 挂载到了这个 waypoint」(istio.io/use-waypoint 标签),因此除了常规的 VirtualService/DestinationRule 变化,ambient 索引里的 kind.Address 更新也会触发 waypoint 重新计算——这是 waypoint 虽然用标准 LDS/RDS/CDS,却仍需要一条 Ambient 专属推送判定分支的原因。


三、四类客户端逐个看

维度 Sidecar Router(Gateway) Waypoint Ztunnel
NodeType 常量 SidecarProxy Router Waypoint Ztunnel
代理实现 Envoy(istio-proxy) Envoy(istio-proxy) Envoy(istio-proxy) Rust(独立 istio/ztunnel 仓库)
部署粒度 每 Pod 一个 每个 Gateway 部署独立若干副本 每命名空间 / 每 ServiceAccount 一组 每节点一个(DaemonSet)
订阅的 xDS 类型 LDS/RDS/CDS/EDS/SDS 同 Sidecar 同 Sidecar(LDS/RDS/CDS/EDS/SDS) AddressAuthorization(自定义类型)
身份粒度 一个证书对应自己的 ServiceAccount 同 Sidecar 一个证书对应 waypoint 自己的 ServiceAccount 多证书:为节点上每个工作负载的身份各申请一份
典型故障表现 黑洞/旧路由(Envoy 11 同 Sidecar,但影响面是整个 Gateway HBONE main_internal 监听未就绪 Address/Authorization 未同步,L4 授权判断错误

Sidecar 是默认路径,本篇不重复;下一篇(第 4 篇)的推送图就是以它为基准画的。

Router(Istio 里代表独立部署的 ingress/egress gateway)与 Sidecar 共用同一套生成器,区别在输入:Sidecar 的 Listener 集合来自「这个 Pod 的 inbound 端口 + Sidecar 资源限定的 outbound 范围」,Router 的 Listener 集合来自它所服务的 Gateway 资源声明的端口。两者殊途同归,都落在 LdsGenerator/CdsGenerator 上。

Waypoint 是 Envoy 二进制,但运行位置和触发条件都不同于 Sidecar:官方 Ambient overview 明确写道「waypoint proxy is a deployment of the Envoy proxy; the same engine that Istio uses for its sidecar data plane mode」,通过 Kubernetes Gateway API(gatewayClassName: istio-waypoint)部署,默认按命名空间共享,也支持按 ServiceAccount 更细粒度部署。它监听 HBONE 端口(15008),用一个 Envoy internal listenermain_internal)在同进程内转发已终止 HBONE 的流量给标准 HTTP filter chain 执行 AuthorizationPolicy/RequestAuthentication。因为它本质是标准 Envoy,第 5–9 篇讲的 CDS/EDS/RDS 翻译规则对它同样适用;差异只在于第二节提到的推送触发条件多了 ambient 附着关系这一层。

Ztunnel 是最激进的分叉。architecture/ambient/ztunnel.md(1.30.3)记录了官方在 Rust、Go、Envoy 三种实现之间做过实测评估后选择 Rust 的决定,理由是性能与可控的资源footprint;同一份文档给出了不用标准 xDS 类型的量化理由:用 Envoy 通用类型表达一条 mTLS 语义要几十行 Protobuf,而 ztunnel 自定义的 Address/Authorization 类型把同样信息塞进”实测下体积、内存分配、CPU 时间都有约十倍优势”的紧凑表示(原文用词为「give custom types a 10x edge」,这是 Istio 团队自己的工程实测结论,不是本站实测,也没有给出具体测试环境细节,属于 B 级来源,只能支持”设计取舍成立”,不能当作跨版本、跨环境都成立的精确倍数)。身份模型上 ztunnel 同样特殊:它以自己的身份向 CA 认证,却代表节点上其他工作负载去请求它们各自的证书;CA 必须验证「这个 ztunnel 有权代表这个身份」——这个校验基于 Kubernetes ServiceAccount JWT 里编码的 Pod 信息完成,本质是把「谁能请求谁的证书」这件事从「连接方=证书归属方」放宽成「连接方=已验证的节点代理,证书归属方=该节点上确有此工作负载」。

AgentgatewayNodeType 里最新加入的一类,配合 features.EnableAgentgateway 特性开关)代表另一条分叉:一个 AI/MCP 原生的 Rust 网关,可以替代 Envoy waypoint 承担 ingress/egress/waypoint 角色,专门处理 LLM 推理、MCP 工具调用、agent-to-agent 流量的路由与治理。它在 DiscoveryServer 里不完全复用经典的 Generators 表,而是通过 s.Collections(基于 KRT,Kubernetes Resource Tree 的内部表示)单独注册(InitCollections)——这是继「标准 xDS 类型」「ztunnel 自定义类型」之后的第三条生成器路径,目前仍在快速演进中,本系列不展开其内部机制。


四、争论:通用 xDS 类型 vs 领域特化协议

通用 xDS 类型(LDS/RDS/CDS/EDS) 领域特化类型(ztunnel 的 Address/Authorization)
代表实现 Sidecar、Waypoint、Router Ztunnel
优点 一套生成器、一套调试工具(istioctl proxy-config)覆盖所有 Envoy 类客户端 单条资源信息密度更高,客户端解析成本更低
代价 表达简单语义(如一条 mTLS 身份要求)也要走完整 Protobuf 结构 需要为每类客户端单独维护一套生成器与调试路径
官方立场 隐含在 Envoy 数据面与传统 xDS 生态的设计里(Envoy 09 显式记录在 architecture/ambient/ztunnel.md,是针对「资源footprint」这一具体约束的定向优化

这不是「哪种设计更正确」的问题,而是协议通用性与客户端资源约束之间的权衡:Sidecar/Waypoint 本身就是完整的 Envoy,多解析几行 Protobuf 不构成瓶颈;ztunnel 的定位是「每节点一个、要托管该节点上所有工作负载的连接」,客户端本身的 CPU/内存预算被压缩到官方文档明确写出的「远低于传统 sidecar」的水平,这时候协议表达的边际成本才会被放大成设计决策的核心变量。开放问题:agentgateway 引入的第三条生成器路径(Collections/KRT)是否会与经典 Generators 表长期并存,还是会成为下一次「合并」的对象——目前没有官方路线图明确回答,这需要观察后续版本的架构文档更新,本文不做预测。


五、参考资料

官方文档(A)

源码 / 设计文档(A)

项目主页 / B 级来源

站内对照

实验台账


六、小结

  1. 身份分两层:节点 ID 与 NodeMetadata 是客户端自述,只有经过 mTLS/JWT 认证后写入的 VerifiedIdentity(SPIFFE)才可信。
  2. 订阅范围首先由请求的类型 URL 决定:标准 Envoy 类客户端请求 LDS/RDS/CDS/EDS/SDS,ztunnel 请求自定义的 Address/Authorization,两条路径在生成器表里天然不相交。
  3. NodeType 影响的是”该不该推”而不是”能不能订阅”xdsNeedsPush 对 ztunnel 整体走独立判定路径,waypointNeedsPush 让 waypoint 额外感知 ambient 附着关系。
  4. 四类客户端 + 正在成型的 agentgateway,代表了「一切经 Envoy」到「按客户端资源约束定制协议」的一条持续分化路径,第 13 篇会在 Ambient 整体场景下继续展开。

系列目录 · 上一篇:istiod 进程模型 · 下一篇:推送图

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-08-11 · network / kubernetes

Istio / 网格控制面内核:从 CRD 到 xDS

补齐 Envoy 数据面 xDS 消费侧与 Mesh 选型叙事之间的控制面内核:istiod 推送图、CRD/HTTPRoute 翻译、Ambient 多客户端、NACK/warming 责任与排障对齐。


By .