Understanding CNI (Container Networking Interface)
https://github.com/containernetworking/cni
执行 kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/a70459be0084506e4ec919aa1c114638878db11b/Documentation/kube-flannel.yml
在宿主机的 /opt/cni/bin 目录下看到它们,如下所示:
root@ubuntu:~# ls /opt/cni/bin antrea bandwidth bridge dhcp firewall flannel host-device host-local ipvlan loopback macvlan portmap ptp sbr static tuning vlan whereabouts root@ubuntu:~#
这些 CNI 的基础可执行文件,按照功能可以分为三类:
-
第一类,叫作 Main 插件,它是用来创建具体网络设备的二进制文件。比如,bridge(网桥设备)、ipvlan、loopback(lo 设备)、macvlan、ptp(Veth Pair 设备),以及 vlan。
Flannel、Weave 等项目,都属于“网桥”类型的 CNI 插件。所以在具体的实现中,它们往往会调用 bridge 这个二进制文件
-
第二类,叫作 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 用的容器网络方案,其实需要做两部分工作,以 Flannel 项目为例:
首先,实现这个网络方案本身。这一部分需要编写的,其实就是 flanneld 进程里的主要逻辑。比如,创建和配置 flannel.1 设备、配置宿主机路由、配置 ARP 和 FDB 表里的信息等等。
然后,实现该网络方案对应的 CNI 插件。这一部分主要需要做的,就是配置 Infra 容器里面的网络栈,并把它连接在 CNI 网桥上。
调用实现
在每台宿主机上生成对应的CNI配置文件/etc/cni/net.d/10-flannel.conflist,kubernetes集群会使用这个配置文件来作为容器网络配置方案
root@ubuntu:~# ls /etc/cni/net.d/ 10-antrea.conflist 10-flannel.conflist root@ubuntu:~#
root@ubuntu:~# 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 } } ] } root@ubuntu:~# ip a sh cbr0 Device "cbr0" does not exist. root@ubuntu:~# cat /etc/cni/net.d/10-antrea.conflist { "cniVersion":"0.3.0", "name": "antrea", "plugins": [ { "type": "antrea", "ipam": { "type": "host-local" } }, { "type": "portmap", "capabilities": {"portMappings": true} }, { "type": "bandwidth", "capabilities": {"bandwidth": true} } ] }
CNI的调用方式是通过一个可执行文件进行的。这里以calico为例,说明CNI插件的调用方式。
首先,calico进行插件注册
mkdir -p /etc/cni/net.d
cat >/etc/cni/net.d/10-calico.conf <<EOF { "name": "calico-k8s-network", "type": "calico", "etcd_authority": "<ETCD_IP>:<ETCD_PORT>", "log_level": "info", "ipam": { "type": "calico-ipam" }, "policy": { "type": "k8s" } } EOF
k8s的DefaultCNIDir是/opt/cni/bin
。因为注册的type
是calico
,所以k8s会从/opt/cni/bin
中搜索一个calico
的可执行文件,然后进行执行。
执行的时候传递参数有两种方式,一种是通过环境变量进行传递,比如上文中的Version、Container ID等;另外一种是通过执行calico
作为执行的参数传进去,这个主要就是Network configuration的部分,通过json将其打包传入。
同样判断是否执行成功,是通过执行文件的返回值获取的。0为成功,1为版本版本不匹配,2为存在不符合的字段。如果执行成功,返回值将通过stdout返回。
CNI与dockershim
CRI(Container Runtime Interface,容器运行时接口)实现容器网络相关的逻辑,对应docker项目为dockershim来实现
dockershim处理过程
- dockershim在处理容器网络时,加载/etc/cni/net.d内的文件,并且把plugins字段中的第一个插件作为默认插件
- 后面的执行过程中,flannel和portmap会按照定义顺序被调用,从而依次完成“配置容器网络”和“配置端口映射”这两步操作
CNI插件的工作原理
- kubelet创建pod时,首先通过dockershim会先调用docker api创建并启动Infra容器,然后执行SetUpPod方法,作用为:为CNI插件准备参数,然后调用CNI插件为Infra容器配置网络
- 将容器添加到CNI网络中,即通过容器以Veth Pair方式插到CNI网桥上
- /run/flannel/subnet.env为宿主机生成的flannel配置文件
- /var/lib/cni/flannel 为flannel cni插件把delegate字段以json文件的方式保存下来
- CNI bridge插件调用CNI ipam插件,为容器分配可用ip地址,同时为容器设置默认路由
- CNI插件会把容器的ip地址等信息返回给dockershim,然后被kubelet添加到Pod的Status字段中