现在各大公有云的 k8s 网络插件基本用的都是 vxlan,我们也需要对这个进行一下详细了解,以便用于公司的正式生产环境。
一、原理
首先,kubernetes的网络模型:
包含三要素:
-
所有的容器(container)之间能够在不使用 NAT 的情况下互相通讯
-
所有的节点(Node)能够在不使用 NAT 的情况下跟所有的容器(container)互相通讯(反之容器访问节点亦然)
-
容器(container)中的IP地址,在容器内和容器外面看起来是一样的
那来到 flannel,它是一种 Overlay network 覆盖网络,盖在原有的 Node 网络基础上:
上图要仔细分析, 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跨主机互相通讯的过程:
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 呢?
看上图,包从用户空间到内核空间的流入流出,会经过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。