Kubernetes下Flannel网络

现在各大公有云的 k8s 网络插件基本用的都是 vxlan,我们也需要对这个进行一下详细了解,以便用于公司的正式生产环境。

一、原理

首先,kubernetes的网络模型:

image-20220317101247744

包含三要素:

  • 所有的容器(container)之间能够在不使用 NAT 的情况下互相通讯

  • 所有的节点(Node)能够在不使用 NAT 的情况下跟所有的容器(container)互相通讯(反之容器访问节点亦然)

  • 容器(container)中的IP地址,在容器内和容器外面看起来是一样的

那来到 flannel,它是一种 Overlay network 覆盖网络,盖在原有的 Node 网络基础上: image-20220317101802355

上图要仔细分析, K8S 中存在三个+一个网络段:

  • node 网络段,上图是 172.20.32.0/19,这是基础网络段
  • pod 网络段,100.96.0.0/16,2¹⁶(65536)个IP,这是由 flannel 产生的 overlay network,所有的 pod 都站在一个大广场上,互相可见
  • svc 网络段,上图未提,我们需要知道,想要把 pod 的 ip 给固定下来,就得使用 svc 来分配固定的域名,这个是由 iptables 来维护的
  • In-Host docker network网络段,这是每个 Node 主机的单独网络段,flannel给每个 Node 主机划分了一个 100.96.x.0/24 段,然后在 etcd 内进行维护,来避免不同的 Node 主机分配IP冲突。

综述:flannel 为每个 Node 分配一个 subnet,容器(container)从此 subnet 中分配 IP,这些 IP 可以在 Node 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。

每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个 Node 上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。

数据包如何在主机间转发是由 backend 实现的。flannel 提供了多种 backend,最常用的有 vxlan 和 host-gw,udp 万万不可使用。

仔细分析一下两个不同主机上的container跨主机互相通讯的过程:

image-20220317105412532

container-1 首先建立 IP 包, src: 100.96.1.2 -> dst: 100.96.2.3, 包发到 docker0 网桥,docker0 是 container-1 的缺省 gateway 网关。

在每个 Node 上,flannel 都会跑一个flanneld的守护进程,它会在 Linux 的 kernel 路由表中建立若干条路由, Node1 的路由表如下:

1admin@ip-172-20-33-102:~$ ip route
2default via 172.20.32.1 dev eth0
3100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.1.0
4100.96.1.0/24 dev docker0  proto kernel  scope link  src 100.96.1.1
5172.20.32.0/19 dev eth0  proto kernel  scope link  src 172.20.33.102

对照路由表,dst 100.96.2.3 的路由会落到 100.96.0.0/16 这一条上,也就是说会落到 flannel0 这个设备上继续转发出去。

flannel0 呢本质上是一个由flanneld 进程建立的 TUN 虚拟网卡设备:

  • 写TUN:当 IP 包写到flannel0后,会转发到 kernel,然后 kernel 查路由表再转发
  • 读TUN:当 IP 包首先到达核心,路由表显示下一跳是flannel0虚拟网卡,kernel会直接把包发到产生这个虚拟网卡的进程去,也就是flanneld进行读包

看上图,IP 包首先到达 Docker0,因为它是网关,然后查内核路由表,到达flannel0虚拟网卡,然后就到达flanneld进程读包, 这时候flanneld会做什么呢?

flanneld从 etcd 中获得各个主机网段对应的节点信息:

1admin@ip-172-20-33-102:~$ etcdctl ls /coreos.com/network/subnets
2/coreos.com/network/subnets/100.96.1.0-24
3/coreos.com/network/subnets/100.96.2.0-24
4/coreos.com/network/subnets/100.96.3.0-24
5
6admin@ip-172-20-33-102:~$ etcdctl get /coreos.com/network/subnets/100.96.2.0-24
7{"PublicIP":"172.20.54.98"}

于是乎 Node1 上面的 flanneld 进程得知 100.96.2.3 IP对应的网段是 100.96.2.0/24 ,再进一步对应到 Node2 的公网IP 172.20.54.98,然后它就会继续封装这个IP包,用UDP或者VXLAN,把这个 IP 包再包裹一层封起来送到 Node2 的 flanneld 进程去。

Node2:包首先从网卡到达 Node2 的核心 kernel,路由表显示下一跳是flannel0 虚拟网卡,包就转发到 flanneld 进程读包,flanneld接收到包后,就会做拆包,拆完包直接写包到 TUN,然后包到达本地核心路由,再查路由表转发到docker0,最终到达 container-2

我们同样可以查看 Node2 的路由表,应该显示如下结果:

1admin@ip-172-20-54-98:~$ ip route
2default via 172.20.32.1 dev eth0
3100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.2.0
4100.96.2.0/24 dev docker0  proto kernel  scope link  src 100.96.2.1
5172.20.32.0/19 dev eth0  proto kernel  scope link  src 172.20.54.98

这样整个过程就明晰了。

道理明晰了,下面我们就需要来实际操作了。

二、实战

直接在 k8s 里装就很简单,用 yaml 一步操作就行了,这里我们不做任何介绍。

我们下面说的是如何单独把 flannel 单独拿出来使用:

1、首先要装etcd

参照之前的帖子:Etcd单节点应用

这里也给出不用Docker,用systemctl来运行的方案:

准备工作,关闭selinux,打开包转发:

1echo 1 > /proc/sys/net/ipv4/ip_forward
2#或者修改/etc/sysctl.conf,然后sysctl -p
3#net.ipv4.ip_forward = 1
4
5systemctl disable firewalld.service
6systemctl stop firewalld.service
7
8iptables -P FORWARD ACCEPT

安装 etcd :

 1yum install etcd -y
 2
 3cp /etc/etcd/etcd.conf/etc/etcd/etcd.conf.bak
 4
 5vi /etc/etcd/etcd.conf
 6ETCD_LISTEN_PEER_URLS="http://172.16.9.110:2380"
 7ETCD_LISTEN_CLIENT_URLS=http://172.16.9.110:2379,http://127.0.0.1:2379
 8ETCD_NAME="default"
 9ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.16.9.110:2380"
10ETCD_ADVERTISE_CLIENT_URLS=http://172.16.9.110:2379
11
12systemctl enable --now etcd

配置 etcd:

1vi /root/etcd.sh
2{ "Network": "10.10.0.0/16","SubnetLen": 24,"Backend": {"Type":"vxlan"} }
3
4etcdctl --endpoints=http://172.16.9.110:2379 set kubernetes‑cluster/network/config < /root/etcd.sh
5
6etcdctl ls kubernetes‑cluster/network/config
7
8curl -s http://172.16.9.110:2379/v2/keys/kubernetes‑cluster/network/subnets 

解释一下:pod 的网段是 10.10.0.0/16,掩码是 /16 ,本地 Node 主机的掩码是/24,也就是说一台宿主机上最多产256台container。

然后 etcd 的 key 是 kubernetes‑cluster/network

2、然后装 flannel:

注意 etcd 的配置跟上面一致:

1yum install flannel -y
2
3cp /etc/sysconfig/flanneld/etc/sysconfig/flanneld.bak
4
5vi/etc/sysconfig/flanneld
6FLANNEL_ETCD_ENDPOINTS=http://172.16.9.110:2379
7FLANNEL_ETCD_PREFIX="kubernetes‑cluster/network"
8
9systemctl enable --now flanneld

3、重启Docker

编辑docker启动配置文件:

 1cat /run/flannel/subnet.env
 2FLANNEL_NETWORK=10.10.0.0/16
 3FLANNEL_SUBNET=10.10.1.1/24
 4FLANNEL_MTU=1450
 5FLANNEL_IPMASQ=false
 6
 7/usr/lib/systemd/system/docker.service
 8
 9# Add: --bip= and --mtu=
10
11vi /usr/lib/systemd/system/docker.service
12dockerd --bip=$FLANNEL_SUBNET --mtu=$FLANNEL_MTU
13
14或者
15cat /run/flannel/docker
16DOCKER_OPT_BIP="‑‑bip=10.10.1.1/24"
17DOCKER_OPT_IPMASQ="‑‑ip‑masq=true"
18DOCKER_OPT_MTU="‑‑mtu=1450"
19DOCKER_NETWORK_OPTIONS=" ‑‑bip=10.10.1.1/24 ‑‑ip‑masq=false ‑‑mtu=1450 "
20
21cat /etc/systemd/system/docker.service.d/docker.conf
22ServiceEnvironmentFile=‑/etc/sysconfig/docker
23EnvironmentFile=‑/etc/sysconfig/docker‑storage
24EnvironmentFile=‑/etc/sysconfig/docker‑network
25EnvironmentFile=‑/run/flannel/docker
26ExecStart=
27ExecStart=/usr/bin/dockerd $OPTIONS 
28          $DOCKER_STORAGE_OPTIONS 
29          $DOCKER_NETWORK_OPTIONS 
30          $BLOCK_REGISTRY 
31          $INSECURE_REGISTRY

然后就可以了。

那么为什么 flannel 不用 UDP 呢?

image-20220317130659132

看上图,包从用户空间到内核空间的流入流出,会经过1、2、3的来回拷贝翻转,性能损失较大,所以 UDP 只能用在测试环境。

最后 flannel 的 VXLAN 和 HOST-GW 又有什么区别呢?

  • 与 vxlan 不同,host-gw 不会封装数据包,而是在主机的路由表中创建到其他主机 subnet 的路由条目,从而实现容器跨主机通信
  • host-gw 把每个主机都配置成网关,主机知道其他主机的 subnet 和转发地址。
  • vxlan 则在主机间建立隧道,不同主机的容器都在一个大的网段内(比如 10.2.0.0/16)。
  • 虽然 vxlan 与 host-gw 使用不同的机制建立主机之间连接,但对于容器则无需任何改变。
  • 由于 vxlan 需要对数据进行额外打包和拆包,性能会稍逊于 host-gw。

Ucarp的安装配置
面试之Nginx的epoll的优势
comments powered by Disqus