一、CNI 是什么
它的全称是 Container Network Interface,即容器网络的 API 接口。
它是 K8S 中标准的一个调用网络实现的接口。Kubelet 通过这个标准的 API 来调用不同的网络插件以实现不同的网络配置方式。实现了这个接口的就是 CNI 插件,它实现了一系列的 CNI API 接口。常见的 CNI 插件包括 Calico、flannel、Terway、Weave Net 以及 Contiv。
二、Kubernetes 中如何使用 CNI
K8s 通过 CNI 配置文件来决定使用什么 CNI。
基本的使用方法为:
- 首先在每个结点上配置 CNI 配置文件(/etc/cni/net.d/xxnet.conf),其中 xxnet.conf 是某一个网络配置文件的名称;
- 安装 CNI 配置文件中所对应的二进制插件;
- 在这个节点上创建 Pod 之后,Kubelet 就会根据 CNI 配置文件执行前两步所安装的 CNI 插件;
- 上步执行完之后,Pod 的网络就配置完成了。
具体的流程如下图所示:
在集群里面创建一个 Pod 的时候,首先会通过 apiserver 将 Pod 的配置写入。apiserver 的一些管控组件(比如 Scheduler)会调度到某个具体的节点上去。Kubelet 监听到这个 Pod 的创建之后,会在本地进行一些创建的操作。当执行到创建网络这一步骤时,首先它会读取刚才我们所说的配置目录中的配置文件,配置文件里面会声明所使用的是哪一个插件,然后去执行具体的 CNI 插件的二进制文件,再由 CNI 插件进入 Pod 的网络空间去配置 Pod 的网络。配置完成之后,Kubelet 也就完成了整个 Pod 的创建过程,这个 Pod 就在线了。
三、哪个 CNI 插件适合我
社区有很多的 CNI 插件,比如 Calico, flannel, Terway 等等。那么在一个真正具体的生产环境中,我们要选择哪一个 CNI 插件呢?
这就要从 CNI 的几种实现模式说起。我们需要根据不同的场景选择不同的实现模式,再去选择对应的具体某一个插件。
通常来说,CNI 插件可以分为三种:Overlay、路由及 Underlay。
-
Overlay 模式的典型特征是容器独立于主机的 IP 段,这个 IP 段进行跨主机网络通信时是通过在主机之间创建隧道的方式,将整个容器网段的包全都封装成底层的物理网络中主机之间的包。该方式的好处在于它不依赖于底层网络;
-
路由模式中主机和容器也分属不同的网段,它与 Overlay 模式的主要区别在于它的跨主机通信是通过路由打通,无需在不同主机之间做一个隧道封包。但路由打通就需要部分依赖于底层网络,比如说要求底层网络有二层可达的一个能力;
-
Underlay 模式中容器和宿主机位于同一层网络,两者拥有相同的地位。容器之间网络的打通主要依靠于底层网络。因此该模式是强依赖于底层能力的。
四、如何开发自己的 CNI 插件
有时社区的插件无法满足自己的需求,比如在阿里云上只能使用 vxlan 这种 Overlay 的插件,而 Overlay 插件的性能相对较差,无法满足阿里云上的一些业务需求,所以阿里云上开发了一个 Terway 的插件。
CNI 插件的实现通常包含两个部分:
- 一个二进制的 CNI 插件去配置 Pod 网卡和 IP 地址。这一步配置完成之后相当于给 Pod 上插上了一条网线,就是说它已经有自己的 IP、有自己的网卡了;
- 一个 Daemon 进程去管理 Pod 之间的网络打通。这一步相当于说将 Pod 真正连上网络,让 Pod 之间能够互相通信。
1、给 Pod 准备一个网卡
通常我们会用一个 “veth” 这种虚拟网卡,一端放到 Pod 的网络空间,一端放到主机的网络空间,这样就实现了 Pod 与主机这两个命名空间的打通。
2、给 Pod 分配 IP 地址
这个 IP 地址有一个要求,我们在之前介绍网络的时候也有提到,就是说这个 IP 地址在集群里需要是唯一的。如何保障集群里面给 Pod 分配的是个唯一的 IP 地址呢?
一般来说我们在创建整个集群的时候会指定 Pod 的一个大网段,按照每个节点去分配一个 Node 网段。比如说上图右侧创建的是一个 172.16 的网段,我们再按照每个节点去分配一个 /24 的段,这样就能保障每个节点上的地址是互不冲突的。然后每个 Pod 再从一个具体的节点上的网段中再去顺序分配具体的 IP 地址,比如 Pod1 分配到了 172.16.0.1,Pod2 分配到了 172.16.0.2,这样就实现了在节点里面 IP 地址分配的不冲突,并且不同的 Node 又分属不同的网段,因此不会冲突。
这样就给 Pod 分配了集群里面一个唯一的 IP 地址。
3、配置 Pod 的 IP 和路由
第一步,将分配到的 IP 地址配置给 Pod 的虚拟网卡;
第二步,在 Pod 的网卡上配置集群网段的路由,令访问的流量都走到对应的 Pod 网卡上去,并且也会配置默认路由的网段到这个网卡上,也就是说走公网的流量也会走到这个网卡上进行路由;
最后,在宿主机上配置到 Pod 的 IP 地址的路由,指向到宿主机对端 veth1 这个虚拟网卡上。这样实现的是从 Pod 能够到宿主机上进行路由出去的,同时也实现了在宿主机上访问到 Pod 的 IP 地址也能路由到对应的 Pod 的网卡所对应的对端上去。
给 Pod 连上网络
刚才我们是给 Pod 插上网线,也就是给它配了 IP 地址以及路由表。那怎么打通 Pod 之间的通信呢?也就是让每一个 Pod 的 IP 地址在集群里面都能被访问到。
一般我们是在 CNI Daemon 进程中去做这些网络打通的事情。通常来说是这样一个步骤:
-
首先 ,CNI 在每个节点上运行的 Daemon 进程会学习到集群所有 Pod 的 IP 地址及其所在节点信息。学习的方式通常是通过监听 K8s APIServer,拿到现有 Pod 的 IP 地址以及节点,并且新的节点和新的 Pod 的创建的时候也能通知到每个 Daemon;
-
拿到 Pod 以及 Node 的相关信息之后,再去配置网络进行打通。
首先,Daemon 会创建到整个集群所有节点的通道。这里的通道是个抽象概念,具体实现一般是通过 Overlay 隧道、阿里云上的 VPC 路由表、或者是自己机房里的 BGP 路由完成的;
第二步,将所有 Pod 的 IP 地址跟上一步创建的通道关联起来。关联也是个抽象概念,具体的实现通常是通过 Linux 路由、fdb 转发表或者OVS 流表等完成的。Linux 路由可以设定某一个 IP 地址路由到哪个节点上去。fdb 转发表是 forwarding database 的缩写,就是把某个 Pod 的 IP 转发到某一个节点的隧道端点上去(Overlay 网络)。OVS 流表是由 Open vSwitch 实现的,它可以把 Pod 的 IP 转发到对应的节点上。