Kubernetes 网络

环境信息

  • Centos7 3.10.0-1160
  • Docker Engine - Community 23.0.3
  • kubernetes 1.21.2-0
  • kubernetes-cni-0.8.7-0

Kubernetes 对任何网络实现都规定了以下要求: [1]

  • 所有 Pod 都可以在不使用网络地址转换 (NAT) 的情况下与所有其他 Pod 通信。

    容器之间直接通信,不需要额外的 NAT,不存在源地址伪装的情况

  • 所有节点都可以在没有 NAT 的情况下与所有 Pod 通信。

    Node 与容器直接通信,不需要额外的 NAT

  • Pod 认为自己的 IP 与其他人认为的 IP 相同。

CNI

CNI 是 Kubernetes 容器网络的标准,CNI 是 Kubernetes 和底层网络插件之间的一个抽象层,为 Kubernetes 屏蔽了底层网络实现的负责度,同时解耦了 Kubernetes 和具体的网络插件实现。

安装 CNI

$ yum install kubernetes-cni

$ rpm -qa | grep kube
kubeadm-1.21.2-0.x86_64
kubectl-1.21.2-0.x86_64
kubelet-1.21.2-0.x86_64
kubernetes-cni-0.8.7-0.x86_64

$ rpm -ql kubernetes-cni-0.8.7-0
/opt/cni
/opt/cni/bin
/opt/cni/bin/bandwidth
/opt/cni/bin/bridge
/opt/cni/bin/dhcp
/opt/cni/bin/firewall
/opt/cni/bin/flannel
/opt/cni/bin/host-device
/opt/cni/bin/host-local
/opt/cni/bin/ipvlan
/opt/cni/bin/loopback
/opt/cni/bin/macvlan
/opt/cni/bin/portmap
/opt/cni/bin/ptp
/opt/cni/bin/sbr
/opt/cni/bin/static
/opt/cni/bin/tuning
/opt/cni/bin/vlan

Kubernetes 要使用 CNI,需要在 kubelet 启动时配置启动参数 --network-plugin=cni(默认配置,可使用 systemctl status kubelet -l 查看启动参数)。

kubelet 从 --cni-config-dir (默认为 /etc/cni/net.d/)中读取网络插件的配置文件,并使用该文件中的 CNI 配置来配置每个 Pod 网络。如果该目录 (/etc/cni/net.d/)中有多个配置文件,则使用文件名字典序列中的第一个文件。

CNI 插件的二进制文件放置的目录是通过 kubelet 的 --cni-bin-dir 参数指定,默认为 /opt/cni/bin/

flannel

flannel 最早由 CoreOS 开发,它是容器编排系统中最成熟的网络插件之一。随着 CNI 概念的兴起,flannel 也是最早实现 CNI 标准的网络插件,CNI 标准也是由 CoreOS 提出的。flannel 的功能非常明确,主要解决以下容器跨接点访问的问题

  • 容器 IP 地址的重复问题。由于 Docker 等容器管理工具只是利用 Linux 内核的 network namespace 实现了网络隔离,各个节点上的容器 IP 由各个节点自动分配,可能出现重复。为了解决这个问题,flannel 设计了一种全局的 IP 地址分配机制,即使用 etcd 存储网段和节点 IP 之间的映射关系,然后配置 docker 只在当前节点对应的网段里面为容器分配 IP。
  • 容器 IP 地址的路由问题。flannel 使用多种后端(底层)技术(如 overlay 网络、Host-Gateway 网络等)解决了容器跨节点的直接通信问题。

flannel 在架构上分为管理面数据面

  • 管理面主要是 etcd。用于存储各个节点的 IP 及其上容器应该分配的网段
  • 数据面是每个节点上运行一个 flanneld 进程,负责从管理面读取节点 IP 及对应的网段信息,并根据这些信息对容器跨接点通信的数据包进行路由转发。

集群内所有的 flannel 节点共享一个大网段,比如 10.0.0.0/16,flanneld 启动后便会读取 etcd 中的信息,得知其他节点的 IP 及其使用的子网段,然后向 etcd 申请本节点可使用的子网段(在大网段中划分一个子网段),比如 10.0.0.1/24,并将该信息上报记录到 etcd。

flannel 目前已经支持的底层网络实现(backend)包括:

  • UDP
  • VXLAN
  • Host-Gateway
  • Alloc
  • AWS VPC
  • GCE 路由

其中,性能最好的是 Host-GatewayAWS VPCGCE 路由都需要 L2 网络支持,并且最好是接入云服务。Alloc 只为本机创建子网,多个节点上的子网之间不能通信。

flannel backend 详解

flannel 通过在每个节点上启动 flanneld 进程,负责每个节点上的子网划分,并将相关配置(如节点的 IP,划分的子网网段等)上报保存到 etcd,而具体的网络报文转发交给具体的 backend 实现。

flanneld 可以在启动时通过配置文件指定不同的 backend 进行跨节点的容器之间的网络报文的路由转发,目前比较成熟的 backend 有 UDPVXLANHost-Gateway,也有 AWS VPCGCE 路由 等专门针对云服务的 backend。目前 VXLAN 是官方最推崇的 backend 实现,Host-Gateway 是网络性能最好的 backend 实现,但是需要所有节点在同一个二层网络中互通。UDP 性能相对较差,建议用于测试及比较老的不支持 VXLAN 的 Linux 内核。

UDP

UDP 模式基于 Linux tun 设备

当采用 UDP 模式时,flanneld 启动时会通过打开 /dev/net/tun 的方式生成一个 tun 设备。当 flanneld 进程运行之后,查看系统上的网卡信息,可以看到多了 flannel0 的网卡,通过命令 ip -d link show flannel0,可以看到其类型为 tun 设备。此模式下 flanneld 进程监听 8285 端口。

flannel UDP 模式跨主机通信流程详解

本说明使用架构图形如下

以 ICMP 报文为例,container Acontainer B 的通信过程如下

  1. container A 发起 ICMP 请求报文,根据 container A 中的路由信息,报文被发送到网关 10.244.1.1,对应设备为 Host A 主机上面的 cni0 网卡。此时报文内容如下

  2. 到达 cni0 的报文,目的 IP 为 10.244.2.149,内核根据 host A 上的路由信息(10.244.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel0),应该将报文发送到 flannel0 网卡。

  3. flannel0 是个 tun 设备,数据会被 flanneld 接受,flanneld 会对数据包进行 UDP 封装。

    • 报文会被添加上 UDP 头部,其源端口为 host A 上的随机端口,目标端口为 8285
    • 添加 IP 头部,源 IP 为本节点出口 IP (eth0:172.16.130.140),目标 IP 为目标容器所在节点的 IP(eth0:172.16.130.146)。

      flanneld 进程是如何知道目标容器所在节点的 IP 地址?是通过 etcd 中的记录,flanneld 进程很容易根据目标容器的 IP 子网段,获取到对应节点的 IP

  4. flanneld 进程封装后的报文重新进入内核协议栈,内核根据主机上的路由信息,从主机 eth0 网卡发送出去,到达了目标主机 host B

  5. host B 主机内核接收到此网络数据报文,通过 UDP 端口号 8285 将数据包交给监听在此端口上的 flanneld 进程。

  6. host B 主机上的 flanneld 进程对数据报文解封,获取到下图所示的报文,在网络层,源 IP 为容器 A 的 IP:10.244.1.96,目标 IP 为容器 B 的 IP:10.244.2.194。解封后的数据报文重新进入内核路由,根据主机 B 的主机路由表(10.244.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0),报文被路由到 cni0

  7. cni0 网桥将数据包根据 MAC 地址(可以 ARP 寻址得到)将报文转发到容 B。

  8. 回程报文将按照上面的数据流原路返回。

纵观以上整个过程,flanneld 进程在其中主要有以下作用

  • UDP 封包解封包,根据目标容器的 IP,将其转发到正确的主机节点(IP)
  • 主机节点上动态更新路由表,根据 etcd 的数据刷新本节点路由表

容器 A 和容器 B 虽然物理上网络未相连,但是逻辑上确是在一个三层网络,这种在物理网络之上构建的上层网络称之为 overlay 网络或者隧道网络

flannel UDP 模式的缺点

  • 数据报文先通过 tun 设备从内核态复制到了用户态 flanneld 进行封装,然后再传输到了内核,仅一次网络传输就进行了 2 次用户态到内核态的切换,效率不高。
  • 因为报文封装的关系,flannel0 网卡的 MTU 要比主机 eth0 网卡小 28 个字节。

VXLAN

flanneld 进程配置为 vxlan 类型的 backend ,flanneld 进程启动时会在主机上创建名为 flannel.1 的网卡(VTEP),网卡命名格式遵循 flannel.[VNI],VNI 默认为 1。

通过以下命令查看 VTEP 设备 flannel.1 的信息。

$ ip -d link show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether 8a:32:fc:1d:b8:88 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 172.31.16.124 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

$ ip -d add show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UNKNOWN group default
link/ether 8a:32:fc:1d:b8:88 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 172.31.16.124 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.4.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::8832:fcff:fe1d:b888/64 scope link
valid_lft forever preferred_lft forever

从以上信息可以看到,flannel.1 类型为 vxlan,local IP 为 172.31.16.124,使用的是 UDP 端口 8472

$ netstat -anutp 
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:8472 0.0.0.0:* -

以上使用 netstat -anutp 命令的输出中,PID/Program name 显示的是 -,说明 8472 这个 UDP 端口不是由用户态的程序监听,而是 flannel 的 VXLAN 模式工作在内核态

Kubernetes 中,VXLAN 模式的 flanneld 的工作流程:

  1. flanneld 启动时,先确保 flannel.1 存在,若已存在则跳过,并将 VTEP 设备的相关信息(ip,节点 IP,MAC 地址等)上报到 etcd 中

  2. 当 flannel 网络中的其他节点加入集群并向 etcd 上报注册时,各个节点的 flanneld 会从 etcd 得到通知,并依次执行下面的步骤

  3. 在本节点添加一条新网段的路由信息,主要是让 Pod 中的流量能路由到 flannel.1

    $ route -n
    Kernel IP routing table
    Destination Gateway Genmask Flags Metric Ref Use Iface
    0.0.0.0 172.31.16.1 0.0.0.0 UG 100 0 0 eth0
    10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
    10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1
    10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1
    10.244.3.0 10.244.3.0 255.255.255.0 UG 0 0 0 flannel.1
    10.244.4.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
    172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
    172.31.16.0 0.0.0.0 255.255.240.0 U 100 0 0 eth0
  4. 在本节点添加一条新增节点的 VTEP 设备的静态 ARP 缓存

flannel VXLAN 模式跨主机通信流程详解

flannel VXLAN 模式时容器跨节点网络通信实现流程

  1. 同 UDP Backend 模式,容器 A 当中的 IP 包通过容器 A 内的路由表被发送到 cni0
  2. 到达 cni0 当中的 IP 包通过匹配 host A 当中的路由表发现通往 10.244.2.194 的 IP 包应该交给 flannel.1 接口
  3. flannel.1 作为一个 VTEP 设备,收到报文后将按照 VTEP 的配置进行封包,首先通过 etcd 得知 10.244.2.194 属于节点 B,并得到节点 B 的 IP,通过节点 A 当中的转发表得到节点 B 对应的 VTEP 的 MAC,根据 flannel.1 设备创建时的设置的参数(VNI、local IP、Port)进行 VXLAN 封包
  4. 通过 host A 跟 host B 之间的网络连接,VXLAN 包到达 host B 的 eth1 接口
  5. 通过端口 8472,VXLAN 包被转发给 VTEP 设备 flannel.1 进行解包
  6. 解封装后的 IP 包匹配 host B 当中的路由表(10.244.2.0),内核将 IP 包转发给 cni0
  7. cni0 将 IP 包转发给连接在 cni0 上的容器 B

在 VXLAN 模式下,数据包的封装解封装及路由转发都是由内核完成,flanneld 不再进行数据包的封装和路由转发,仅动态设置主机的路由表项、ARP 表及 FDB 表项。其效率相比 UDP 模式高效。

host-gw

要配置使用 host-gw 模式,将 Backend 中的 type 改为 host-gw 即可。

使用 host-gw Backend 的 Flannel 网络的网络包传输过程如下图所示:

  1. 同 UDP、VXLAN 模式一致,通过容器 A 的路由表, IP 包到达 cni0
  2. 到达 cni0 的 IP 包匹配到 host A 当中的路由规则(10.244.2.0),并且网关为 172.16.130.164,即 host B,所以内核将 IP 包发送给 host B(172.16.130.164)
  3. IP 包通过物理网络到达 host B 的 eth1
  4. 到达 host B eth1 的 IP 包匹配到 host B 当中的路由表(10.244.2.0),IP 包被转发给 cni0
  5. cni0 将 IP 包转发给连接在 cni0 上的容器 B

host-gw 模式其中一个局限性就是,由于是通过节点上的路由表来实现各个节点之间的跨节点网络通信,那么就得保证两个节点是可以直接路由过去的。按照内核当中的路由规则,网关必须在跟主机当中至少一个 IP 处于同一网段,故造成的结果就是采用 host-gw 这种 Backend 方式时集群中所有的节点必须处于同一个网络当中,这对于集群规模比较大时需要对节点进行网段划分的话会存在一定的局限性。另外一个则是随着集群当中节点规模的增大,flanneld 需要维护主机上成千上万条路由表的动态更新也是一个不小的压力。

采用 host-gw 模式后 flanneld 的唯一作用就是负责主机上路由表的动态更新。

Kubernetes 中 flannel 相关配置

CNI 调用 flannel 插件是通过其配置文件 /etc/cni/net.d/10-flannel.conflist

$ cat /etc/cni/net.d/10-flannel.conflist 
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}

flannel 安装好启动之后,查看 flannel 的配置,可以看到配置的网段信息。

$ cat /run/flannel/subnet.env 
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=8951
FLANNEL_IPMASQ=true

在 Kubernetes 中,要查看 flannel 详细的配置,可以查看以下 configmap

$ kubectl edit configmap -n kube-flannel kube-flannel-cfg
data:
cni-conf.json: |
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}

从中可以看到 flannel 使用的大网段 10.244.0.0/16,以及其使用的底层(后端)网络通信的实现技术(Backend

脚注