云原生虚拟网络之 Flannel 工作原理

464次阅读  |  发布于2年以前

我以前在看 k8s 相关知识的时候,由于网络知识相对薄弱,所以看到 Flannel 网络这块看不大懂,所以最近就研究了一些云原生虚拟网络知识,写了两篇文章: 云原生虚拟网络之 VXLAN 协议 和 云原生虚拟网络 tun/tap & veth-pair 介绍了虚拟网络的一些知识,这些都是为了 flannel 做铺垫,现在终于来到 Flannel 篇了。

概述

在讨论 Flannel 之前,我们先看看 Docker 的网络模式。在默认情况,Docker 使用 bridge 网络模式:

Docker 在安装的时候会创建一个 docke0 的虚拟网桥,然后当运行容器时,在宿主机上创建虚拟网卡 veth pair 设备,veth pair 设备是成对出现的,从而组成一个数据通道,数据从一个设备进入,就会从另一个设备出来。将veth pair 设备的一端放在新创建的容器中,命名为 eth0;另一端放在宿主机的 docker0 中,以 veth 为前缀的名字命名,这一段内容我在前一篇文章 云原生虚拟网络 tun/tap & veth-pair 已经讲过。

在 Docker 的默认配置下,一台宿主机上的 docker0 网桥,和其他宿主机上的 docker0 网桥,没有任何关联,它们互相之间也没办法连通。所以,连接在这些网桥上的容器,自然也没办法进行通信了。

这个时候 Flannel 就来了,它是 CoreOS 公司主推的容器网络方案。实现原理其实相当于在原来的网络上加了一层 Overlay 网络,该网络中的结点可以看作通过虚拟或逻辑链路而连接起来的。

Flannel 会在每一个宿主机上运行名为 flanneld 代理,其负责为宿主机预先分配一个Subnet 子网,并为 Pod 分配ip地址。Flannel 使用 Kubernetes 或 etcd 来存储网络配置、分配的子网和主机公共 ip 等信息,数据包则通过 VXLAN、UDP 或 host-gw 这些类型的后端机制进行转发。

安装

安装我用的是 kubeasz 来进行安装的,总体参考这个文档:https://github.com/easzlab/kubeasz/blob/master/docs/setup/00-planning_and_overall_intro.md ,基于它我们能快速的搭建好一套完整的 k8s 集群。

在使用 docker exec -it kubeasz ezctl new 集群名 这个命令之后,会在 /etc/kubeasz/clusters/ 这个目录下生成对应的集群配置。

由于我们要自定义安装网络插件,记得修改 /etc/kubeasz/clusters/集群名/hosts 文件内容配置节点信息和网络插件:

...
# master node(s)
[kube_master]
192.168.13.130

# work node(s)
[kube_node]
192.168.13.150
192.168.13.140
192.168.13.130

# 集群网络范围
CLUSTER_CIDR="172.20.0.0/16"

# Network plugins supported: calico, flannel, kube-router, cilium, kube-ovn
CLUSTER_NETWORK="flannel"
...

然后再修改 Flannel 的后端配置/etc/kubeasz/clusters/集群名/config.yml

# node 节点上 pod 网段掩码长度(决定每个节点最多能分配的pod ip地址) 
# 即子网的划分范围
NODE_CIDR_LEN: 24

# [flannel]设置flannel 后端"host-gw","vxlan"等
FLANNEL_BACKEND: "vxlan"

每次用完环境之后可以销毁环境,然后重装,每次重装和销毁都是自动的,相信我,这样是最快的:

docker exec -it kubeasz ezctl destroy 集群名

需要注意的是 udp 模式太老了,现在已经废弃了,所以是无法通过这种方式安装,暂时没找到更好的方式安装,有安装方法的同学不妨和我说一下,感谢。

Subnet 子网

Flannel 要建立一个集群的覆盖网络(overlay network),首先就是要规划每台主机容器的 ip 地址。比如我现在有个三节点的 k8s 集群,那么我们执行下面命令可以看到各自的子网:

[root@localhost ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.20.0.0/16
FLANNEL_SUBNET=172.20.0.1/24
FLANNEL_MTU=1450
FLANNEL_ipMASQ=true

通过检查其他两个 node 我们可以知道,在 Flannel 网络中,每个 pod 都会被分配唯一的 ip 地址,且每个 k8s node 的子网各不重叠,没有交集。

下面我们看看子网是怎么分配的。

在 Flannel 安装的时候,一般会设置好整个集群的网络分段,以及子网的长度:

{"Network":"172.20.0.0/16","SubnetLen":24,"Backend":{"Type":"vxlan"}}

在首次启动 Flannel 会在主机中运行 flanneld 作为 agent ,然后从对应的网络划分的范围中,选择一个没有用过的子网作为本机的子网,例如 172.20.0.1/24,然后上报到 etcd ,利用 k8s api 或者 etcd 用于存储整个集群的网络配置,根据配置记录集群使用的网段。

etcd 保证了所有 node 上 flanned 所看到的配置是一致的。同时每个 node 上的 flanned 监听 etcd 上的数据变化,实时感知集群中 node 的变化,从而使得整个集群的 ip 是相互不冲突的。

Flannel backend

关于 Flannel backend 下面讲一下 udp 、vxlan、host-gw。

udp

udp 是 Flannel 最早支持的模式,在这个模式中主要有两个主件:flanneld 、flannel0 。flanneld 进程负责监听 etcd 上面的网络变化,以及用来收发包,flannel0 则是一个三层的 tun 设备,用作在操作系统内核和用户应用程序之间传递 ip 包。

如上图,tun 设备是一个三层网络层设备,它用来模拟虚拟网卡,可以直接通过其虚拟 IP 实现相互访问。tun 设备会从 /dev/net/tun 字符设备文件上读写数据包,应用进程 A 会监听某个端口传过来的数据包,负责封包和解包数据。

所有应用进程 B 发送到另一个虚拟 ip 的数据包都会由应用进程 A 包一层,然后再发出去;发到该虚拟网卡的数据会首先会发到应用进程 A 监听的端口中,然后由它解包之后再发送给应用进程 A ,所以对于 flannel0 来说也是这样。

下图画的是 udp 模式下数据处理过程:

如图,ip 为 172.20.0.8 的容器想要给另一个节点的 172.20.1.8 容器发送数据,这个数据包根据 ip 路由会先交给 flannel0 设备,然后 flannel0 就会把这个 ip 包,交给创建这个设备的应用程序,也就是 flanneld 进程,flanneld 进程是一个 udp 进程,负责处理 flannel0 发送过来的数据包。

flanneld 进程会监听 etcd 的网络信息,然后根据目的 ip 的地址匹配到对应的子网,从 etcd 中找到这个子网对应的宿主机 node 的 ip 地址,然后将这个数据包直接封装在 udp 包里面,然后发送给 node 2。

由于每台宿主机上的 flanneld 都监听着一个 8285 端口,所以 node 2 机器上 flanneld 进程会从 8285 端口获取到传过来的数据,解析出封装在里面的发给源 ip 地址。

flanneld 会直接把这个 ip 包发送给它所管理的 tun 设备,即 flannel0 设备。然后网络栈会将这个数据包根据路由发送到 cni0 网桥,cni0 网桥会扮演二层交换机的角色,将数据包发送给正确的端口,进而通过 veth pair 设备进入到容器里。

至于 veth-pair ,它是一对的虚拟设备接口,它是成对出现的,一端连着协议栈,一端彼此相连着,在 veth 设备的其中一端输入数据,这些数据就会从设备的另外一端原样不变地流出。在 k8s 中的 veth 直接连在 cni0 网桥上,利用网桥的能力就可以将数据发送到同一个 node 节点的不同容器上。

上面所讲的 Flannel udp 模式现在已经废弃,原因就是因为它经过三次用户态与内核态之间的数据拷贝。容器发送数据包经过 cni0 网桥进入内核态一次;数据包由 flannel0 设备进入到 flanneld 进程又一次;第三次是 flanneld 进行 udp 封包之后重新进入内核态,将 UDP 包通过宿主机的 eth0 发出去。

VXLAN

在讲 VXLAN 模式之前,我们现在看看什么是 VXLAN(虚拟可扩展局域网),它是 Linux 内核本身就支持的一种网络虚似化技术。

VXLAN 采用 L2 over L4 (MAC in UDP)的报文封装模式,把原本在二层传输的以太帧放到四层 UDP 协议的报文体内,同时加入了自己定义的 VXLAN Header。在 VXLAN Header 里直接就有 24 Bits 的 VLAN ID,可以存储 1677 万个不同的取值,VXLAN 让二层网络得以在三层范围内进行扩展,不再受数据中心间传输的限制。VXLAN 工作在二层网络( ip 网络层),只要是三层可达(能够通过 ip 互相通信)的网络就能部署 VXLAN 。VXLAN 的整个报文结构如图:

通过上面的报文我们可以知道,其实就是将内层的数据报文再包了一层,然后通过一个叫 VTEP 的进程负责解包和封包。VXLAN header 里面还有一个 VNI 标志,它的作用主要是用来标记数据包是不是属于当前租户的,用来做网络隔离使用。

VXLAN 在通信的时候,VTEP 在进行通信前会通过查询转发表 FDB 来确定目标 VTEP 地址,转发表 FDB 用于保存远端虚拟机/容器的 MAC 地址,远端 VTEP IP,以及 VNI 的映射关系,这个映射关系在 k8s 上 Flannel 会通过 flanneld 进程来自动更新 FDB(查询转发表) 表项。

比如 node1 的容器要和 node2 的容器通信,那么会经过如下:

host-gw

host-gw模式通信十分简单,它是通过 ip 路由直连的方式进行通信,flanneld 负责为各节点设置路由 ,将对应节点Pod子网的下一跳地址指向对应的节点的 ip :

比如 node1 的容器想要访问 node2 的容器,那么会匹配 node1 节点上这条路由规则:

[root@easzlab ~]# ip r
...
172.20.1.0/24 via 192.168.13.140 dev ens33

这条路由规则会把目的 ip 地址属于 172.20.1.0/24 网段的 ip 包,应该经过本机的 ens33 设备发出去(即:dev ens33);并且,它下一跳地址(next-hop)是 192.168.13.140

一旦配置了下一跳地址,那么接下来,当 ip 包从网络层进入链路层封装成帧的时候,ens33 设备就会使用下一跳地址对应的 MAC 地址,作为该数据帧的目的 MAC 地址。显然,这个 MAC 地址,正是 node2 的 MAC 地址。

node2 从数据帧里拿到 ip 包后,发现这个 ip 包的目的 ip 地址是 172.20.1.2,然后会匹配 node2 上面这条路由规则:

[root@localhost ~]# ip r
...
172.20.1.0/24 dev cni0 proto kernel scope link src 172.20.1.1

然后会进入到 cni0 网桥,进而进入到对应容器中。

从上面我们可以知道 host-gw 模式能够正常工作的核心,就在于 ip 包在封装成帧发送出去的时候,会使用路由表里的下一跳来设置目的 MAC 地址。这样,它就会经过二层网络到达目的宿主机。所以说,Flannel host-gw 模式必须要求集群宿主机之间是二层连通的。

总结

这篇文章首先使用 kubeasz 来快速搭建我们的实验环境,让大家可以快速的在自己机器上也模拟一个这样的环境。然后讲了一下 Flannel 一些通信机制,包括子网、三个后端(udp、vxlan、host-gw)等,分别研究了它们实现原理。

对比三种网络,udp 主要是利用 tun 设备来模拟一个虚拟网络进行通信;vxlan 模式主要是利用 vxlan 实现一个三层的覆盖网络,利用 flannel1 这个 vtep 设备来进行封拆包,然后进行路由转发实现通信;而 host-gw 网络则更为直接,直接改变二层网络的路由信息,实现数据包的转发,从而省去中间层,通信效率更高。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8