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 的 MutatingWebhookConfiguration(manifests/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 一个字没动。自动注入时序: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
注入判定
injectRequired(inject.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-inject(istioctl/pkg/kubeinject/kubeinject.go:477)在本地渲染并直接产出注入后的 YAML。
kubectl apply -f <(istioctl kube-inject -f deployment.yaml)
👶 小白 vs 👨🏫 老师
👶:手动注入和自动注入产出的 Pod 会不会不一样?
👨🏫:一样。它俩共用同一套渲染引擎(
👶:那什么时候用手动?
👨🏫:调试时想先看看"到底会注入成啥样",或者你不想开 webhook(比如 CI 里生成静态 YAML)。
👨🏫:一样。它俩共用同一套渲染引擎(
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 12:pilot-agent——sidecar 容器里 1 号进程不是 Envoy,而是 pilot-agent。它负责拉起 Envoy、跑本地 SDS 发证书、做 xDS 代理。它是 Istio 控制面和你学过的 Envoy 之间的胶水。