【笔记】阿里云 ENI Trunking 的 Pod 数据路径
ENI Trunking 解决的问题不是“怎样给 Pod 再分一个 IP”,而是“怎样让一台节点有限的物理网卡承载更多具有独立云网络身份的 Pod”。VLAN、Linux datapath 和策略路由都是为了把 Pod 流量正确映射回对应 Member ENI。
本文依据 Terway 官方文档与 2026-08-01 GitHub main 源码快照。Terway 支持多种 datapath,内核对象和规则会随配置与版本变化;文中的 tc/VLAN link 细节不能外推为所有 ACK 集群的固定形态。
1. 中心模型:云上身份与节点内转发分层
flowchart LR
P[Pod network namespace] --> D[节点内 datapath]
D --> T[Trunk ENI]
T --> H[云虚拟化网络]
H --> M[Member ENI 身份]
M --> V[VPC vSwitch / 路由 / 安全组]
每层回答不同问题:
| 层 | 责任 |
|---|---|
| Pod | 使用自己的 IP 和默认路由收发包 |
| Terway datapath | 在 Pod、节点路由和承载网卡之间转发 |
| Trunk ENI | 在一条节点侧 ENI 通道上复用多个 Member ENI |
| 云虚拟化网络 | 按 Member ENI/VLAN 关联恢复云上网络身份 |
| VPC | 执行路由、安全组和交换 |
关键不是包最终从哪张 Linux 设备发出,而是离开节点时携带的信息能否让云侧识别“它属于哪个 Member ENI”。
2. Trunk ENI 与 Member ENI
2.1 Trunk ENI 是承载通道
Trunk ENI 附着到 ECS 节点,Guest OS 能看到对应网络设备。它承担多个 Pod 网络身份的聚合流量,而不是给所有 Pod 提供同一个不可区分的身份。
2.2 Member ENI 是 Pod 的云侧身份
阿里云和 Terway 文档中常见 Member ENI,也可能在讨论中被称为 Branch ENI。它记录独立的 ENI ID、MAC、IP、vSwitch 与安全组配置,并关联到某个 Trunk ENI。
PodENI CR 会记录 Pod 使用的 ENI allocation,以及当前绑定的 ECS instance 和 trunk ENI ID。它是控制面期望与状态记录,不是 Linux network namespace 内的一张同名实体设备。
2.3 VLAN ID 是复用标签
云侧把一个 Member ENI 绑定到 Trunk ENI 时,会分配 VLAN ID。Trunk 链路上的 802.1Q tag 让同一承载通道中的帧可归属到不同 Member ENI。
1
2
Pod A → VLAN 101 → Member ENI A
Pod B → VLAN 205 → Member ENI B
VLAN ID 是链路内映射键,不是 Pod IP,也不是安全组 ID。它的意义来自控制面建立的 trunk + VLAN ↔ member ENI 关系。
3. 为什么只看 Pod IP 不够
IP 能帮助节点和 VPC 做三层路由,但 ENI 身份还关联 MAC、vSwitch、安全组和生命周期。若一条节点链路上只有源 IP,没有与 Member ENI 的明确绑定,云侧就难以按独立 ENI 身份执行这些能力。
Trunking 把两个维度分开:
- Pod IP 表示三层端点;
- VLAN/Member ENI 关联表示流量所属的云网络接口。
同一个 Pod packet 在节点内可能以普通 IP 包被路由,在 Trunk 边界才加上或保留 VLAN 语义。
4. 控制面如何把 Pod 映射到网络身份
4.1 PodNetworking 描述选择规则
用户通过 PodNetworking CR 指定:
- Pod 与 Namespace selector;
- 可用 vSwitch;
- security group IDs;
- IP allocation type 与释放策略。
控制面根据 selector 为 Pod 选择网络配置。一个 Pod 应避免被多个互相冲突的配置重复匹配。
4.2 PodENI 记录分配与绑定状态
控制面为匹配 Pod 创建或维护 PodENI,其中可看到:
1
2
3
Pod → Member ENI ID / MAC / IP
Member ENI → Trunk ENI ID / ECS instance
Pod → IP allocation lifecycle
节点侧 Terway 等待该资源进入可用绑定状态,再将 allocation 转换成 CNI datapath 配置。
4.3 CNI ADD 落地 Linux 网络
Pod 调度到节点后,Terway CNI 根据 allocation 和 datapath 配置创建接口、地址、路由、规则和 VLAN 处理。控制面决定“该用哪个云身份”,节点面负责“怎样把 Pod 包送到该身份”。
5. 先确认 datapath,不能只背一张拓扑
5.1 Terway 不是只有 veth + tc
当前源码在解析 CNI 配置时会根据 IP type、是否 trunk,以及 vlan_strip_type 等条件选择 datapath,包括 IPVlan、Vlan、policy route 等路径。
因此以下断言都过于绝对:
- Pod IP 一定只在 veth 端;
- Trunking 一定没有 VLAN 子接口;
- 所有版本都由同一组 tc filter 完成全部转发;
- 每个 Pod 一定有同样的 policy routing table。
5.2 Filter 路径
当采用 filter 方式处理 VLAN 时,当前源码会:
- 在 trunk device ingress 上确保 VLAN untagger;
- 使用 tc U32/filter action 处理 VLAN;
- 按 IPv4/IPv6 源地址创建 egress VLAN push 规则;
- 为失效 Pod IP 清理旧 filter。
这条路径适合用一张 trunk device 配合动态 filter 表示多个 Pod 到 VLAN 的映射。
5.3 VLAN link 路径
当 vlan_strip_type 选择 VLAN datapath 时,当前源码会创建 netlink.Vlan 设备,并把具体 VLAN ID 放进 link 配置。此时 Linux 802.1Q 子接口参与 tag 的处理。
所以“tc 与 VLAN 子接口二选一”的判断要放在具体配置和版本下,而不是当作 ENI Trunking 的产品定义。
5.4 IPVlan 与 veth 是另一个维度
Pod 侧可以通过 IPVlan 或其他 virtual interface 连接节点网络。它回答“Pod namespace 怎样接入 host”,VLAN 处理回答“host 怎样在 trunk 链路标识 Member ENI”。两者处在不同层,不应合并成一个开关理解。
6. Filter 路径中的 tc 位置
6.1 Ingress:把 trunk 帧交给节点协议栈
云侧送入节点的 trunk frame 带有对应 Member ENI 的 VLAN tag。filter 路径在 trunk device ingress 执行 pop,让后续节点协议栈按普通 IP packet 路由到 Pod。
1
2
3
4
5
云侧带 tag 帧
→ trunk device ingress
→ tc vlan pop
→ host routing
→ Pod link
当前 EnsureVlanUntagger 会检查/创建 clsact qdisc 和 ingress filter,并使用 VLAN pop action。它是当前 Terway filter datapath 的实现事实。
6.2 Egress:按 Pod 源地址恢复 Member ENI 标签
Pod 流量经节点路由选中 trunk device 后,egress filter 按 source IP 匹配,并执行 VLAN push:
1
2
3
4
5
6
Pod packet
→ host routing
→ trunk device egress
→ src PodIP 对应的 tc U32 rule
→ vlan push <VID>
→ 云侧映射为 Member ENI
当前源码为 IPv4 和 IPv6 使用不同 protocol 与 filter priority,说明 tag rule 是按协议族显式构造的。不能假设一条 IPv4 filter 会自然覆盖 IPv6。
6.3 tc 只处理本层职责
tc action 可以加减 VLAN tag,却不替代:
- Pod IPAM;
- 路由选择;
- Member ENI 控制面绑定;
- 云侧安全组求值。
看到 trunk 上有 tag 规则,不等于完整数据面已经正确。
7. 路由与策略路由在解决什么
7.1 路由先决定下一跳和出接口
Linux 发送 packet 时先根据 policy rules 和 route tables 选择路径,之后才到对应 netdevice 的 egress tc。故 egress tag 并不能替代“让 Pod 流量走到正确 trunk device”的路由配置。
7.2 源地址规则维持返回路径
多 ENI 或多网关节点里,main table 的默认路由可能指向主 ENI。若 Pod 从 trunk 收到流量,却通过另一个 ENI 返回,会破坏源地址、云身份或反向路径预期。
Terway 会按 ENI index 等信息生成 route table,并为相关 Pod 源地址安装规则,使返回流量选择对应 ENI gateway。
1
2
ip rule: from <PodIP> lookup <eni-table>
ip route: default via <eni-gateway> dev <trunk-device> table <eni-table>
实际规则还可能包含 Pod host route、IPv6、onlink 和优先级等细节,应以节点现场为准。
7.3 local table 不等于所有 Pod 包都走 INPUT
Linux policy routing 通常先查 local table,用于本机 local、broadcast 等路由。Pod IP 配置在 Pod namespace 或特定虚拟设备后,host 如何判断 local/forward 取决于实际 datapath、路由和 namespace。
不能仅凭“Pod IP 曾出现在节点配置中”断言 packet 必然进入 INPUT;应结合:
1
2
3
4
ip rule show
ip route show table all
ip netns exec <ns> ip addr
ip netns exec <ns> ip route
8. 入方向完整链路
以 filter datapath 的概念链路为例:
sequenceDiagram
participant V as VPC
participant H as 云虚拟化网络
participant T as Trunk device
participant K as Host routing
participant P as Pod
V->>H: 目标为 Member ENI IP
H->>H: 查 Member ENI 与 VLAN 关联、执行云侧策略
H->>T: 通过 trunk 注入带 VLAN tag 的帧
T->>T: ingress tc pop
T->>K: 普通 IP packet
K->>P: 按 Pod route 转发
这里至少有三次映射:目标 IP 找 Member ENI、Member ENI 找 trunk/VLAN、节点 route 找 Pod link。任何一层缺失都会表现为 Pod 不通,但排障位置完全不同。
9. 出方向完整链路
sequenceDiagram
participant P as Pod
participant K as Host routing
participant T as Trunk device
participant H as 云虚拟化网络
participant V as VPC
P->>K: src=PodIP 的 packet
K->>K: ip rule + ENI route table
K->>T: 选中 trunk device
T->>T: egress tc 按 src IP push VLAN
T->>H: 带 VLAN tag 的帧
H->>H: 还原 Member ENI 身份并执行云侧策略
H->>V: 进入 VPC 转发
若采用 VLAN link datapath,tag 的具体加减位置会变化,但“Pod packet 必须映射到正确 Member ENI 身份”的不变量不变。
10. 安全组在哪一层生效
10.1 安全组附着到 Member ENI 配置
PodNetworking.spec.securityGroupIDs 参与创建或选择 Pod 对应 Member ENI。它让不同 Pod 可以获得不同的云侧安全组边界。
10.2 Guest 内 tc 不是安全组执行器
tc filter 在这里负责 VLAN 标记,不负责解释阿里云 security group rules。安全组由云平台数据面基于 Member ENI 身份执行。
因此:
- tag 正确不代表安全组一定 Allow;
- Linux iptables/eBPF Allow 不代表云安全组一定 Allow;
- 云安全组 Allow 也不代表 Kubernetes NetworkPolicy 或应用监听一定 Allow。
10.3 NetworkPolicy 是独立控制层
Terway 还可以实现 Kubernetes NetworkPolicy,但它与 ENI security group 的主体、规则模型和执行位置不同。排障时应分别验证 Pod/Node 内策略和云侧 ENI 策略。
11. 现场排障按映射链检查
11.1 控制面
1
2
3
kubectl get podnetworkings.network.alibabacloud.com -o yaml
kubectl get podenis.network.alibabacloud.com -A -o yaml
kubectl get node <node> -o yaml
确认 Pod 命中了哪个配置、Member ENI allocation、trunk ID、binding phase 和 security groups。
11.2 Pod namespace
1
2
3
ip addr
ip route
ip link
确认 Pod IP、接口类型、默认路由和邻居关系。不要先假设一定是 veth 或 IPVlan。
11.3 Node datapath
1
2
3
4
5
6
ip -d link show
ip rule show
ip route show table all
tc qdisc show
tc filter show dev <trunk> ingress
tc filter show dev <trunk> egress
先识别当前 datapath,再看对应对象。若配置走 VLAN link,却只检查 tc egress filter,会得到错误结论。
11.4 云侧
核对 trunk 与 Member ENI 绑定、VLAN ID、vSwitch、IP、security groups 和实例归属。节点内配置看起来正确但云侧绑定未完成时,packet 仍无法以预期身份进入 VPC。
12. 常见误区
12.1 “Branch ENI 会作为独立设备出现在 Guest”
Trunk 模式的价值正是用一条承载通道复用 Member ENI。Guest 中看到的 Linux link 是 Terway datapath 的实现对象,不应简单与云控制面 Member ENI 一一等同。
12.2 “VLAN ID 就是 Pod 网络隔离策略”
VLAN 主要标识 trunk 内逻辑通道。安全组、NetworkPolicy 和路由分别在其他层表达策略。
12.3 “Trunking 一定使用 tc,不会创建 VLAN link”
当前 Terway 同时存在 filter 与 VLAN datapath 分支。必须先看 vlan_strip_type 和现场 link/filter。
12.4 “tc 打 tag 后无需策略路由”
tc egress 只有 packet 已被路由到该 device 才会运行。多 ENI 环境仍需正确的 source-based routing 与返回路径。
12.5 “Pod 通则所有策略都正确”
连接可能只覆盖同节点、同安全组或 IPv4。需要分别验证跨节点、出 VPC、IPv6、NetworkPolicy 和安全组边界。
13. 复习索引
- Trunk ENI 是承载通道,Member ENI 是 Pod 对应的云侧网络身份;
- VLAN ID 把 trunk frame 映射到 Member ENI,不等于 Pod IP 或安全组;
PodNetworking选择 vSwitch/安全组/IP 策略,PodENI记录 allocation 与绑定;- Terway 有多种 datapath,当前既有 tc filter,也有 Linux VLAN link 路径;
- 路由决定 packet 去哪张设备,VLAN 处理决定它以哪个 Member ENI 身份离开;
- 安全组在云侧按 ENI 身份执行,和节点内 NetworkPolicy 是两层控制;
- 排障应沿 Pod → node datapath → trunk/member binding → VPC 逐层验证。
14. 核验入口
- Terway
docs/terway-trunk.md:trunk mode、PodNetworking和PodENI; plugin/terway/cni.go:按 IP type、trunk 和vlan_strip_type选择 datapath;plugin/driver/utils/utils_linux.go:当前 tc VLAN pop/push 实现;plugin/driver/vlan/vlan.go:Linux VLAN link datapath;daemon/rule_linux.go与plugin/datapath:policy route 生成与同步;- 阿里云 ACK/Terway 官方文档:实例规格、功能开关、限制和生产配置。