UDP和VXLAN都有一个共性,就是用户的容器都连接到docker0网桥上,而网络插件为宿主机插件了一个特殊的设备,docekr0与这个设备之间,通过IP转发进行协作

网络插件真正做的事情,是通过方法,把不同宿主机上的特殊设备连通,从而到达容器跨主机通信的目的

Kubernetes对容器网络的主要处理方式,只不过Kubernetes是通过了一个叫做CNI的接口,维护了一个单独的网桥代替docker0,这是网桥名字叫做CNI网桥,对应宿主上的设备名称默认为cni0

我们以Flannel 的VXLAN为例,在Kubernetes环境中,工作环境类似,不过docker0网桥替换为了CNI网桥罢了

这里,我们为Flannel分配的子网范围是10.244.0.0/16 这个参数可以在部署的时候指定

$ kubeadm init –pod-network-cidr=10.244.0.0/16

或者通过kube-controller-manager的配置文件来指定

这时候,假设Infra-container-1要访问Infra-container-2,也就是Pod-1要访问Pod2,如何办呢?

IP的源地址是10.244.0.2 目的IP是10.244.1.3.

这个流程如下

这时候Infra-container-1里的eth0设备,同样是以Veth Pair的方式连接到Node1的cni0网桥上,这个IP包就会经过cni0网桥出现在宿主机上

这时候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

在宿主机上,这个IP包的IP地址是10.244.1.3,匹配的规则是第二条,就是10.244.1.0对应的路由规则

可以看到,这条规则这个相关地址的IP包交给flannel.1处理,并且,flannel.1处理完成后,将IP包转发给的Gateway,正是隧道另一端的VTEP设备,就是Node 2的flannel.1设备,和上文的Flannel的VXLAN模式一致

CNI网桥只会管理所有交由Kubernetes创建的容器Pod,如果是利用Docker启动的容器,还是会连接到docker0网桥上,所以这个容器的IP地址,一定属于docker0网桥的172.17.0.0/16网段

Kubernetes之所以要设置一个和Docker0网桥一致功能的CNI网桥,主要是关于

不希望和Docker有过多的耦合

kubernetes也不具有使用docker网络模型的能力,而且这还和Kubernetes创建Pod,Infra容器的Network Namespace密切相关

而CNI的设计思路和我们Pod中的Infra容器相关,我们的Pod必然先启动一个Infra容器,用来hold住整个Pod的Network Namespace,Kubernetes创建一个Pod的第一步,CNI的设计思想,就是在Kubernetes启动Infra容器后,直接调用CNI网络插件,为这个Infra容器的Network Namespace,配置符合预期的网络栈,这个网络栈是如何配置的呢?

在部署Kubernetes的时候,有一个步骤是安装kubernetes-cni的包,目的是在宿主机上安装CNI插件所需的基础可执行文件

安装完成后,可以在宿主机的/opt/cni/bin目录下看到对应CNI的基础文件

基础文件可以分为三类

第一类,叫做Main插件,可以用来创建具体的网络设备的二进制文件,比如bridge,ipvlan,loopback lo设备,macvlan,ptp(Veth Pair设备) 以及Vlan

我们其中的Flannle Weave等项目,都属于网桥类型的CNI插件,再具体实现中,往往调用bridge 这个二进制的文件

第二类,叫做IPAM插件,这是负责分配IP地址的二进制文件,比如DHCP,向DHCP服务器发起请求,获取自己的IP地址

第三类,是由CNI社区维护的CNI插件,比如Flannl,就是专门为Flannel项目提供的CNI插件;tuning,一个通过sysctl调整网络设备参数的二进制文件.portmap.用来通过iptables来配置端口映射的二进制文件,bandwidth,一个使用Token Bucket Filter TBF来进行限流的二进制文件

那么,我们需要实现一个容器的网路方案,需要做两件事,以Flannel为例

实现这个网络方案本身,需要编写的,就是flanneld进程中的主要逻辑,创建和配置flannel.1设备,配置宿主机的路由,ARP FDB等信息

然后实现对应的CNI插件,将Infra容器中网络栈进行配置并连接到CNI网桥上

由于Flannel对应的CNI插件已经被内置了,所以无需再单独安装,对于Weave Calico等项目,就需要在安装插件的时候,将对应的CNI插件的可执行文件放在/opt/cni/bin目录

然后flanneld启动后就会在每台宿主机上生成对应的CNI配置文件,告诉Kubernetes,使用Flannel作为容器解决方案

CNI的配置文件如下

$ cat /etc/cni/net.d/10-flannel.conflist

{

“name”: “cbr0”,

“plugins”: [

{

“type”: “flannel”,

“delegate”: {

“hairpinMode”: true,

“isDefaultGateway”: true

}

},

{

“type”: “portmap”,

“capabilities”: {

“portMappings”: true

}

}

]

}

而且Kubernetes并不会实际的去执行相关的网络配置,而是调用给容器项目去执行,利用的是CRI 容器运行时借口而,对应的CRI实现是dockershim,是Kubelet的代码实现

然后,在dockershim中,会加载上述的CNI配置文件

Kubernetes目前不支持CNI插件混用,而是在CNI配置目的(/etc/cni.net,d)防止多个CNI配置文件的话,dockershim会加载按照字母顺序的第一个插件

CNI会允许在CNI配置文件,通过plugins字段,定义多个插件进行协同操作

Flannel就指定了flannel和portmap两个插件

当Kubelet组件需要创建Pod的时候,先创建了Infra容器,这一步,dockershim会调用Docker API创建并启动Infra容器,然后执行一个叫做SetUpPod的方法,为CNI插件准备相关的参数,然后调用CNI插件为Infra容器配置网络

第一部分,是由dockershim设置一组CNI环境变量

最重要的变量叫做CNI_COMMAND,取值只有两种 ADD和DEL

这个ADD和DEL,就是CNI插件需要的接口方法

ADD是为了添加容器进入CNI网络,DEL为了将容器从CNI网络中移除

CNI的ADD中需要一些参数,容器网卡的名字 eth0(CNI_IFNAME),Pod的Network Namespace文件的路径,容器的ID等,这些参数都属于环境变量

Pod的Network Namespace文件路径,就是 /proc/<容器进程PID>/ns/net

在这个CNI环境变量中,还有一个CNI_ARGS的参数,通过这个参数,CRI实现,可以以Key-Value的格式,传递自定义信息给网络插件,这是用户来自定义CNI协议的重要实现

然后是dockershim从CNI配置文件中加载的,默认插件的配置信息

这个配置信息在CNI中叫做Network Configuration,dockershim会将Network Configuration以JSON数据的格式通过标准输入的方式传给Flannel CNI插件

有了这两个部分,CNI就可以进行ADD操作了

Flannel的CNI配置文件中有一个字段delegate

“delegate”: {

“hairpinMode”: true,

“isDefaultGateway”: true

}

Delegate字段说明了这个CNI会通过调用Delegate指定的容器内置插件完成,这个Flannel就会指定使用之前说的CNI bridge插件

所以说,dockershim对于Flannel CNI插件的调用,就是走了个过场,Flannel CNI插件唯一需要做的,就是对dockershim传来的Network Configuration进行补充,比如将Delegate的Type字段设置为bridge,将Delegate的IPAM字段设置为host-local

补充完成的Delegate字段如下

{

“hairpinMode”:true,

“ipMasq”:false,

“ipam”:{

“routes”:[

{

“dst”:”10.244.0.0/16″

}

],

“subnet”:”10.244.1.0/24″,

“type”:”host-local”

},

“isDefaultGateway”:true,

“isGateway”:true,

“mtu”:1410,

“name”:”cbr0″,

“type”:”bridge”

}

ipam字段里的信息,比如10.244.1.0/24,读取自Flannel的Fannel配置文件,即为宿主机上的/run/flannel/subnet.env文件

接下来Flannel CNI插件会调用CNI bridge 插件,执行/opt/cni/bin/bridge二进制文件

这一次,调用CNI bridge插件需要两个部分的第一部分,进行网络的配置,其中的CNI_COMMAND值为ADD,

第二个部分Network Configration,正是上面补充好的Delegate字段,Flannel CNI插件会将Delegate 字段以标准输入的方式传给CNI brige插件

而且,还会将Delegate字段以JSON文件的方式,保存在/var/lib/cni/flannel目录下,方便后续的DEL操作使用

有了这两个参数,接下来CNI bridge插件可以代表Flannel,进行将容器加入到CNI网络中这一步操作

这样,CNI bridge 插件会通过Infra容器的Newwork 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

这样,vethb4963f3就在宿主机上了,这样,就是完成了容器中的eth0

这样,创建了Veth Pair设备的操作,可以在宿主机上进行执行,然后再将其放在容器的Network Namespace中一样

不过之所以反着来,是因为CNI 中对Namespace操作函数的设计

err := containerNS.Do(func(hostNS ns.NetNS) error {

return nil

})

这个设计其实不难理解,容器的Namespace是可以通过Namespace中拿到的,而Host Namespace,是一个隐含了上下文参数,先通过容器的Namespace进入到容器内,然后反向操作Host Namespace,比较方便

CNI bridge插件就可以将vethb4963f3设备放在CNI网桥上,就好比在宿主机上执行

# 在宿主机上

$ ip link set vethb4963f3 master cni0

将veth4963f3设备连接在CNI网桥之后,CNI bridge插件还会为其设置Hairpin Mode,在默认情况下,网桥设备是不允许一个数据包从一个端口进来在从这个端口出去的,但是可以开启Hairpin Mode,消除这个限制

这个限制经常出现在一些场景,诸如执行docker run -p 8080:80 ,这会将宿主机端口8080转为容器80端口,这个请求最终会经过docker0网桥进入到容器内

如果容器内访问宿主机的8080,那么到达了宿主机后,并且处理完成并返回了,这个包需要返回对应的网桥,通过vethb4963端口进入到容器中的,所以我们需要开启veth4963f3端口的Hairpin Mode了

接下来,CNI bridge插件会调用CNI ipam插件,为容器分配一个可用的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插件会为CNI网桥添加IP地址,相当于在宿主机上执行

ip addr add 10.244.0.1/24 dev cni0

执行完上述操作后,CNI插件会将容器的IP地址等信息返回给dockershim,然后被kubelet添加到Pod的status字段

CNI插件的ADD方法就完成了,接下来,我们就可以直接使用了

在本章中,我们讲解了Kubernetes中CNI网络的实现

所有容器使用IP地址和其他容器通信,无须使用NAT

所有宿主机可以直接使用IP地址和其他容器进行通信,无须使用NAT

容器看到的自己的IP地址和别人看到的地址是一样

那么,这样这个网络模型,就可以总结为一个字,通.

容器和容器之间要通,容器和宿主机要通,Kubernetes要求这个通,是基于容器和宿主机的IP进行的

那么为何Kubernetes不自己实现容器网络,而是通过CNI进行一个简单的假设呢?

CNCF一直遵循的开放的,交给使用者自定义的插件 原则,方便提供更多的便利选择,有了CNI,就可以自定义插件,方便应对不同的,复杂的网络环境

发表评论

邮箱地址不会被公开。 必填项已用*标注