土法炼钢 · 系统与基础设施

【Linkerd】服务发现流:per-target Get 与 EndpointTranslator

文章导航

分类入口
kubernetesnetwork
标签入口
#linkerd#destination#service-discovery#endpointslices#endpoint-translator#v2.20

目录

第 6 篇 钉了四套 gRPC 与 xDS 寻址差;第 5 篇 说明 destination 容器靠 Informer 驱动。读者仍常把「Service 有 Endpoints 但 mesh 内连不上」误判成数据面或 mTLS 问题——实际要先问:该出站目标对应的 destination.Get 流里有没有 WeightedAddr、有没有 no_endpoints{exists: true}、流是否因队列溢出被掐断。

本篇展开发现轴内核:per-target 查询语义、EndpointTranslator 与 EndpointSlice 增量、identity/zone 富化,以及相对 xDS EDS 的失败落格。不写未测的发现延迟数字。

本篇在系列中的位置

篇目 核心内容
第 6 篇 · linkerd2-proxy-api 四套 gRPC、专用 API vs xDS
第 7 篇 · 服务发现流 per-target Get、EndpointTranslator、空流
第 8 篇 · 策略模型 Server、AuthorizationPolicy
系列目录 关键问题、阅读路径

版本锚定:Linkerd 2.20(tag version-2.20)。2.20 默认启用 EndpointSlice 路径(enableEndpointSlices);机制对齐 controller/api/destination/server.goendpoint_translator.godestination.proto @ linkerd2-proxy-api v0.20.0无真实集群则不粘贴伪造端点推送日志或 viz 指标。


一、per-target 查询语义

linkerd2-proxy 对每个出站目标发起独立的 destination.Get(GetDestination) 服务端流。GetDestination.path 形如 svc.namespace.svc.cluster.local:port;省略端口时默认 80,省略 namespace 时默认 defaultserver.go 注释)。

一次 Get 的生命周期:

  1. 解析 path → Kubernetes Service 三元组(name / namespace / port)。
  2. 区分本地发现、远程多集群发现、联邦服务(federated service)三条订阅路径。
  3. 为该流创建 EndpointTranslator,向 EndpointsWatcher 注册为 listener。
  4. 阻塞至客户端取消、控制面 shutdown、或流异常结束。

与 xDS EDS 的对照:EDS 按 Cluster 名 订阅端点集合,同一连接上可挂多种 xDS 类型;Linkerd 是 每个逻辑目标一条 gRPC 流,流内只有该目标的 Update 序列。排障口令因此是「对这个 authority 的 Get 流收到了什么」,而不是「EDS 快照里有没有这个 Cluster」。

destination.proto 规定三种 Update

语义细节:no_endpoints 后接 add 不等价于只发 add——客户端必须按 proto 注释处理 exists 字段,否则会在重连窗口内错误清空缓存。


二、EndpointTranslator / EndpointSlice

EndpointsWatcher 基于 client-go Informer 监听 Endpoints 或 EndpointSlice(2.20 由 EnableEndpointSlices 控制)。Informer 回调必须非阻塞,因此 EndpointTranslator 用带缓冲 channel 异步处理:

Informer Add/Remove → enqueueUpdate → goroutine processUpdate → stream.Send(Update)

endpoint_translator.go 实现 EndpointUpdateListenerAdd 翻译为 Update_AddRemove 翻译为 Update_Remove。默认队列容量 DefaultStreamQueueCapacity = 100;队列满时递增 endpoint_updates_queue_overflow关闭流endStream),迫使 proxy 重连——这是发现轴上的「流落后」信号,不是 xDS NACK。

翻译时每个 WeightedAddr 可附带:

destination RBAC(destination-rbac.yaml)在启用 EndpointSlice 时授予 endpointslices 的 list/get/watch/create/update/patch/delete——控制面既消费也必要时维护切片,与纯只读 EDS 翻译器的权限模型不同。本篇不展开 2.20 destination 内存重构细节,指针见 第 12 篇

flowchart TD
  eps["EndpointSlice Informer"] --> ew["EndpointsWatcher"]
  ew -->|"Add/Remove"| et["EndpointTranslator"]
  et -->|"stream Send"| get["destination.Get stream"]
  get --> proxy["linkerd2-proxy outbound"]

三、identity/zone 富化

端点从 Kubernetes 地址到 proxy 可消费的 WeightedAddr,中间有一层 富化,排障时常与「无端点」混淆:

mTLS 身份createWeightedAddr 在 Pod 受同一控制面管理、端口未在 skip-inbound-ports 中跳过时,构造

{sa}.{ns}.serviceaccount.identity.{control-ns}.{trust-domain}

作为 TlsIdentity.DnsLikeIdentity。无身份富化的端点可能是未 mesh 工作负载、opaque 端口、或 ExternalWorkload 的 URI-like 身份——需与 第 4 篇 轴 2 分列。

Zone 富化getNodeTopologyZone 读取源 Pod 所在节点的 topology.kubernetes.io/zone,在 metric_labels 写入 zonezone_localitylocal / remote / unknown)。实验性 ExtEndpointZoneWeights 可把同 zone 端点权重乘以 10——机制门,非 benchmark。

协议提示:annotation / Server CRD 决定的 opaque 端口、forceOpaqueTransport、H2 upgrade 影响 ProtocolHint,与 第 3 篇 的 opaque/inbound 端口轴衔接。

富化失败(如读不到 inbound 端口环境变量)会在 translator 日志记 error 并 跳过该地址,表象是「Endpoints 有 Ready 地址但流里少了几台」,不是 Service 不存在。


四、相对 xDS EDS 失败落格

症状 xDS / EDS 常见落格 Linkerd destination 落格
配置被拒 NACK、warming、PushOrder 不适用;查 gRPC 状态码与流是否存活
集群无端点 EDS 空 ClusterLoadAssignment no_endpoints{exists: true} 或空 add
服务不存在 CDS/EDS 无 Cluster 或 404 类行为 NotFoundno_endpoints{exists: false}
推送落后 Delta 版本错乱、SotW 全量重推 endpoint_updates_queue_overflow、流被关闭
错后端 EDS 订阅错 Cluster 名 Get path 解析错、远程发现 label 错

口诀:先确认 authority 字符串,再确认流内 Update 序列,最后才看 proxy 出站日志。 不要把 istiod「EDS 为空」的 runbook 原样套到 Linkerd。

与「对端未 mesh」区分:未 mesh 的对端可能仍有 TCP 端点但 TlsIdentity 或握手失败;no_endpoints{exists: true} 则是 discovery 层认定 Service 存在但当前无 Ready 地址——第 13 篇 五轴会分列。


五、空流与延迟

空流几种可核对形态:

  1. no_endpoints{exists: true}:Service 存在,Deployment 未 Ready、selector 不匹配、或 EndpointSlice 尚未生成——Kubernetes 轴 + 发现轴。
  2. 流建立后长时间无 add:Informer 未同步、RBAC 拒绝 watch、destination Pod 未 Ready——控制面健康轴。
  3. add 后全被 remove:滚动更新正常抖动;若持续空集回到 no_endpoints
  4. 队列溢出断流:EndpointSlice churn 过快,translator 跟不上;查 endpoint_updates_queue_overflow 与 destination 资源(2.20 内存优化见第 12 篇)。

延迟:proto 要求订阅开始尽快首包;客户端重连可暂用旧缓存直至收到首条新消息。Informer 反射延迟 + translator 队列深度决定「Kubernetes 已 Ready 但 mesh 仍用旧端点」的窗口——本篇无实测延迟数字,不写成 SLA。

开放问题(工程判断):per-target 流在超大服务名基数下连接税高于 xDS 多路复用——第 16 篇 选型收束时回指;机制事实是不做 type-subscription 聚合。


参考资料

规范 / 官方文档 / 源码(A)

博客 / 维护者文章(B)

站内对照(A/B)


上一篇linkerd2-proxy-api

下一篇策略模型

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-23 · kubernetes / network

【Linkerd】linkerd2-proxy-api:专用 gRPC 与 xDS 寻址差

钉清 Linkerd 2.20 控制面与 linkerd2-proxy 之间的四套专用 gRPC 服务(destination/identity/inbound/tap),说明 per-target Get 流式寻址与 xDS type-subscription 的机制差,以及错误落在 gRPC 层而非 ACK/NACK。


By .