首页
/ Istio CNI Node Agent 深度解析:Sidecar 流量劫持、Ambient 模式与 CNI 插件实现原理

Istio CNI Node Agent 深度解析:Sidecar 流量劫持、Ambient 模式与 CNI 插件实现原理

2026-09-05 20:35:52作者:齐冠琰

本篇基于 Istio 仓库的 cni/README.md 展开,系统讲解 Istio CNI Node Agent 的三大职责(CNI 插件安装、Sidecar 模式流量劫持、Ambient 模式事件上报)、Sidecar 模式下的 Selection/Redirect API、CmdAdd 完整工作流以及 Ambient 模式的环境变量与权限模型,并结合 cni/pkg 下的插件实现、节点代理选项与 Helm chart 模板,帮助你在生产集群中排查 CNI 相关问题并深入理解 Istio 无侵入流量拦截的底层机制。

一、Istio CNI Node Agent 是什么

Istio CNI Node Agent 以 DaemonSet 形式运行在集群每个节点上,承担以下三类核心职责:

  1. 安装并守护 CNI 插件:在每个节点的本地文件系统中安装 istio-cni 插件二进制,更新该节点的 CNI 配置(如 /etc/cni/net.d),并持续 watch 配置与二进制路径,一旦被修改即重新安装。
  2. Sidecar 模式:替代 istio-init:Pod 被容器运行时调度时,由 CNI 插件使用 iptables 配置 sidecar 网络。这替代了 Istio 过去在用户应用 Pod 中注入带 NET_ADMIN 权限的特权 initContainersistio-init)的做法——彻底移除了用户应用 Pod 中对特权容器的需求
  3. Ambient 模式:事件上报:CNI 插件本身不配置任何网络,只负责把新 Pod 事件同步推送回运行在 Node Agent 内部的 ambient watch server。该 server 负责定位 Pod 的 netns 并通过 iptables 在其内部配置网络;它还会 watch 启用 ambient 的 namespace,以同样方式“补纳”已启动但新近被启用 ambient 的 Pod。

Ambient 模式下的 Pod 注册全生命周期详见架构文档 ztunnel-cni-lifecycle.md

二、部署架构:Helm Chart、install-cni 容器与插件二进制

istio-cni Helm chart 的结构看,部署由三部分构成:

  • install-cni DaemonSet:主功能是安装并辅助节点 CNI,但它同时是一个完整的 server,会与 K8S 交互并 watch Pod 用于恢复(repair)。
  • istio-cni-config ConfigMap:存放要合并进节点 CNI 链式配置的 CNI 插件配置。
  • 创建带 ClusterRoleBinding 的 service-account istio-cni,允许其获取 Pod 信息,并在恢复场景下对 Pod 执行删除/修改。

install-cni 容器的具体行为

结合 pkg/install 源码install-cni 容器在 installAll 流程中依次完成(cni/pkg/install/install.go):

  1. 拷贝二进制:将 istio-cniistio-iptables 拷贝到 /opt/cni/bin;若目标目录只读会提示设置 global.platform(源码中对 "read-only file system" 错误有专门提示分支);
  2. 写 kubeconfig:把 Pod 所在 service account 的 kubeconfig 写到 agent run 目录(文件名固定为 istio-cni-kubeconfig,见 constants.go),并周期性刷新宿主机上的 K8S JWT token,供插件连接 K8S;
  3. 注入 CNI 插件配置:将 CNI_NETWORK_CONFIG 插入 /etc/cni/net.d/${CNI_CONF_NAME}plugins 列表。安装器会按文件扩展名(.conf.conflist)在挂载的 CNI net 目录下查找配置文件,文件名可用 CNI_CONF_NAME 环境变量显式指定;Istio 自有配置默认为 02-istio-cni.conflistconstants.go);
  4. 就绪探针与监控:提供 /readyz/healthz 端点并暴露 Prometheus 指标(监控端口默认 8000,见 constants.go);
  5. UDS 日志转发:建立 Unix Domain Socket(log.sock),让 istio-cni 插件把日志发到本容器,最终可通过普通 kubectl logs 查看(CNI 插件进程不能写 stdout,CNI 规范用 stdin/stdout 传数据,详见下文);
  6. 可选控制器:按配置运行 repair 控制器(检测 istio 设置失败或处于特殊角落场景的 Pod 并重启之,源码见 repaircontroller.go);若启用 ambient,则同时运行 watch Pod/Namespace 的 ambient 控制器

安装后进程会进入 watch 循环,持续比对配置与期望状态,发生漂移时触发重装(Installer.Run)。

istio-cni 插件

  • CNI 插件可执行文件,被拷贝到 /opt/cni/bin,当前仅实现 Kubernetes 场景;
  • Pod 创建时(ADD 事件)判断该 Pod 是否需要配置指向 Istio proxy 的 netns 重定向(逻辑见下文 CmdAdd 工作流);
  • 插件使用 install-cni 拷贝过来的 kubeconfig 和 JWT token 连接 K8S 获取 Pod 与 Namespace 信息。由于是短生命周期命令,每次调用都新建连接
  • 判定需要重定向后,调用 istio-iptables 并在 Pod netns 中执行 nsenter --net=<k8s pod netns> /opt/cni/bin/istio-iptables ... 完成端口列表设置;ambient 场景则走 ambient 事件逻辑。

istio-iptables

  • 建立 iptables 规则,把一组端口重定向到 Envoy 监听端口;
  • istio-init 容器共享代码,根据 annotation/label 及其他设置生成 iptables-save 配置并应用。

三、Sidecar 模式:Selection API 与 Redirect API

Istio CNI 注入当前沿用与 init-container/inject 模式相同的 Pod annotation 体系。

Selection API(判定是否由 CNI 接管)

判定顺序为:

  1. 插件配置中的 exclude namespaces 最先生效;
  2. ambient 判定满足以下条件时为真:
    • namespace label istio.io/dataplane-mode == ambient,和/或 pod label istio.io/dataplane-mode == ambient
    • Pod 上不存在 sidecar.istio.io/status annotation(该 annotation 由 sidecar 注入产生);
    • pod label istio.io/dataplane-mode 不等于 none
  3. sidecar 拦截启用需同时满足:
    • Pod 中不存在 istio-init 容器;
    • 存在 istio-proxy 容器,且:
      • 未设置 DISABLE_ENVOY 环境变量(该变量会触发 proxyless 模式);
      • istio-proxy 容器前两个参数为 proxysidecar——或参数不足两个、或第一个参数不是 proxy
      • sidecar.istio.io/inject 不为 false
      • sidecar.istio.io/status 存在。

这套规则在源码 cni/pkg/plugin/plugin.go 中逐条落地:先检查 istio-init 容器排除、再检查 DISABLE_ENVOY、再要求存在 istio-proxy 容器、检查 proxy 类型、解析 sidecar.istio.io/inject(新版 label API 优先于 annotation,见 plugin.go),最后要求 sidecar.istio.io/status annotation 存在。ambient 判定则由 isAmbientPod 通过编译后的 EnablementSelector 对 Pod 与 Namespace label 匹配完成。

Redirect API(重定向参数)

基于 annotation 的细粒度控制目前仅在 sidecar 模式支持,实现细节见 cni/pkg/plugin/sidecar_redirect.go。可配置的参数包括:

参数 说明
redirectMode 可设为 TPROXY(要求 envoy 具备额外权限),默认 redirect(REDIRECT 模式)
includeIPCidr / excludeIPCidr 需要拦截 / 排除拦截的 IP 段
includeInboundPorts / excludeInboundPorts 入站端口白/黑名单
includeOutboundPorts / excludeOutboundPorts 出站端口白/黑名单
excludeInterfaces 排除的网卡接口
reroute-virtual-interfaces 重路由虚拟网卡(旧名 kubevirtInterfaces 已废弃)
ISTIO_META_DNS_CAPTURE proxy 上的环境变量,开启后启用 DNS 重定向
INVALID_DROP proxy 上的环境变量,改变 iptables 中对非法流量的行为(reset → drop)

两个自动行为:

  • 自动排除入站端口 15020、15021、15090:这三个代理端口(health、status、prometheus 指标等)无需再绕回 Envoy,源码中在构造重定向配置时直接追加 redir.excludeInboundPorts += "15020,15021,15090"sidecar_redirect.go),并有 plugin_test.go 的测试断言兜底;
  • 自动识别 proxyUID/proxyGID:代码从 istio-proxy 容器的 RunAsUser/RunAsGroup 自动提取 UID/GID 并排除其流量(默认 1337),见 kubernetes.go,避免 proxy 自身流量被二次劫持。

四、CmdAdd Sidecar 工作流(源码级解读)

CmdAdd 在新 Pod 创建时触发,运行在节点上、位于 CNI 插件链中——Istio 在主 CNI 完成 Pod IP 与网络配置之后才被执行。完整工作流:

  1. 对照插件配置的排除列表检查 Pod namespace。配置必须排除 Istio 控制平面所在 namespace(文档中留有 TODO:Pod 级别排除已足够,未来可能让 Istiod 等组件也使用 ambient)。若被排除则忽略该 Pod 并返回 prevResult;
  2. 为 Pod 设置重定向规则
    • 从 Pod 定义与 annotation 中取得端口列表;
    • nsenter --net=<k8s pod netns> /opt/cni/bin/istio-iptables ... 方式在 Pod netns 中设置 iptables。以下情况会阻止重定向规则的设置:
      • Pod annotation sidecar.istio.io/injectfalse,或不存在 sidecar.istio.io/status annotation;
      • Pod 含 istio-init initContainer——表示该 Pod 自行做注入设置;
    • 返回 prevResult。

源码层面(CmdAdd)还有几个关键实现细节:

  • panic 兜底CmdAdddefer/recover 捕获 panic,确保任何异常都能以规范的 CNI 错误返回给容器运行时,而不是让插件崩溃;
  • 自我保护,避免死锁:插件先检查本次 ADD 是否属于自身的 istio-cni-node-* Pod(isCNIPod)。因为 kubeconfig 可能尚未由 install-cni 写入(例如节点重启后),直接创建 K8S client 会阻塞 CNI Pod 自身启动;
  • 降级放行:若 K8S API 不可达(如凭证过期),插件会重试最多 30 次(间隔 1 秒,见 plugin.go);期间若发现该 Pod“看起来是”替换中的 istio-cni agent,则基于 CNI 层传入的 K8S_POD_NAME/K8S_NAMESPACE 直接放行,防止升级期间新 agent Pod 被旧插件卡死;
  • ambient 分支提前返回:若插件配置启用 ambient 且该 Pod 判定为 ambient Pod,插件仅通过 UDS(pluginevent.sock/cmdadd 路径)把事件推送给节点 agent 后即返回,不做任何本地网络配置。

插件的日志策略也很特别(GetLoggingOptions):日志写到 stderr、tee 到 UDS(由 DaemonSet 读走并暴露为 kubectl logs),同时再 tee 一份滚动日志 istio-cni.log(上限 10MB,见 constants.go)以防 UDS server 宕机;日志级别由插件配置的 plugin_log_level 控制。

五、Ambient 模式设计细节

“in-pod sidecar” 原理

从文档设计说明看,istio-cni 实现 ambient 流量重定向的方式是:指导 ztunnel 在应用 Pod 的网络命名空间内建立 socket,其一端位于应用 Pod 内、另一端位于 ztunnel 的 Pod 内,再配合 iptables 规则把流量通过这根 socket“管道”送入 ztunnel 并折返。

这样带来的效果是:

  • 行为上等效于 ztunnel 是 Pod 内 sidecar,但无需把 ztunnel 注入 Pod manifest,也不以任何方式变更应用 Pod
  • 不需要在 host 网络命名空间中配置任何网络规则/路由,极大提升了对第三方 CNI 的兼容性——绝大多数情况下,这种 "in-pod" ambient CNI 与第三方 CNI 的兼容性不逊于传统 sidecar 模式。

关键环境变量

以下环境变量在 cni/pkg/nodeagent/options.go 中注册,默认值定义于 options.go

环境变量 默认值 用途
HOST_PROBE_SNAT_IP 169.254.7.127 应用于 SNAT 后的主机探测(host probe)报文,便于 Pod 侧识别并跳过。覆盖默认 SNAT IP 时应使用 169.254.0.0/16 网段内任意地址
HOST_PROBE_SNAT_IPV6 fd16:9254:7127:1337:ffff:ffff:ffff:ffff IPv6 链路本地地址在默认设计上就抗冲突,因此几乎永远不需要覆盖

源码注释解释了其目的:为了在 Pod 内可靠地识别 kubelet 健康探测流量(与 kube-proxy 流量区分,二者源 IP 通常相同),ambient server 会在 host netns 中把已识别的 host probe 报文 SNAT 到固定的 APIPA/链路本地 IP。该 IP 具体取值无关紧要,只要不可路由、不与任何现有地址冲突即可。

六、权限要求与最小化能力集

无论运行在何种模式,Istio CNI Node Agent 都需要节点级特权权限;在默认禁止特权工作负载的受限环境中需要将其加入白名单。若启用了 sidecar repair 模式或 ambient 模式,node agent 还需要进入 Pod 网络命名空间并在其中执行网络配置的权限。

当 sidecar repair 或 ambient 模式任一启用时,容器启动时通过 drop: ALL 丢弃全部 Linux capabilities,再显式加回实际需要的能力。从 istio-cni DaemonSet 模板 可以核对实际配置(privileged: false):

Capability 用途
NET_ADMIN 允许访问 ipset 与路由表
NET_RAW 允许变更 iptables 的 nat
SYS_PTRACE repair 与 ambient 模式描述 Pod 网络命名空间所需
SYS_ADMIN ambient 与 repair 模式打开 /proc 下网络命名空间、进入 Pod netns 所需(没有更细粒度的替代能力)
DAC_OVERRIDE 丢弃全部能力后失去对他人拥有目录的读写权,而 hostPath 挂载需要写权限,此能力用于绕过该限制

README 中列出的三项为 CAP_SYS_ADMINCAP_NET_ADMINCAP_NET_RAW;从 chart 模板看,实际部署还额外加入了 SYS_PTRACEDAC_OVERRIDE 以满足 netns 描述与 hostPath 写入需求。

七、排障:收集 CNI 日志

CNI 插件由 kubelet 进程内的线程执行,插件日志最终进入 syslog 并归属 kubelet 进程。三种收集方式:

1. 通过 istioctl / helm 调整日志级别

# values 配置
values.global.logging.level="cni:debug,ambient:debug"

然后查看特定节点上 istio-cni DaemonSet Pod 的日志。由于插件日志已通过 UDS tee 到 DaemonSet(plugin.go),kubectl logs 中即可看到插件输出。

2. 从特定节点的 syslog 收集

在有 journalctl 的系统上,查看最近 1000 条 kubelet 日志并支持 vi 式检索:

$ journalctl -t kubelet -n 1000 | less

3. GKE 通过 Stackdriver 日志查看器

GKE 集群的日志会被 Stackdriver 收集,可通过项目日志查看器或 gcloud logging read 查询。例如抓取包含 "cmdAdd" 的最近 10 条 kubelet 日志:

$ gcloud logging read "resource.type=k8s_node AND jsonPayload.SYSLOG_IDENTIFIER=kubelet AND jsonPayload.MESSAGE:cmdAdd" --limit 10 --format json

八、开发说明

  • 硬性依赖 Linuxistio-cni 插件强依赖 Linux。虽为非 Linux 系统做了部分不可用构建的兼容,但并不普遍;实际上只支持在 Linux 上构建。非 Linux 开发环境请使用 make shell
  • 架构支持:Go 支持的大多数 Linux 架构均可工作,Istio 仅在 AMD64 与 ARM64 上做过测试。

开发相关入口可参考:

九、设计渊源

该 CNI 插件的实现框架基于 containernetworking 官方 sample plugin(链式插件、多 CNI 版本 prevResult 解析),部署与安装细节则主要借鉴 Calico CNI 插件的做法:

  • CNI 安装脚本容器化并以 DaemonSet 部署,其 DaemonSet + ConfigMap(install-cni 容器)与 RBAC 结构均参照 Calico 的对应清单设计;
  • 这种“install-cni DaemonSet 负责安装 + 节点侧短生命周期插件负责 ADD 事件”的双组件模式,正是本文 部署架构 一节描述的结构。

关键路径速查

内容 路径
本文源文档 cni/README.md
CmdAdd / 排除判定 cni/pkg/plugin/plugin.go
Redirect 参数与自动排除端口 cni/pkg/plugin/sidecar_redirect.go
Pod 信息提取(proxyUID 等) cni/pkg/plugin/kubernetes.go
HOST_PROBE_SNAT_IP 注册 cni/pkg/nodeagent/options.go
常量(UDS 名、kubeconfig、CNIBinDir) cni/pkg/constants/constants.go
安装器 cni/pkg/install/install.go
DaemonSet(capabilities 配置) manifests/charts/istio-cni/templates/daemonset.yaml
Ambient 生命周期架构文档 architecture/ambient/ztunnel-cni-lifecycle.md
登录后查看全文
热门项目推荐
相关项目推荐