单pod单IP模型
该网络模型的目标是为每个pod分配一个Kubernetes集群私有网络地址段(譬如10.x.x.x)的IP地址,通过该IP地址,pod能够跨网络与其他物理机、虚拟机或容器进行通信,pod内的容器全部共享这pod的网络配置,彼此之间使用localhost通信,就仿佛它们运行在一个机器上一样。
为每个pod分配一个IP地址的另一个好处是用户不再需要显式为相互通信的pod内的容器创建Docker link,况且Docker link也无法解决容器的跨宿主机通信问题。我们称Kubernetes的这种网路模型为单pod单IP模型。在该模型中,从端口分配、网络通信、域名解析、服务发现、负载均衡、应用配置和迁移等角度,pod都能够被简单地看成一台独立的虚拟机或物理机,这就大大降低了用户应用从虚拟机或物理机向容器迁移的成本,甚至还能够与原先的网络基础设施兼容。
在这种网络环境下,在任意一个Kubernetes集群中的容器内调用ioctl发起一个获取其网卡IP地址的请求时,它所获得的IP地址和其他与它通信的容器看到的IP地址是一样的,即Kubernetes为每个pod分配的IP地址都在一个非NAT(网络地址转换)的扁平化网络地址空间中(这一点非常重要,因为NAT将网络地址空间分段的做法,不仅引入了额外的复杂性,还带来了破坏自注册机制等问题)。这个扁平网络加上单pod单IP原则,就构成了Kubernetes的网络模型。
注意,Kubernetes的网络模型中的扁平网络并不是由Kubernetes保证,而是由用户来保证的。也就是说,用户要么使用某种IaaS(最典型的就是GCE)来实现pod的扁平化网络空间,要么借助网络工具(如OpenVSwich等)手动创建好这样的网络。当然,不管哪种方法,Kubernetes都会使用iptables完成pod内容器端口在宿主机上的端口映射,发往宿主机端口的流量会被转发至对应pod中的容器,而从pod发往宿主机外部的流量需要使用宿主机的IP地址进行源地址转换。
pod和网络容器
单pod单IP模型的实质是Kubernetes将IP地址应用到pod范围,同一个pod内的容器共享包括IP地址在内的网络namespace。这意味着同一个pod内的容器能够在localhost上访问各自的端口,而且这些容器可能会发生端口冲突。在每个pod中有一个网络容器(有时候也称为pod基础容器或者infra容器),该容器先于pod内所有用户容器被创建,并且拥有该pod的网络namespace, pod的其他用户容器使用Docker的--net=container:<id>选项加入
该网络namespace,这样就实现了pod内所有容器对网络栈的共享。
接下来,Kubernetes每次在上述pod内创建用户容器时,都会指定该网络容器名作为其POD参数(最终映射成为Docker命令的net参数)。这样Docker会先找到这个网络容器进程的PID,进而获得其网络namespace和进程间通信namespace的文件描述符(fd )。然后,用户容器就在自己的proc/{PID}/ns/目录下创建一个硬链接文件net,指向网络容器的上述网络namespace,从而实现对网络容器netns的共享。
实现Kubernetes的网络模型
Kubernetes的网络模型里pod必须都处在一个扁平化的网络地址空间中,即需要满足如下3个假设(个别依据实际应用场景而分隔的特殊网段除外):
- 所有容器之间的通信无需经过NAT。
- 所有集群节点与容器、容器与集群节点的通信无需经过NAT。
- 容器本身看到的容器 IP地址与其他容器看的IP地址是一样的。
这就意味着用户不能只是启动两台运行Docker容器的minion节点然后指望Kubernetes能让他们建立连接:用户需要自己帮助Kubernetes完成网络模型的实现,并保证最终的网络满足以上3个基本条件。
OpenVSwitch GRE/VxLAN方式
下面简单介绍如何使用OpenVSwitch的tunnel方式建立跨宿主机pod的网络连接。OpenVSwitch ( OVS )的tunnel类型可以是GRE或VxLAN,在需要大规模的网络隔离的应用场景下,推荐使用VxLAN。这里我们使用Vagrant搭建的Kubernetes的例子,其网络拓扑图如图所示。
这种做法的具体实现细节如下:
- 将默认的docker0网桥用一个Linux网桥kbr0替换,并使每个工作节点获得一个IP地址空间为10.244.x.0/24的子网。工作节点上的Docker配置成使用kbr0网桥而不是docker0网桥。
- 创建一个OVS网桥(obr0)并作为一个端口添加到kbro网桥上。所有的宿主机上的OVS网桥通过GRE/VxLAN tunnel连接在一起,这样就实现了每个工作节点的网络互联。因此,所有跨宿主机的pod流量都会通过OVS网桥进入GRE/VxLAN tunnel。
- 每个OVS网桥上启用STP(生成树)模式来避免GRE/VxLAN tunnel的回路。
- 在每个工作节点上设置防火墙规则,允许所有目的地址是10.244.0.0/16,且从obr0网络接口进来的IP数据包。
其他实现
除了上面列举的实现外,还有其他一些网络方案能够用于实现Kubernetes的单pod单IP模型,例如Flannel, Weave, Calico等,有兴趣的读者可以自行实践。
kubernetes网络插件
Kubernetes通过一个名为NetWorkPlugin的接口定义了网络插件,系统运行时具体采用的网络插件名称可通过kubelet的启动参数--network-plugin以及--network-plugin-dir传递进来。从代码实现的角度来看,网络插件实质上就是Golang中的一个interface,提供了对pod网络进行配置的一些方法。目前kubelet一共支持3种网络插件模式,即Exec、 CNI以及kubenet。在默认的情况下,即启动kubelet组件时不指定network-plugin的参数,kubelet所使用的网络插件的名称就是"kubernetes.io/no-op",此时,用户需要按照上文所介绍的那样,根据情况选择合适的网络方案,提前设置好Kubernetes的基本网络模型。在pod创建的时候,kubelet会认为当前的环境已是扁平化的网络。如果使用了network-plugin,那么用户pod在创建的过程中,需要通过具体的network-plugin来设置pod的网络环境。
kubelet中的网络插件的接口主要声明了以下几个方法:
- Init:初始化插件,在其他方法被调用之前,初始化方法会被调用一次。
- Name:返回插件的名称。
- Status:得到容器的ipv4以及ipv6的网络状态。
- SetUpPod:在pod的infra器(上文中提到的pod的网络容器)创建之后被调用,此时Pod中的其他容器还没有启动起来,这个方法的主要功能是将pod中的infra容器加人到一个网络中。
- TearDownPod: pod的infra容器被删除之前,调用该方法,将pod的infra容器从网络中删除。
目前可以用来配置pod网络的network-plugin包括CNI, Exec, kubenet三种。
CNI ( Container Network Plugin )规范由CoreOS提出,并被Kubernetes采纳。当前containernetworking/cni项目实现了CNI接口规范。containernetworking/cni项目针对Linux container的网络配置提供了指定的接口以及具体的插件实现。宏观上来看,cni所做的很简单,就是将容器加入到一个网络中,并且保证容器之间的连通性。具体的实现方案由底层的不同cni插件来实现,有兴趣的读者可以参考github.com/containernetworking/cni,了解libcni的具体实现细节。
如果选择插件的方式为exec , kubelet会到指定的目录下去寻找可执行的二进制文件,这个二进制文件对于kubelet来说就是第三方插件,为了便于kubelet调用,对应的二进制插件所支持的命令必须包括init, setup, teardown, status,执行参数必须满足格式:<action> <pod namespace> <pod name> <dockerid ofinfra container>。
kubenet插件的功能与--configure-cbr0的参数类似,它会创建一个名为cbr0的网桥,并且为每个pod创建一个veth pair,其中一端连接在cbr0网桥上,另一端会与pod相连并且被分配到一个ip地址作为pod的ip地址。