Day 11 / 共 20 天 · 第 3 周 sidecar 与安全

Sidecar 注入

Istio 怎么把 Envoy"塞进"你的业务 Pod?靠 Kubernetes 的 MutatingWebhook——Pod 创建时拦截、返回一份 JSON Patch,Pod 就多了一个 Envoy sidecar 容器。今天读透注入机制。第 3 周开场:W1-2 我们看的是 istiod 这个"总调度室"怎么生成 xDS,从今天起看这些配置最终落到哪——先解决"Envoy 怎么进到每个 Pod 里"。

📍 你在整门课的位置 · 第 3 周「sidecar 与安全」(W1 控制面全景 ✓ · W2 xDS 生成 ✓)
D11 sidecar 注入 D12 pilot-agent D13 bootstrap D14 mTLS·CA D15 SDS·授权
L01

什么是 sidecar 注入

🤔 痛点:没有"自动注入"你得怎么办? 你有 200 个 Deployment,想让每个都用上 Istio 的流量治理/加密。没有注入机制,你得手动给每个 Deployment 的 YAML 加一个 Envoy 容器、一个 init 容器、一堆卷和环境变量——200 份、每份几十行、还不能写错。升级 Envoy 版本?200 份再改一遍。这显然不可接受。
💡 本质:一次登记,全楼自动配"贴身管家" sidecar 注入就像物业在小区大门口立了个规矩:以后任何住户(Pod)入住,门口自动派一个"贴身管家"(Envoy)跟着他,管家帮他收发所有快递(流量)。你不用挨家挨户签合同,只要在大门口登记一次规则(MutatingWebhook),后续全自动。这就是把 W1-2 里"总调度室"(istiod)算好的治理能力,透明地送到每个住户身边。
sidecar = "跟班" sidecar(边车,摩托车挎斗)模式:给你的业务容器配一个"跟班"容器(Envoy),它俩住在同一个 Pod 里、共享网络。业务容器的所有进出流量,都被悄悄改道,先经过这个 Envoy 跟班(靠 init 容器改 iptables 规则实现)。于是 Envoy 就能对流量做治理(路由/mTLS/遥测),而业务代码完全无感——它以为在直接收发,其实中间隔了个 Envoy。"注入"就是"自动给 Pod 加上这个 Envoy 跟班"的过程。你不用手动改每个 Deployment,Istio 自动帮你塞。
L02

MutatingWebhook

自动注入靠 K8s 的 MutatingWebhookConfigurationmanifests/charts/istio-control/istio-discovery/templates/mutatingwebhook.yaml):

# clientConfig(:15-27):指向 istiod 的 /inject 路径、443
# rules(:29-33):operations:[CREATE], resources:[pods] —— 只在 Pod 创建时触发
# namespaceSelector + objectSelector:按 istio.io/rev 标签或对象标签匹配
#   且 sidecar.istio.io/inject != "false" 才注入
MutatingWebhook = "K8s 的拦截钩子" Kubernetes 允许你注册一个"变更 webhook":每当有人创建某类资源(这里是 Pod),API Server 先把这个资源发给你的 webhook,你可以返回一份"修改补丁",API Server 应用后再真正创建。Istio 就注册了这么个钩子——Pod 创建时,K8s 把 Pod 发给 istiod 的 /inject,istiod 返回"加一个 Envoy 容器"的补丁。这就是"自动注入"的魔法:不改你的 YAML,在 Pod 落地前动态改。只对打了 Istio 标签的 namespace/Pod 生效。
📝 举个例子:一个 Pod 走一遍注入kubectl apply 一个只有 1 个容器 app 的 Deployment → API Server 创建 Pod 前先把它发给 istiod 的 /inject → istiod 返回 JSON Patch [{op:add, path:/spec/initContainers, ...istio-init}, {op:add, path:/spec/containers/-, ...istio-proxy}] → 最终落地的 Pod 里有 3 样东西istio-init(init 容器) + 你的 app + istio-proxy(Envoy)。你原来的 Deployment YAML 一个字没动
你 (kubectl) API Server istiod /inject etcd apply Pod(1 容器) AdmissionReview(Pod) 返回 JSON Patch(+init +proxy) 写入改造后的 Pod(3 样东西) API Server 把 patch 应用到 Pod 后才真正落库
自动注入时序:Pod 在"落库"前被 istiod webhook 拦一道、打上补丁。你的原始 YAML 全程没被改。
L03

Webhook Server

// pkg/kube/inject/webhook.go
// NewWebhook(:202):237 行 p.Mux.HandleFunc("/inject", wh.serveInject) 注册处理器
// serveInject(:1288):解析 AdmissionReview,调 wh.inject
// inject(:1104):
//   :1129 injectRequired(...) 判定是否需要注入(不需要直接放行)
//   :1198 injectPod(params) 生成 patch
//   :1204 返回 AdmissionResponse{Allowed:true, Patch, PatchType:"JSONPatch"}
读法:istiod 内跑着一个 HTTP server 处理 /inject。收到 Pod → 判断要不要注入 → 生成 JSON Patch 返回。配置(模板)从 ConfigMap istio-sidecar-injector 热加载(改了立即生效,不重启)。
L04

注入判定

injectRequiredinject.go:199)决策顺序:

// 1. HostNetwork 的 Pod 跳过(iptables 会改宿主机路由,:207)
// 2. 系统命名空间跳过(:212)
// 3. 读 sidecar.istio.io/inject 注解/标签,label 优先(:223)
// 4. 未显式指定 → 匹配 NeverInjectSelector / AlwaysInjectSelector(:243/:258)
// 5. 最后按 config.Policy 综合出 required(:274)
读法:不是所有 Pod 都注入——有精细的判定规则。HostNetwork 的不能注入(会搞乱宿主机网络),系统 ns 不注入,用户可用注解/标签显式开关。这套"多层判定 + 显式优先"避免误注入。
📝 单步走查:4 个 Pod 分别判成什么
Pod 情况命中哪条规则结果
普通业务 Pod(ns 打了 istio-injection=enabled)走到第 5 步按 Policy✅ 注入
Pod 标了 sidecar.istio.io/inject: "false"第 3 步 label 显式关❌ 跳过
hostNetwork: true 的 Pod第 1 步直接跳过❌ 跳过
kube-system 里的 Pod第 2 步系统 ns❌ 跳过
⚠️ 常见误解:小白常以为"只要装了 Istio,所有 Pod 就都被注入了"。其实不是——注入是"选择性"的:既要 namespace/Pod 命中标签,又不能被显式关闭,还要躲开 hostNetwork/系统 ns 这些红线。判定顺序里"显式 label/注解"优先级最高,是你手动开关的总闸。
L05

模板渲染 + SMP

// injectPod(webhook.go:463)核心:
//   :473 RunTemplate(req) 渲染模板得到 mergedPod
//   :488 createPatch 生成 JSON Patch
// RunTemplate(inject.go:407):
//   :434 构造 SidecarTemplateData(ProxyConfig/MeshConfig/Values/ProxyImage/UID…)
//   :456 遍历模板名,逐个 runTemplate 渲染再 applyOverlayYAML 叠加(Strategic Merge Patch)
注入 = "渲染模板 + 策略合并补丁" 注入的本质:①把一段 YAML 模板(描述 Envoy 容器长啥样)用当前 Pod 的信息渲染出来;②用 Kubernetes 的"策略化合并补丁(Strategic Merge Patch)"把渲染结果叠加到原 Pod 上;③原 Pod 和叠加后 Pod 的差异,就是要返回的 JSON Patch。SMP 比普通 patch 智能——它懂 K8s 资源结构(比如按容器 name 合并而非整个数组替换)。这样用户对 Envoy 容器的自定义(改资源限制等)能正确合并进去。
L06

注入模板内容

模板 manifests/charts/istio-control/istio-discovery/files/injection-template.yaml(542 行)注入两样:

  • init 容器 istio-init:80-179):跑 istio-iptables,配流量拦截规则——-p 15001(出站重定向)、-z 15006(入站)、-u 1337(proxy uid 绕过拦截)。
  • istio-proxy 容器:184-405):跑 pilot-agent + Envoy,端口 15090(prom);注入 CA_ADDR/POD_NAME/SERVICE_ACCOUNT 等 env;挂载 istio-token(SA JWT)/istiod-ca-cert(根证)等卷。
init 容器的 iptables 是流量劫持的关键 Envoy 怎么做到"业务容器的流量都先经过我"?istio-init 这个 init 容器在 Pod 启动前跑一次,用 iptables 规则把 Pod 里所有出站流量重定向到 Envoy 的 15001 端口、所有入站到 15006。proxy 自己的流量用 uid 1337 标记以绕过(否则死循环)。于是业务容器一发包,内核就把它转给 Envoy——业务完全无感。这就是 sidecar 模式"透明劫持"的底层。挂载的卷(token/证书)则是 Day 14-15 mTLS 的材料。
L07

手动注入

除自动注入,还能手动:istioctl kube-injectistioctl/pkg/kubeinject/kubeinject.go:477)在本地渲染并直接产出注入后的 YAML。

kubectl apply -f <(istioctl kube-inject -f deployment.yaml)
👶 小白 vs 👨‍🏫 老师 👶:手动注入和自动注入产出的 Pod 会不会不一样?
👨‍🏫:一样。它俩共用同一套渲染引擎pkg/kube/inject),区别只是"谁来触发、在哪渲染"——自动是 API Server 回调 istiod 在线渲染,手动是 istioctl 在你本机离线渲染。就像同一份"改装图纸",可以工厂帮你装(自动),也可以你自己按图纸装(手动),装出来的车一样。
👶:那什么时候用手动?
👨‍🏫:调试时想先看看"到底会注入成啥样",或者你不想开 webhook(比如 CI 里生成静态 YAML)。
读法:手动(istioctl 本地渲染)和自动(API Server 回调 istiod)共用同一套渲染引擎pkg/kube/inject)。手动适合调试/不想开 webhook 的场景。istiod 侧的装配在 pilot/pkg/bootstrap/sidecarinjector.go:43
L08

今日小结 + 动手

🧠 今天你应该能回答

  • sidecar 模式是什么?"注入"注入了什么?
  • MutatingWebhook 怎么实现自动注入?
  • injectRequired 的判定规则?
  • 注入的本质(模板渲染 + SMP)?
  • init 容器的 iptables 怎么劫持流量?手动 vs 自动区别?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/istio
grep -n 'serveInject\|func (wh \*Webhook) inject\|injectPod' pkg/kube/inject/webhook.go | head
grep -n 'func injectRequired\|func RunTemplate' pkg/kube/inject/inject.go
sed -n '80,110p' manifests/charts/istio-control/istio-discovery/files/injection-template.yaml
明天预告 · Day 12pilot-agent——sidecar 容器里 1 号进程不是 Envoy,而是 pilot-agent。它负责拉起 Envoy、跑本地 SDS 发证书、做 xDS 代理。它是 Istio 控制面和你学过的 Envoy 之间的胶水。
← 总目录 Day 12 · pilot-agent →