它的全称是 Container Network Interface,即容器网络的 API 接口。
它是 K8S 中标准的一个调用网络实现的接口。Kubelet 通过这个标准的 API 来调用不同的网络插件以实现不同的网络配置方式。实现了这个接口的就是 CNI 插件,它实现了一系列的 CNI API 接口。常见的 CNI 插件包括 Calico、flannel、Terway、Weave Net 以及 Contiv。
K8s 通过 CNI 配置文件来决定使用什么 CNI。
基本的使用方法为:
具体的流程如下图所示:
在集群里面创建一个 Pod 的时候,首先会通过 apiserver 将 Pod 的配置写入。apiserver 的一些管控组件(比如 Scheduler)会调度到某个具体的节点上去。Kubelet 监听到这个 Pod 的创建之后,会在本地进行一些创建的操作。当执行到创建网络这一步骤时,首先它会读取刚才我们所说的配置目录中的配置文件,配置文件里面会声明所使用的是哪一个插件,然后去执行具体的 CNI 插件的二进制文件,再由 CNI 插件进入 Pod 的网络空间去配置 Pod 的网络。配置完成之后,Kubelet 也就完成了整个 Pod 的创建过程,这个 Pod 就在线了。
社区有很多的 CNI 插件,比如 Calico, flannel, Terway 等等。那么在一个真正具体的生产环境中,我们要选择哪一个 CNI 插件呢?
这就要从 CNI 的几种实现模式说起。我们需要根据不同的场景选择不同的实现模式,再去选择对应的具体某一个插件。
通常来说,CNI 插件可以分为三种:Overlay、路由及 Underlay。
Overlay 模式的典型特征是容器独立于主机的 IP 段,这个 IP 段进行跨主机网络通信时是通过在主机之间创建隧道的方式,将整个容器网段的包全都封装成底层的物理网络中主机之间的包。该方式的好处在于它不依赖于底层网络;
路由模式中主机和容器也分属不同的网段,它与 Overlay 模式的主要区别在于它的跨主机通信是通过路由打通,无需在不同主机之间做一个隧道封包。但路由打通就需要部分依赖于底层网络,比如说要求底层网络有二层可达的一个能力;
Underlay 模式中容器和宿主机位于同一层网络,两者拥有相同的地位。容器之间网络的打通主要依靠于底层网络。因此该模式是强依赖于底层能力的。
有时社区的插件无法满足自己的需求,比如在阿里云上只能使用 vxlan 这种 Overlay 的插件,而 Overlay 插件的性能相对较差,无法满足阿里云上的一些业务需求,所以阿里云上开发了一个 Terway 的插件。
CNI 插件的实现通常包含两个部分:
通常我们会用一个 “veth” 这种虚拟网卡,一端放到 Pod 的网络空间,一端放到主机的网络空间,这样就实现了 Pod 与主机这两个命名空间的打通。
这个 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 地址。
第一步,将分配到的 IP 地址配置给 Pod 的虚拟网卡;
第二步,在 Pod 的网卡上配置集群网段的路由,令访问的流量都走到对应的 Pod 网卡上去,并且也会配置默认路由的网段到这个网卡上,也就是说走公网的流量也会走到这个网卡上进行路由;
最后,在宿主机上配置到 Pod 的 IP 地址的路由,指向到宿主机对端 veth1 这个虚拟网卡上。这样实现的是从 Pod 能够到宿主机上进行路由出去的,同时也实现了在宿主机上访问到 Pod 的 IP 地址也能路由到对应的 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 转发到对应的节点上。