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

【Linkerd】网格成员与注入:proxy-injector 与 native sidecar 门

文章导航

分类入口
kubernetesnetwork
标签入口
#linkerd#proxy-injector#native-sidecar#mesh-injection#opaque-ports#v2.20

目录

第 1 篇 把 Linkerd 控制面钉成 destination ∥ identity ∥ proxy-injector 三组件,并给出五轴排障坐标。读者若仍把「装了 Linkerd」等同于「Pod 已 mesh」,会在第一步指错组件:一次出站连接卡在成员资格还是流量重定向identity 还是 destination,证据包完全不同。

本篇只回答一个问题:Pod 如何成为 mesh 成员,2.20 默认的 native sidecar 改变了什么,以及 membership 失败落在哪一层。 难点不在 Helm 装控制面,而在 admission 时刻 Pod spec 被改了什么、以及排障时能否把「未注入」与「已注入但重定向未就绪」分开——后者见 第 3 篇

本篇在系列中的位置

篇目 核心内容
第 1 篇 · 控制面全景 五轴坐标系、16 篇路线
第 2 篇 · 网格成员与注入 proxy-injector、native sidecar 默认、opaque ports
第 3 篇 · 流量重定向 linkerd-init、CNI、iptables
系列目录 关键问题、阅读路径

版本锚定:Linkerd 2.20(源码 tag version-2.20,公告 2026-06-23;edge edge-26.6.3)。native sidecar 为 2.20 默认注入形态(2.15 引入、2.19 beta、2.20 stable 默认)。机制叙述对齐 tag 与 Architecture 2.20 文档树;不以 linkerd.io/docs/ live 滚动页冒充该版本。无真实 Linkerd 集群则不粘贴伪造 linkerd check 输出或 viz 指标。


一、proxy-injector webhook

Linkerd 的 proxy-injector 是运行在控制面命名空间(默认 linkerd)的 MutatingAdmissionWebhook。Kubernetes 在 Pod 创建时向 webhook 发送 AdmissionReview;injector 检查 namespace 或 workload 是否携带 mesh 注入注解,若命中则原地修改 Pod spec,再交回 apiserver 持久化。

官方 Architecture 描述的触发条件是 linkerd.io/inject: enabled(也可通过 namespace 默认注入策略配置)。注入内容至少包括:

injector 与 destinationidentity 是独立 Deployment:injector 挂了不会阻止已运行 Pod 的 mTLS 续签,但新 Pod 无法 mesh——这是 istio-xds/14 强调的「三组件拆分」在轴 1 上的第一个落点,而不是「另一个 istiod 进程内 injection 配置」。

除自动 webhook 外,运维还可对 manifest 执行 linkerd inject客户端预注入(把 YAML 发给 injector 同等逻辑处理后再 kubectl apply)。无论 CLI 还是 webhook,变异内容都遵循同一套模板:控制面版本与 linkerd-config ConfigMap 中的全局默认值(含 proxy.nativeSidecar)必须一致,否则会出现「Helm 以为开了 native、旧 injector 缓存仍写 legacy」类版本漂移——升级后应滚动 injector 并重启受影响 workload,而不是只改业务 Deployment 镜像。

flowchart TD
  create["Pod CREATE request"] --> apiserver["kube-apiserver"]
  apiserver --> webhook["proxy-injector webhook"]
  webhook --> check{"inject annotation?"}
  check -->|no| pass["unchanged spec"]
  check -->|yes| mutate["append proxy plus init or CNI flag"]
  mutate --> persist["persisted Pod spec"]
  pass --> persist

图:注入决策在 admission 层完成;未带注入注解的 Pod 不会获得 proxy 容器。

谱系:Sidecar 模式在 Burns & Beda, Designing Distributed Systems(2018)中被形式化为「与主容器同生命周期、共享网络命名空间的辅助进程」——Linkerd 的 injector 是把该模式自动化到 Kubernetes admission 路径上,而非应用自行拉 sidecar 镜像。与 Istio 把 injection 配置合并进 istiod 单进程不同,Linkerd 把 webhook 独立成组件,故障域更细,排障时先查 linkerd-proxy-injector Deployment 与 webhook 配置是否就绪。

namespace 级默认注入:除 Pod 注解 linkerd.io/inject: enabled 外,常见做法是给 namespace 打 linkerd.io/inject: enabled,使该空间内新建 Pod 默认 mesh。关闭单个 workload 可用 linkerd.io/inject: disabled 覆盖。这与 Istio 的 istio-injection=enabled 标签同类,但 Linkerd 不经过 istiod 内部模板合并——injector 直接读集群中的 linkerd-config 与版本化注入模板,因此 控制面版本与 injector 镜像必须成对升级,否则会出现注入 spec 与 proxy 镜像能力不匹配(例如 2.20 native 默认未生效到 injector)。


二、native sidecar 默认(2.20 门)

Kubernetes KEP-753(Native Sidecar Containers,1.28 alpha → 1.33 GA)允许 init 容器设置 restartPolicy: Always,使其在 init 阶段完成后仍以 sidecar 身份常驻。Linkerd 自 2.15 支持该模式,2.19 标为 beta,2.20 将其提升为默认:proxy 以带 restartPolicy: Always 的 init 容器注入,而非常规 containers 列表中的普通 sidecar。

官方 Native sidecars 文档与 2.20 公告写明默认行为及关闭方式:

层级 关闭 native sidecar(回退 legacy 形态)
全局 Helm proxy.nativeSidecar: false
namespace / workload config.linkerd.io/proxy-enable-native-sidecar: "false"

config.beta.linkerd.io/proxy-enable-native-sidecar 在 2.20 已弃用,应改用上述稳定键。

2.20 门改变了什么(机制,非性能排名)

  1. 启动顺序:native sidecar proxy 在应用 initContainers 之前即可就绪,缓解「init 需要出网但 proxy 尚未启动」的经典竞态(CNI 路径下 legacy 模式仍可能踩坑,见第 3 篇)。
  2. Job / CronJob 终止:legacy 模式下 proxy 作为普通容器常在 Job 主容器退出后继续运行,导致 Pod 无法 Completed;native sidecar 由 kubelet 按平台语义与主容器协同终止(官方博客 The Proxy Died First 讨论 shutdown race)。
  3. 证据包形状:排障时看 Pod spec——native 路径下 linkerd-proxy 出现在 initContainersrestartPolicy: Always;legacy 路径下 proxy 在 containers,另有一个一次性 linkerd-init init 容器。

控制面升级到 2.20 后,新创建或滚动重启的 meshed workload 默认采用 native 形态;未重启的旧 Pod 仍保留升级前 spec,排障必须分列证据包,不能把集群版本当作每个 Pod 的注入形态。

与 Istio 注入模型的对照(机制,非优劣):Istio 由 istiod 生成 sidecar 容器模板,变更常体现为 istiod 版本与 istio-proxy 镜像的配对;Linkerd 由独立 injector Deployment 读取 linkerd-config,模板与 linkerd-proxy 镜像由 Linkerd 发行物钉死。xDS 通用协议与专用 linkerd2-proxy-api 的争论在控制面 API 层展开(第 6 篇);在轴 1 上,两者差异首先是 webhook 组件是否独立native sidecar 是否为 2.20 默认门


三、legacy init+sidecar 路径

proxy.nativeSidecar=false 或注解显式关闭时,injector 回退 legacy 布局,与 2.15 之前的主流形态一致:

组件 在 Pod 中的位置 作用
linkerd-init 一次性 initContainers 安装 iptables 规则,将 TCP 流量导向 proxy(CNI 启用时省略)
linkerd-proxy 普通 containers 数据面微代理

该路径在 2.20 仍受支持,并非删除项。官方 Architecture 仍把 linkerd-init 描述为「在任何其他容器启动前运行」的 init 容器——在 legacy 模式下,init 先写完 iptables 再退出,随后应用容器与 proxy 并行启动。

与 native 的分叉点仅在 injector 写 spec 的时刻;destination、identity 协议不变。运维上常见误区是把「集群已 2.20」等同于「所有 Pod 已是 native sidecar」——实际上只有触发过重新注入的 workload 才会更新形态。

legacy 路径在以下场景仍合理或不可避免:

排障 legacy Pod 时,证据包应同时记录 linkerd-init 退出码proxy 容器 restart 次数。native 路径下 proxy 位于 initContainers,但未启用 CNI 时 linkerd-init 仍可能存在(负责 iptables);启用 CNI 则两者都可能从 spec 中省略。轴 1 与轴 2 的交界见第 3 篇。


四、opaque ports 与跳过注入

注入轴不仅决定「有没有 proxy」,还决定哪些端口走 proxy 的协议栈。两类配置常被混淆:

机制 典型注解 流量路径 mTLS / 指标
opaque ports config.linkerd.io/opaque-ports 仍经 proxy,跳过 HTTP 协议探测,按 TCP 透传 保留
skip ports config.linkerd.io/skip-inbound-ports / skip-outbound-ports iptables 绕过 proxy 不保留

官方 Protocol DetectionProxy Configuration 强调:opaque 一般优先于 skip——前者只关掉探测,不拆掉安全与观测;后者仅适合调试。opaque 注解应标在流量接收方(destination),skip-outbound 标在发送方

此外,注入本身也可被关闭:linkerd.io/inject: disabled 显式跳过 webhook 变异;某些系统命名空间默认不注入。这与「已注入但端口 opaque」是不同问题:前者无 proxy 容器,后者有 proxy 但特定端口行为不同。

默认 opaque 端口列表由控制面全局配置提供(安装时写入 linkerd-config),注解 config.linkerd.io/opaque-ports 在 Pod 级覆盖默认值而非追加——误以为是「追加」会导致以为已声明的端口仍未 opaque。对数据库类工作负载,还应优先尝试 Service ports[].appProtocol(Kubernetes 1.19+),使 meshed 客户端在走协议探测前即可获知语义;仅当 headless、直连 Pod IP 或 unmeshed 客户端等场景才必须依赖 Pod 注解(官方 Protocol Detection)。

场景 推荐配置位置 原因
ClusterIP Service,meshed 客户端 Service appProtocol 客户端经 Service 发现协议
Headless / Pod IP 接收方 Pod opaque-ports 无 Service 级端口元数据
调试旁路 skip-*-ports 明确放弃 mTLS,仅短期

开放争论:opaque 流量在入站侧汇聚到 proxy 的 inbound 端口(默认 4143),与基于端口的 NetworkPolicy 可能冲突——社区 issue 表明需允许 4143 入站,否则 CNI/策略层表现为「连不上」而 mesh 成员资格实际成立。该症状易被误判为未 mesh,实则是轴 1 配置与轴 2 策略的交界,详见第 3 篇第三节。


五、membership 失败表象

排障口诀:先确认轴 1(注入与成员),再查轴 2(重定向)。下列表象与建议落点:

表象 优先核对 常见根因
Pod 无 linkerd-proxy 容器 linkerd.io/inject、namespace 注入策略、injector webhook 未命中注入条件;webhook 失败或 injector 不可用
有 proxy 但非预期形态 initContainers vs containersrestartPolicy 2.20 默认 native 与 legacy 混部;Helm/注解覆盖
meshed 客户端访问失败,对端无 proxy 对端 Pod spec 对端未 mesh(轴 1),不是 destination 空端点
仅特定端口失败 opaque / skip 注解;Service appProtocol 协议探测或 NP 与 inbound 端口不匹配
Job 长期 Running proxy 容器形态 legacy Job 未退出;可评估 native sidecar 或官方 Job 指引

注入决策与 xDS 无关:无论数据面之后如何订阅 destination,membership 只由 admission 结果决定。把「未注入」当成「xDS 未推送」是 istiod 心智模型的误用;Linkerd 读者应在 Pod spec 层完成轴 1 否证,再进入 linkerd2-proxy-api(第 6 篇)。同样,linkerd inject 的 dry-run 语义是预览变异后 YAML,不替代集群内 webhook 是否注册成功——webhook 配置错误时 CLI 本地 inject 仍可能成功,而集群内自动注入失败。

不要在轴 1 未确认时改 ServiceProfile 或重装 destination——那属于轴 4–5。与 istio-xds/14 对照:Istio 注入失败查 istiod webhook 与 sidecar 模板;Linkerd 查 linkerd-proxy-injectornative vs legacy spec 形状

开放问题:native sidecar 依赖 Kubernetes 1.33+ GA 语义时,混合版本节点集群如何在 namespace 级关闭 native 而业务 namespace 保持默认,仍缺少统一 runbook;本系列 第 16 篇 与 Job/CronJob 默认策略一并收束。轴 1 排障的底线是:先读 Pod spec,再读 Helm values,最后才动控制面重装。

多集群与注入:multicluster 扩展不改变单集群 injector 语义——每个集群仍有自己的 linkerd-proxy-injectorlinkerd-config。联邦服务发现发生在 destination 层(第 11 篇),而非在 webhook 层合并 Pod 模板;因此「集群 A 已 mesh、集群 B 未注入」是轴 1 在各自集群独立否证的问题。

ExternalWorkload 与 VM 扩展(2.20 公告提及 Windows VM mesh expansion):轴 1 对非 Pod 工作负载的注入形态不在本篇展开,但原则不变——无 proxy 则无 mesh 成员资格;VM 侧 harness 与 K8s webhook 是不同入口,排障时勿把 VM 未注册当成 injector 故障。


参考资料

规范 / 官方文档(A)

博客 / 维护者文章(B)

学术 / 设计谱系(A)

站内对照


上一篇控制面全景

下一篇流量重定向

读完这篇,下一步读什么

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


By .