跳转至

研究 k8s 网络工作原理

背景

用 k8s 也有一段时间了,之前遇到过 iptables 等出现问题,导致 k8s 节点间网络出现问题,于是想研究一下 k8s 的网络工作原理。

Docker 网络

首先研究一下 Docker 网络连接是如何实现的。Docker 首先会创建一个 bridge,名为 bridge0:

$ ip a show docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c4:87:73:bf brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:c4ff:fe87:73bf/64 scope link
       valid_lft forever preferred_lft forever

默认情况下,每个容器都会有单独的一个 netns,然后创建一对 veth pair,一端留在 global netns,另一端放到容器中。在 global netns 中的 veth 端口会加入到 docker0 中:

$ ip a show dev veth3db9316
21: veth3db9316@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether e2:49:a6:2d:5a:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::e049:a6ff:fe2d:5abd/64 scope link
       valid_lft forever preferred_lft forever
$ brctl show docker0
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242c48773bf       no              veth3db9316

容器中的网络,在 veth 上 docker 会分配并配置一个地址(比如 172.17.0.2),然后设置默认路由 via 172.17.0.1。一方面,可以通过默认路由到 172.17.0.1 再通过 iptables NAT 访问外面的网络:

$ iptables-save -t nat
# Generated by xtables-save v1.8.2 on Sat Sep 18 10:44:49 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Sat Sep 18 10:44:49 2021

另一方面,因为连接不同容器的 veth 在同一个 bridge 下面,所以不同容器的可以认为在同一个二层网络中,自然可以互相访问。

K8s 网络

在 k8s 中,所有的 pod 都希望可以通过 IP 地址互联。一个思路是把各个节点上的 pod 通过类似 docker 的方法实现,即每个 netns 通过 veth 连接到一个 bridge 上,然后再想办法去路由在其它节点上的 pod。

因为我用 k3s 搭建 k8s 集群,它用的 cni 是 flannel。flannel 采用的是 vxlan 的方式来实现节点间的网络通信。

首先还是看看节点内的 pod 如何组网。

5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether 6a:4f:ff:8b:b1:b3 brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.1/24 brd 10.42.0.255 scope global cni0
       valid_lft forever preferred_lft forever
    inet6 fe80::7cf6:57ff:fed7:c49b/64 scope link
       valid_lft forever preferred_lft forever
6: vethc47d6140@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
    link/ether da:19:f8:48:f6:49 brd ff:ff:ff:ff:ff:ff link-netns cni-9d2a5120-16a3-453e-bf64-c4006c06c93b
    inet6 fe80::d819:f8ff:fe48:f649/64 scope link
       valid_lft forever preferred_lft forever

首先,flannel 给每个节点分配了一个 /24 的网段,比如第一个节点是 10.42.0.0/24,第二个是 10.42.1.0/24,依次类推。然后,节点内的 pod 就从这个网段里分配地址,比如 10.42.0.50/24,它的默认网关是 10.42.0.1。这些 veth 都会加入到 cni0 的 bridge 中。这一部分原理和 docker 是一样的,只不过名字不同了。也有相应的 iptables 规则:

$ iptables-save | grep MASQUERADE
-A POSTROUTING -s 10.42.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE --random-fully
-A POSTROUTING ! -s 10.42.0.0/16 -d 10.42.0.0/16 -j MASQUERADE --random-fully

那么,节点间网络如何实现呢?假如,我们要从第一个节点 pod 10.42.0.50/24 访问第二个节点的 pod 10.42.1.51/24,首先,pod 根据默认路由会发给 10.42.0.1/24,到达第一个节点的 cni0,然后查路由表:

$ ip r
10.42.0.0/24 dev cni0 proto kernel scope link src 10.42.0.1
10.42.1.0/24 via 10.42.1.0 dev flannel.1 onlink

可以看到,它会匹配 10.42.1.0/24 via 10.42.1.0 dev flannel.1 的路由。flannel.1 是一个 vxlan 的 interface:

$ ip a show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether b6:2f:39:4a:02:c0 brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::b42f:39ff:fe4a:2c0/64 scope link
       valid_lft forever preferred_lft forever

当这个 interface 接收到一个 packet 的时候,会查询 fdb:

$ bridge fdb show brport flannel.1
...

这个 fdb 中包括了 (MAC 地址,IP 地址) 的 tuple。当 flannel.1 收到一个 Ethernet Frame 的时候,如果目的地址匹配这里的 MAC 地址,就会直接把 Eth Frame 封装到 UDP 里面发给目的 IP 地址;否则,就会在这个表里面 broadcast。这样,第二个节点就会收到 packet 并且转给实际的 pod。

总结

总结一下 k8s 的网络互联的实现方法:节点内通过 bridge 实现,把链接各个 netns 的 veth 桥接起来;节点间划分为多个子网,子网间通过 flannel 的网关进行路由,flannel 网关间通过 vxlan 进行互联。

参考文档

技术干货 | 深入理解 flannel

一文看懂 k8s 的 Flannel 网络

评论