在容器跨主机网络中介绍了容器跨主机网络的两种实现方式。UDP和VXLAN。
这些例子的共性就是:用户的容器都连接在docker0网桥上,然后网络插件在宿主机上创建了一个特殊的设备(UDP模式的TUN,VXLAN的VTEP),docker0与这个设备之间通过IP转发的方式进行协作。
网络插件真正要做的事,则是通过某种方法,把不同宿主机上的特殊设备联通,从而达到容器跨主机的目的。
上面的流程也是k8s对容器网络的处理方法。只不过k8s通过一个叫做CNI的接口,维护了一个单独的网桥来代替docker0.这个网桥的名字叫做CNI网桥,它在宿主机上的设备名称默认是cni0。

Kubernetes 为 Flannel 分配的子网范围是 10.244.0.0/16。这个参数可以在部署的时候指定:
$ kubeadm init --pod-network-cidr=10.244.0.0/16也可以在部署完成之后,通过修改kube-controller-manager的配置文件来指定。
Note
再次强调一遍,一个宿主机的所有容器必须属于该宿主机被分配的子网。只有这样才能够根据路由表将IP包发送到flannel.1设备上去,并转发到正确的网关。
现在有一个Pod1要访问Pod2,这个 IP 包的源地址就是 10.244.0.2,目的 IP 地址是 10.244.1.3。那么Pod1中容器里的eth0同样是通过Veth Pair的方式连接在其所在节点的cni0网桥上。所以这个IP包就会通过csi0网桥出现在宿主机上。
Node1上的路由表如下:
# 在Node 1上
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
...
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0当一个节点加入k8s集群当中,就会把其节点的子网网段分发给其它的节点。所以这里可以看到第二条路由规则,也正是通过这个规则指定使用flannel.1设备处理这个IP包。于是处理之后进行转发。这个流程与容器跨主机网络中的VXLAN就完全一样了。
Note
CNI网桥只是接管所有CNI插件负责的、k8s创建的容器。如果你用 docker run 单独启动一个容器,那么 Docker 项目还是会把这个容器连接到 docker0 网桥上。所以这个容器的 IP 地址,一定是属于 docker0 网桥的 172.17.0.0/16 网段。
k8s设置一个与docker0网桥作用一样的CNI网桥,原因有两个:
- k8s并没有使用docker的网络模式,所以它并不希望,也不具备配置docker0的能力
- 这与k8s如何配置Pod,也就是Infra容器的Network Namespace相关。
接下来便详细说说第二点。k8s在启动的每个Pod中都启动了一个Infra容器,来设置这个Pod的Network Namespace。所以CNI的设计思想就是:k8s在Infra容器启动后,就可以直接调用CNI网络插件,为这个Infra容器的Network Namespace设置预期的网络栈。
这个网络栈的配置过程在下面展开。
CNI插件配置网络栈
经过前面容器网络和容器跨主机网络的学习,现在可以知道配置网络栈需要创建一些虚拟设备,比如网桥,Veth Pair,VTEP等等,还需要分配网桥的网段,例如docker0,cni0。所以在安装部署k8s的时候,就有一个步骤是安装k8s-cni包,在宿主机上安装了CNI插件所需要的基础可执行文件用来创建和配置这些设备和网络信息。在宿主机的/opt/cni/bin目录下可以看到这些文件。
$ ls -al /opt/cni/bin/
total 73088
-rwxr-xr-x 1 root root 3890407 Aug 17 2017 bridge
-rwxr-xr-x 1 root root 9921982 Aug 17 2017 dhcp
-rwxr-xr-x 1 root root 2814104 Aug 17 2017 flannel
-rwxr-xr-x 1 root root 2991965 Aug 17 2017 host-local
-rwxr-xr-x 1 root root 3475802 Aug 17 2017 ipvlan
-rwxr-xr-x 1 root root 3026388 Aug 17 2017 loopback
-rwxr-xr-x 1 root root 3520724 Aug 17 2017 macvlan
-rwxr-xr-x 1 root root 3470464 Aug 17 2017 portmap
-rwxr-xr-x 1 root root 3877986 Aug 17 2017 ptp
-rwxr-xr-x 1 root root 2605279 Aug 17 2017 sample
-rwxr-xr-x 1 root root 2808402 Aug 17 2017 tuning
-rwxr-xr-x 1 root root 3475750 Aug 17 2017 vlan默认内置了flannel项目的CNI插件。对于其它项目,如Weave、Calico等项目就需要在安装插件的时候,把对应的可执行文件放在/opt/cni/bin目录下。
接下来,flanneld启动后会在每台宿主机上生成对应的CNI配置文件(是一个ConfigMap),告诉k8s,这个集群使用flannel作为容器网络方案。 k8s目前不支持多个CNI插件混用,但是支持在CNI配置文件的plugins字段指定多个CNI插件协同工作。
$ cat /etc/cni/net.d/10-flannel.conflist
{
"name": "cbr0",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}处理容器网络相关的逻辑并不是由kubelet完成的,而是由具体的CRI来处理,毕竟最终配置的也是容器的网络。docker就是dockershim,它会加载这个CNI配置文件,然后把列表中的第一个插件设置为默认插件。
kubelet要创建Pod,第一个创建的一定是Infra容器,dockershim就会调用Docker API创建并启动Infra容器,然后执行SetUpPod 方法:为CNI插件准备参数,然后调用CNI插件为Infra容器配置网络。这里的CNI插件就是/opt/cni/bin/flannel。
通过环境变量,和读取的配置文件,CNI插件就能够获取足够的信息。一个CNI插件只需要实现两个方法:ADD和DEL,即将容器加入CNI网络或者从CNI网络移除。
然后CNI插件就可以利用准备好的CNI基础可执行文件来配置网络了。使用CNI bridge创建cni网桥
# 在宿主机上
$ ip link add cni0 type bridge
$ ip link set cni0 up进入到Infra容器的NetworkNamespace中创建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 接着就可以将移动的这一端连接到cni0网桥上了
# 在宿主机上
$ ip link set vethb4963f3 master cni0CNI bridge 插件还会为它设置 Hairpin Mode(发夹模式)。这是因为,在默认情况下,网桥设备是不允许一个数据包从一个端口进来后,再从这个端口发出去的。但是,它允许你为这个端口开启 Hairpin Mode,从而取消这个限制。主要用于容器通过NAT端口映射的方式,**“自己访问自己”**的情况下。
docker run -p 8080:80,就是在宿主机上通过 iptables 设置了一条DNAT(目的地址转换)转发规则。宿主机上的进程访问8080端口就会被Iptables转发到docker0网桥进入容器。
Flannel 插件要在 CNI 配置文件里声明 hairpinMode=true。这样,将来这个集群里的 Pod 才可以通过它自己的 Service 访问到自己。
CNI Bridge调用CNI ipam从 ipam.subnet 字段规定的网段里为容器分配一个可用的 IP 地址。
# 在容器里
$ ip addr add 10.244.0.2/24 dev eth0
$ ip route add default via 10.244.0.1 dev eth0为CNI网桥添加IP地址
# 在宿主机上
$ ip addr add 10.244.0.1/24 dev cni0CNI 插件会把容器的 IP 地址等信息返回给 dockershim,然后被 kubelet 添加到 Pod 的 Status 字段。