Kubernetes网络模型
- 网络插件真正要做的事情,是通过某种方法,把不同宿主机上的特殊设备连通,从而达到容器跨主机通信的目的
- Kubernetes通过一个叫做CNI的接口,维护了一个单独的网桥来代替docker0,网桥的名字就叫做CNI网桥,设备名称默认是cni0
- Kubernetes 之所以要设置这样一个与 docker0 网桥功能几乎一样的 CNI 网桥,主要原因包括两个方面:
- Kubernetes 项目并没有使用 Docker 的网络模型(CNM),所以它并不希望、也不具备配置 docker0 网桥的能力;
- 这还与 Kubernetes 如何配置 Pod,也就是 Infra 容器的 Network Namespace 密切相关。
- CNI的设计思想:Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace,配置符合预期的网络栈。
网络栈的配置工作是如何完成的?
- 在部署 Kubernetes 的时候,有一个步骤是安装 kubernetes-cni 包,它的目的就是在宿主机上安装 CNI 插件所需的基础可执行文件。(/opt/cni/bin)
- 第一类,叫作 Main 插件,它是用来创建具体网络设备的二进制文件。比如,bridge(网桥设备)、ipvlan、loopback(lo 设备)、macvlan、ptp(Veth Pair 设备),以及 vlan。
- 第二类,叫作 IPAM(IP Address Management)插件,它是负责分配 IP 地址的二进制文件。比如,dhcp,这个文件会向 DHCP 服务器发起请求;host-local,则会使用预先配置的 IP 地址段来进行分配。
- 第三类,是由 CNI 社区维护的内置 CNI 插件。比如:flannel,就是专门为 Flannel 项目提供的 CNI 插件;tuning,是一个通过 sysctl 调整网络设备参数的二进制文件;portmap,是一个通过 iptables 配置端口映射的二进制文件;bandwidth,是一个使用 Token Bucket Filter (TBF) 来进行限流的二进制文件。
- 实现一个给Kubernetes用的容器网络方案
- 实现这个网络方案本身:需要编写的,其实就是 flanneld 进程里的主要逻辑。比如,创建和配置 flannel.1 设备、配置宿主机路由、配置 ARP 和 FDB 表里的信息等等。
- 实现该网络方案对应的 CNI 插件:配置 Infra 容器里面的网络栈,并把它连接在 CNI 网桥上。
- 在宿主机上安装flanned。在这个过程中flanneld启动后会在每台宿主机上生成它对应的CNI配置文件(ConfigMap),从而告诉kubernetes,这个集群要使用Flannel作为容器网络方案
- kubelet调用CRI接口来执行处理容器网络相关的逻辑,docker项目的CRI实现叫做dockershim
- dockershim加载上述的CNI配置文件,kubernetes不支持多个CNI插件混用,但可以定义多个插件进行协作
- Flannel项目就指定了flannel和portmap这两个插件,dockershim会把这个CNI配置文件加载起来,flannel 和 portmap 插件会按照定义顺序被调用,从而依次完成“配置容器网络”和“配置端口映射”这两步操作。
CNI插件的工作原理
-
kubelet组件创建Pod时,第一个创建的一定是Infra容器
-
dockershim 就会先调用 Docker API 创建并启动 Infra 容器
-
执行一个叫作 SetUpPod 的方法,为 CNI 插件准备参数,然后调用 CNI 插件为 Infra 容器配置网络。
-
调用的CNI插件就是/opt/cni/bin/flannel
-
所需的参数,第一部分,是由 dockershim 设置的一组 CNI 环境变量。(CNI_COMMAND:ADD和DEL)
- ADD:把容器添加到CNI网络里
- 参数:容器里网卡的名字 eth0(CNI_IFNAME)、Pod 的 Network Namespace 文件的路径(CNI_NETNS)、容器的 ID(CNI_CONTAINERID)等。这些参数都属于上述环境变量里的内容。其中,Pod(Infra 容器)的 Network Namespace 文件的路径,我在前面讲解容器基础的时候提到过,即:/proc/< 容器进程的 PID>/ns/net。除此之外,在 CNI 环境变量里,还有一个叫作 CNI_ARGS 的参数。通过这个参数,CRI 实现(比如 dockershim)就可以以 Key-Value 的格式,传递自定义信息给网络插件。这是用户将来自定义 CNI 协议的一个重要方法。
- DEL:把容器从CNI网络里移除
- ADD:把容器添加到CNI网络里
-
-
第二部分,则是 dockershim 从 CNI 配置文件里加载到的、默认插件的配置信息。
-
这个配置信息在 CNI 中被叫作 Network Configuration,dockershim 会把 Network Configuration 以 JSON 数据的格式,通过标准输入(stdin)的方式传递给 Flannel CNI 插件。
-
配置文件中的delegate字段,表示CNI插件并不会自己做事,而是会调用内置插件
-
它唯一作用是补充完整配置文件
-
ipam字段10.244.1.0/24读取自Flannel在宿主机上生成的Flannel配置文件(/run/flannel/subnet.env)
-
接下来,Flannel CNI 插件就会调用 CNI bridge 插件,也就是执行:/opt/cni/bin/bridge 二进制文件。
-
这一次,调用 CNI bridge 插件需要的两部分参数的第一部分、也就是 CNI 环境变量,并没有变化。所以,它里面的 CNI_COMMAND 参数的值还是“ADD”。
-
而第二部分 Network Configration,正是上面补充好的 Delegate 字段。Flannel CNI 插件会把 Delegate 字段的内容以标准输入(stdin)的方式传递给 CNI bridge 插件。(Delegate字段以Json文件方式保存/var/lib/cni/flannel目录下,为了给后面删除容器调用DEL操作使用)
-
有了两部分参数,CNI bridge插件就可以将容器加入到CNI网络里
-
首先,CNI bridge 插件会在宿主机上检查 CNI 网桥是否存在。如果没有的话,那就创建它。这相当于在宿主机上执行:
# 在宿主机上 $ ip link add cni0 type bridge $ ip link set cni0 up
-
接下来,CNI bridge 插件会通过 Infra 容器的 Network Namespace 文件,进入到这个 Network Namespace 里面,然后创建一对 Veth Pair 设备。
-
紧接着,它会把这个 Veth Pair 的其中一端,“移动”到宿主机上。这相当于在容器里执行如下所示的命令:
#在容器里 # 创建一对Veth Pair设备。其中一个叫作eth0,另一个叫作vethb4963f3 $ ip link add eth0 type veth peer name vethb4963f3 # 启动eth0设备 $ ip link set eth0 up # 将Veth Pair设备的另一端(也就是vethb4963f3设备)放到宿主机(也就是Host Namespace)里 $ ip link set vethb4963f3 netns $HOST_NS # 通过Host Namespace,启动宿主机上的vethb4963f3设备 $ ip netns exec $HOST_NS ip link set vethb4963f3 up
-
接下来,CNI bridge 插件就可以把 vethb4963f3 设备连接在 CNI 网桥上。这相当于在宿主机上执行:
# 在宿主机上 $ ip link set vethb4963f3 master cni0
在将 vethb4963f3 设备连接在 CNI 网桥之后,CNI bridge 插件还会为它设置 Hairpin Mode(发夹模式)。这是因为,在默认情况下,网桥设备是不允许一个数据包从一个端口进来后,再从这个端口发出去的。但是,它允许你为这个端口开启 Hairpin Mode,从而取消这个限制。
-
接下来,CNI bridge 插件会调用 CNI ipam 插件,从 ipam.subnet 字段规定的网段里为容器分配一个可用的 IP 地址。然后,CNI bridge 插件就会把这个 IP 地址添加在容器的 eth0 网卡上,同时为容器设置默认路由。这相当于在容器里执行:
# 在容器里 $ ip addr add 10.244.0.2/24 dev eth0 $ ip route add default via 10.244.0.1 dev eth0
-
最后,CNI bridge 插件会为 CNI 网桥添加 IP 地址。这相当于在宿主机上执行:
# 在宿主机上 $ ip addr add 10.244.0.1/24 dev cni0
在执行完上述操作之后,CNI 插件会把容器的 IP 地址等信息返回给 dockershim,然后被 kubelet 添加到 Pod 的 Status 字段。
-
-