Kubernetes CNI 网络(cni0)
- Kubernetes 通过 CNI(Container Network Interface)规范抽象网络管理,允许第三方插件(如 Flannel、Calico)接管网络。
- Docker 仅用于运行容器,但网络由 CNI 插件接管。
什么是 veth pair?
- veth pair(虚拟以太网设备对) 是 Linux 内核提供的一种虚拟网络设备,由两个互联的虚拟网卡组成:
- 一端在 容器(Pod)的网络命名空间中,命名为
eth0
。 - 另一端在 宿主机的根命名空间 中,命名为 vethXXXX(如 veth78b826c3@if2)
- 一端在 容器(Pod)的网络命名空间中,命名为
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox1-857448d9ff-shdfz 1/1 Running 0 84s 10.244.2.2 node02 <none> <none>
可以看到pod在node02节点上
kubectl exec -it busybox1-857448d9ff-shdfz -- cat /sys/class/net/eth0/iflink
6 #表明接口的序号为9,然后在node02节点查看接口号
ip link show
6: veth78b826c3@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether da:aa:84:90:7c:a9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 核心作用:
- 打破网络命名空间的隔离,允许容器与宿主机及其他容器通信。
- 类似于一根“虚拟网线”,将容器的流量引出到宿主机的网络栈。
- veth pair 的工作原理
- 数据流向:
- 当 Pod 发送数据包时,流量通过
eth0
(Pod 内的veth
端)传输到宿主机的 vethXXXX端。
- 当 Pod 发送数据包时,流量通过
- 数据流向:
flannel环境
关于flannel环境我们只讲解VXLAN模型(默认)的流程,host-gw模型不经过封包解包比较简单,想了解的可以查看【flannel】章节
同一node节点上的pod互通
解析案例环境
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox1-857448d9ff-shdfz 1/1 Running 0 84s 10.244.2.2 node02 <none> <none>
busybox2-5c8f48d797-kk8mx 1/1 Running 0 10s 10.244.2.3 node02 <none> <none>
可以看到busybox1(10.244.2.2)和busybox2(10.244.2.3)在同一个节点,busybox1(10.244.2.2)访问busybox2(10.244.2.3)
图解
通信原理详解
- busybox1(10.244.2.2)发起请求,目标IP:10.244.2.3
- 数据包在busybox1中进行路由
ip route
default via 10.244.2.1 dev eth0
10.244.0.0/16 via 10.244.2.1 dev eth0
10.244.2.0/24 dev eth0 scope link src 10.244.2.2 #目标IP为10.244.2.3,走这条路由到eth0网卡
- 数据包在busybox1容器的eth0网卡通过veth pair到达veth78b826c3@if2接口
kubectl exec -it busybox1-857448d9ff-shdfz -- cat /sys/class/net/eth0/iflink
6 #表明接口的序号为6,然后在node02节点查看接口号
ip link show
6: veth78b826c3@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether da:aa:84:90:7c:a9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 数据包从veth78b826c3@if2接口通过网桥到达cni0网桥
验证网桥关系:
brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.22bf3056c6a9 no veth78b826c3(veth78b826c3@if2接口确实是和cni0网桥是绑定关系)
veth8dcd4c25
- 数据包从cni0网桥根据目标IP10.244.2.3通过网桥到达veth8dcd4c25@if2接口
验证网桥关系:
brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.22bf3056c6a9 no veth78b826c3
veth8dcd4c25(veth8dcd4c25@if2接口确实是和cni0网桥是绑定关系)
- 数据包从veth8dcd4c25@if2接口通过veth pair到达busybox2(10.244.2.3)的eth0网卡
kubectl exec -it busybox2-5c8f48d797-kk8mx -- cat /sys/class/net/eth0/iflink
7 #表明接口的序号为9,然后在node02节点查看接口号
ip link show
7: veth8dcd4c25@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether 6a:df:3f:58:5d:e0 brd ff:ff:ff:ff:ff:ff link-netnsid 1
不同node节点上的pod互通
解析案例环境
kubectl get pod -o wide
busybox1-857448d9ff-shdfz 1/1 Running 0 92m 10.244.2.2 node02 <none> <none>
busybox3-c997b9cc4-tlvrq 1/1 Running 0 8s 10.244.1.2 node01 <none> <none>
可以看到busybox1(10.244.2.2)和busybox3(10.244.1.2)在同一个节点,busybox1(10.244.2.2)访问busybox3(10.244.1.2)
图解
通信原理详解
- busybox1(10.244.2.2)发起请求,目标IP:10.244.1.2
- 数据包在busybox1中进行路由
ip route
default via 10.244.2.1 dev eth0 #目标IP为10.244.1.2,走这条默认路由到eth0网卡
10.244.0.0/16 via 10.244.2.1 dev eth0
10.244.2.0/24 dev eth0 scope link src 10.244.2.2
- 数据包在busybox1容器的eth0网卡通过veth pair到达veth78b826c3@if2接口
kubectl exec -it busybox1-857448d9ff-shdfz -- cat /sys/class/net/eth0/iflink
6 #表明接口的序号为6,然后在node02节点查看接口号
ip link show
6: veth78b826c3@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether da:aa:84:90:7c:a9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 数据包从veth78b826c3@if2接口通过网桥到达cni0网桥
验证网桥关系:
brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.22bf3056c6a9 no veth78b826c3(veth78b826c3@if2接口确实是和cni0网桥是绑定关系)
veth8dcd4c25
- 数据包从cni0网桥通过路由到达flannel.1网卡
查看node02路由:
ip route
default via 100.100.137.1 dev ens192 proto static metric 100
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink #目标IP为 10.244.1.2,走这条路由到flannel.1网卡
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1
100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
数据包在 node02 的flannel.1网卡进行 VXLAN 封装之后路由到ens192网卡
FDB 表查询: Node02 查询 FDB 表,获取目标 VTEP(
10.244.1.0/24
)对应的物理 IP(100.100.137.201
)和 MAC 地址:# 查看 node02 的 FDB 表 bridge fdb show dev flannel.1 de:65:a0:34:f0:d6 dst 100.100.137.200 self permanent 42:88:6a:88:a9:3f dst 100.100.137.201 self permanent #根据flannel为每个node分配的子网和目标ip进行匹配,找到 10.244.1.0/24 对应的条目是这条
封装 VXLAN 数据包:
- 外层 IP 头:源 IP 100.100.137.202,目标 IP 100.100.137.201。
- 外层 UDP 头:源端口随机,目标端口 8472(Flannel 默认 VXLAN 端口)。
- VXLAN 头:VNI(VXLAN Network Identifier)为
1
。 - 原始数据包:源 IP 10.244.2.2,目标 IP 10.244.1.2。
封装之后路由到ens192网卡
查看路由: ip route default via 100.100.137.1 dev ens192 proto static metric 100 10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink 10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100 #封装之后目标IP为100.100.137.201,走这条路由 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 [root@node02 ~]# bridge fdb show dev flannel.1 de:65:a0:34:f0:d6 dst 100.100.137.200 self permanent 42:88:6a:88:a9:3f dst 100.100.137.201 self permanent
数据包从 node02 的ens192网卡进行物理网络传输到node01的ens192网卡
- 数据包到达node01的ens192网卡之后进行识别
内核根据 VXLAN Network Identifier (VNI)(默认 1)判断该数据包属于哪个 VXLAN 设备(这里是 flannel.1)。
流量到达 node01 flannel.1的网卡进行VXLAN解封装之后路由到cni0网桥
node01 的 flannel.1 接口监听 UDP 端口 8472,接收数据包后:
- 验证 VNI 是否匹配(1)。
- 剥离外层 IP、UDP 和 VXLAN 头,得到原始数据包。
- 根据目标 IP 10.244.1.2,通过路由将数据包交给 cni0 网桥。
查看路由: ip route default via 100.100.137.1 dev ens192 proto static metric 100 10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 #解封装之后目标IP为10.244.1.2,走这条路由到cni0网桥 10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink 100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.201 metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
数据包从cni0网桥到达 busybox3(10.244.1.2)容器
- cni0 网桥通过 MAC 地址表以网桥方式将数据包转发到 busybox3容器 的 veth42779e36@if2接口
查看网桥绑定情况: brctl show cni0 bridge name bridge id STP enabled interfaces cni0 8000.1a70cf92258b no veth42779e36(可以看到是和veth42779e36@if2接口绑定的状态)
- veth42779e36@if2接口通过veth pair到达busybox3(10.244.1.2)容器
kubectl exec -it busybox3-c997b9cc4-tlvrq -- cat /sys/class/net/eth0/iflink 6 #表明接口的序号为6,然后在node01节点查看接口号 ip link show 6: veth42779e36@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default link/ether 26:a9:23:fb:7a:c7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
calico环境
关于calico环境我们只讲解IPIP模型(默认)的流程
同一node节点上的pod互通
解析案例环境
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox1-857448d9ff-shdfz 1/1 Running 1 (125m ago) 25h 10.244.140.65 node02 <none> <none>
busybox2-5c8f48d797-kk8mx 1/1 Running 1 (125m ago) 25h 10.244.140.67 node02 <none> <none>
可以看到busybox1(10.244.140.65)和busybox2(10.244.140.67)在同一个节点,busybox1(10.244.140.65)访问busybox2(10.244.140.67)
图解
通信原理详解
- busybox1(10.244.140.65)发起请求,目标IP:10.244.140.67
- 数据包在busybox1中进行路由
kubectl exec -it busybox1-857448d9ff-shdfz -- sh
ip route
default via 169.254.1.1 dev eth0 #目标IP为10.244.140.67,走这条路由到eth0网卡
169.254.1.1 dev eth0 scope link
为什么容器内的默认网关是 169.254.1.1?
Calico 会配置一个虚拟的默认网关为 169.254.1.1,这不是实际存在的设备,而是 由 Calico CNI 插件在设置容器网络时写入的路由配置。
- 数据包在busybox1容器的eth0网卡通过veth pair到达cali16cfa08a6f9@if3接口
kubectl exec -it busybox1-857448d9ff-shdfz -- cat /sys/class/net/eth0/iflink
4 #表明接口的序号为4,然后在node02节点查看接口号
4: cali16cfa08a6f9@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 数据包从cali16cfa08a6f9@if3接口通过路由到calibc123ec5773@if4接口
ip route
default via 100.100.137.1 dev ens192 proto static metric 100
blackhole 10.244.140.64/26 proto bird
10.244.140.65 dev cali16cfa08a6f9 scope link
10.244.140.66 dev caliae3c1d6879e scope link
10.244.140.67 dev calibc123ec5773 scope link #目标IP为10.244.140.67,走这条路由
10.244.196.128/26 via 100.100.137.201 dev tunl0 proto bird onlink
10.244.241.64/26 via 100.100.137.200 dev tunl0 proto bird onlink
100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 数据包从calibc123ec5773@if4接口通过veth pair到busybox2容器
kubectl exec -it busybox2-5c8f48d797-kk8mx -- cat /sys/class/net/eth0/iflink
7
7: calibc123ec5773@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 2
不同node节点上的pod互通
解析案例环境
kubectl exec -it busybox1-857448d9ff-shdfz -- sh
kubectl get pod -o wide
busybox1-857448d9ff-shdfz 1/1 Running 1 (174m ago) 26h 10.244.140.65 node02 <none> <none>
busybox3-c997b9cc4-tlvrq 1/1 Running 1 (175m ago) 25h 10.244.196.131 node01 <none> <none>
可以看到两个pod在不同的节点,busybox1(10.244.140.65)访问busybox3(10.244.196.131)
图解
通信原理详解
- busybox1(10.244.140.65)发起请求,目标IP:10.244.196.131
- 数据包在busybox1中进行路由
ip route
default via 169.254.1.1 dev eth0 #目标IP为10.244.196.131,走这条路由到eth0网卡
169.254.1.1 dev eth0 scope link
为什么容器内的默认网关是 169.254.1.1?
Calico 会配置一个虚拟的默认网关为 169.254.1.1,这不是实际存在的设备,而是 由 Calico CNI 插件在设置容器网络时写入的路由配置。
- 数据包在busybox1容器的eth0网卡通过veth pair到达cali16cfa08a6f9@if3接口
kubectl exec -it busybox1-857448d9ff-shdfz -- cat /sys/class/net/eth0/iflink
4 #表明接口的序号为4,然后在node02节点查看接口号
4: cali16cfa08a6f9@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 数据包从cali16cfa08a6f9@if3接口通过路由到达tunl0网卡
ip route
default via 100.100.137.1 dev ens192 proto static metric 100
blackhole 10.244.140.64/26 proto bird
10.244.140.65 dev cali16cfa08a6f9 scope link
10.244.140.66 dev caliae3c1d6879e scope link
10.244.140.67 dev calibc123ec5773 scope link
10.244.196.128/26 via 100.100.137.201 dev tunl0 proto bird onlink #目标IP为10.244.196.131,走这条路由到达tunl0网卡
10.244.241.64/26 via 100.100.137.200 dev tunl0 proto bird onlink
100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
数据包在tunl0网卡进行IPIP隧道封装
- 进行路由查询获取目标IP(10.244.196.131)对应的node IP (100.100.137.201)
ip route default via 100.100.137.1 dev ens192 proto static metric 100 blackhole 10.244.140.64/26 proto bird 10.244.140.65 dev cali16cfa08a6f9 scope link 10.244.140.66 dev caliae3c1d6879e scope link 10.244.140.67 dev calibc123ec5773 scope link 10.244.196.128/26 via 100.100.137.201 dev tunl0 proto bird onlink #目标IP为10.244.196.131,走这条路由,可以看到网关为:100.100.137.201 10.244.241.64/26 via 100.100.137.200 dev tunl0 proto bird onlink 100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 封装数据包:
- 外层 IP 头: 源 IP 100.100.137.202,目标 IP 100.100.137.201。
- 内层原始包: 源IP 10.244.140.65, 目标IP 10.244.196.131 。
- 隧道设备: tunl0(MTU=1480)
- 协议:ipip-proto-4
数据包经过tunl0封装之后路由到ens192网卡
ip route
default via 100.100.137.1 dev ens192 proto static metric 100
blackhole 10.244.140.64/26 proto bird
10.244.140.65 dev cali16cfa08a6f9 scope link
10.244.140.66 dev caliae3c1d6879e scope link
10.244.140.67 dev calibc123ec5773 scope link
10.244.196.128/26 via 100.100.137.201 dev tunl0 proto bird onlink
10.244.241.64/26 via 100.100.137.200 dev tunl0 proto bird onlink
100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.202 metric 100 #数据包封装之后目标ip为100.100.137.201,走这条路由
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 数据包从node02的ens192网卡传输到node01的ens192网卡
- 数据包到达 node01 的ens192网卡自动送到 node01的tunl0网卡
为什么数据包会自动送到 tunl0?
IPIP 协议绑定:
tunl0 设备在内核注册为 IPIP 隧道的端点,所有协议号为 4 的 IP 包会自动递交给它。
类似 HTTP 流量交给 80 端口的服务。
数据包到达 node01tunl0网卡进行解封装之后通过路由到达calibd1d74ace38@if4接口
- 剥离外层封装,得到原始数据包。
- 根据目标 IP 10.244.196.131 ,通过路由将数据包交给calibd1d74ace38@if4接口 。
ip route default via 100.100.137.1 dev ens192 proto static metric 100 10.244.140.64/26 via 100.100.137.202 dev tunl0 proto bird onlink blackhole 10.244.196.128/26 proto bird 10.244.196.129 dev cali48e9a291abc scope link 10.244.196.130 dev calif738bc18da9 scope link 10.244.196.131 dev calibd1d74ace38 scope link #解封装之后真实目标IP为10.244.196.131,走此条路由 10.244.241.64/26 via 100.100.137.200 dev tunl0 proto bird onlink 100.100.137.0/24 dev ens192 proto kernel scope link src 100.100.137.201 metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 数据包从calibd1d74ace38@if4接口通过veth pair交给到busybox3容器
为什么交给busybox3容器? kubectl exec -it busybox3-c997b9cc4-tlvrq -- cat /sys/class/net/eth0/iflink 7 #表名接口的序号为7,然后在node01节点查看接口号 ip link show 7: calibd1d74ace38@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 2