Kubernetes网络设计
在实际生产中,面临多种多样的业务场景,导致业务之间的调用关系更加复杂化,从而对网络提出更高的要求,对于Kubernetes来说主要解决以下四个问题:
-
Pod中容器与容器之间的通信问题;
-
Pod与Pod之间的网络通信问题;
-
Pod与Service之间的通信问题;
-
集群内部与外部的通信问题;
第三和第四点我们在前面的章节已经讲过,大家可以在Service章节具体回忆下,重点介绍下第一、二点;
容器与容器之间的通信问题
介绍Pod时候我们说过Pod内部的容器共享Pod的命名空间的,因此在同一个Pod内的容器之间对于网络的各类操作,就和它们在同一台机器上一样,可以用localhost地址访问彼此的端口。
Pod与Pod之间的网络通信问题
每个Pod都有自己单独的IP,两个Pod既有可能运行在同一个Node上,也有可能运行在不同的Node上,因此对于Pod之间的通信可以分为两类:
同一个Node内的Pod之间的通信
对于在同一台机器上的两个Pod来说,都是连接到同一个docker0网桥上,它们的IP地址IP1、IP2都是从docker0网段上动态获取的,它们和网桥本身的IP3是同一个网段的,Pod1和Pod2处于同一局域网内,它们之间可以通过docker0作为路由进行通信。
不同Node上的Pod之间的通信
在Kubernetes的网络世界中,Pod之间假设是通过访问对方的Pod IP进行通信的,而不同Node之间的通信只能通过Node的物理网卡进行,Pod的IP地址是由各Node上的docker0网桥动态分配的。我们想要实现跨Node的Pod之间的通信,至少需要满足下面两个条件:
- Kubernetes集群中对Pod的IP分配不能有冲突;
- Pod IP 和Node IP之间的映射关系,通过Node IP转发到Pod IP;
根据条件1的要求,Kubernetes会再部署的时候对docker0的IP地址进行规划,保证每个Node上的docker0地址都没有冲突,此外Kubernetes会记录所有正在运行的Pod的IP分配信息,并将这些信息保存到etcd中,这样我们就可以知道Pod IP和Node IP之间的映射关系。
根据条件2要求,Pod中的数据在发送出去的时候,需要一个机制能够知道对方Pod的IP地址挂载到那个具体的Node上,这样就可以将数据发送到对应的宿主机上,宿主机将响应的数据转发到具体的docker0上,当数据到达docker0以后,就会将对应的数据转发到对应的容器上了。
CNI
Kubernetes本身并不负责网络通信,Kubernetes提供了容器网络接口CNI,具体的网络通信交给CNI插件来负责,开源的CNI插件非常多,像Flannel、Calico等,各大云厂商也都自己定制的CNI。
为什么会有CNI
CNI是Container Network Interface的缩写,简答说就是容器解决网络问题,提供的一个标准的,通用的接口。现在有各种各样的容器平台:docker,kubernetes,mesos,我们也有各种各样的容器网络解决方案:flannel,calico,weave,并且还有各种新的解决方案在不断涌现。如果每出现一个新的解决方案,我们都需要对两者进行适配,那么由此带来的工作量必然是巨大的,而且也是重复和不必要的。事实上,我们只要提供一个标准的接口,更准确的说是一种协议,就能完美地解决上述问题。一旦有新的网络方案出现,只要它能满足这个标准的协议,那么它就能为同样满足该协议的所有容器平台提供网络功能,而CNI正是这个标准接口协议。
什么是CNI
CNI是一个接口协议,用于连接容器管理系统和网络插件。前者提供一个容器所在的network namespace(从网络的角度来看,network namespace和容器是完全等价的),后者负责将network interface插入该network namespace中(比如veth的一端),并且在宿主机做一些必要的配置(例如将veth的另一端加入bridge中),最后对namespace中的interface进行IP和路由的配置。那么CNI的工作其实主要是从容器管理系统处获取运行时信息,包括network namespace的路径,容器ID以及network interface name,再从容器网络的配置文件中加载网络配置信息,再将这些信息传递给对应的插件,由插件进行具体的网络配置工作,并将配置的结果再返回到容器管理系统中。
CNI仅关注在创建容器时分配网络资源,和在销毁容器时删除网络资源,这使得CNI规范非常轻巧、易于实现,得到了广泛的支持。在CNI模型中只涉及两个概念:容器和网络。
容器(Container):是拥有独立Linux网络命名空间的环境,例如使用Docker或rkt创建的容器。容器需要拥有自己的Linux网络命名空间,这是加入网络的必要条件。
网络(Network):表示可以互连的一组实体,这些实体拥有各自独立、唯一的IP地址,可以是容器、物理机或者其他网络设备(比如路由器)等。
对容器网络的设置和操作都通过插件(Plugin)进行具体实现,CNI插件包括两种类型:CNI Plugin和IPAM(IP Address Management)Plugin。CNI Plugin负责为容器配置网络资源,IPAM Plugin负责对容器的IP地址进行分配和管理。IPAM Plugin作为CNI Plugin的一部分,与CNIPlugin协同工作。
插件介绍
CNI Plugin插件
CNI Plugin包括3个基本接口的定义:添加(ADD)、删除(DELETE)、检查(CHECK)和版本查询(VERSION)。这些接口的具体实现要求插件提供一个可执行的程序,在容器网络添加或删除时进行调用,以完成具体的操作。
添加:将容器添加到某个网络。主要过程为在Container Runtime创建容器时,先创建好容器内的网络命名空间(Network Namespace),然后调用CNI插件为该netns进行网络配置,最后启动容器内的进程。添加接口的参数如下:
-
Version:CNI版本号;
-
ContainerID:容器ID;
-
Network Namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net;
-
Network configuration:网络配置JSON文档,用于描述容器待加入的网络;
-
Extra arguments:其他参数,提供基于容器的CNI插件简单配置机制;
-
Name of the interface inside the container:容器内的网卡名,返回的信息如下:
-
Interfaces list:网卡列表,根据Plugin的实现,可能包括Sandbox Interface名称、主机Interface名称、每个Interface的地址等信息;
-
IPs assigned to the interface:IPv4或者IPv6地址、网关地址、路由信息等;
-
DNS information:DNS相关的信息;
删除:容器销毁时将容器从某个网络中删除。删除接口的参数如下:
-
Version:CNI版本号;
-
ContainerID:容器ID;
-
Network Namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net;
-
Network configuration:网络配置JSON文档,用于描述容器待加入的网络;
-
Extra arguments:其他参数,提供基于容器的CNI插件简单配置机制;
-
Name of the interface inside the container:容器内的网卡名;
检查:检查容器网络是否正确设置。检查接口的参数如下:
-
Version:CNI版本号;
-
ContainerID:容器ID;
-
Network Namespace path:容器的网络命名空间路径,例如/proc/[pid]/ns/net;
-
Network configuration:网络配置JSON文档,用于描述容器待加入的网络;
-
Extra arguments:其他参数,提供基于容器的CNI插件简单配置机制;
-
Name of the interface inside the container:容器内的网卡名;
版本查询:查询网络插件支持的CNI规范版本号。无参数,返回值为网络插件支持的CNI规范版本号。
IPAM Plugin插件
为了减轻CNI Plugin对IP地址管理的负担,在CNI规范中设置了一个新的插件专门用于管理容器的IP地址(还包括网关、路由等信息),被称为IPAM Plugin。通常由CNI Plugin在运行时自动调用IPAM Plugin完成容器IP地址的分配。
IPAM Plugin负责为容器分配IP地址、网关、路由和DNS,典型的实现包括host-local和dhcp。与CNI Plugin类似,IPAM插件也通过可执行程序完成IP地址分配的具体操作。IPAM可执行程序也处理传递给CNI插件的环境变量和通过标准输入(stdin)传入的网络配置参数。
如果成功完成了容器IP地址的分配,则IPAM插件应该通过标准输出(stdout)返回JSON报文。包括ips、routes和dns三段内容:
-
ips段:分配给容器的IP地址(也可能包括网关);
-
routes段:路由规则记录;
-
dns段:DNS相关的信息;
Kubernetes网络插件
Kubernetes管理的是集群,Kubernetes中的网络要解决的核心问题就是每台主机的IP地址网段划分,以及单个容器的IP地址分配。需要做到以下几点:
-
保证每个Pod拥有一个集群内唯一的IP地址;
-
保证不同节点的IP地址划分不会重复;
-
保证跨节点的Pod可以互相通信;
-
保证不同节点的Pod可以与跨节点的主机互相通信;
Kubernetes目前支持两种网络插件的实现。
CNI插件:根据CNI规范实现其接口,以与插件提供者进行对接。
kubenet插件:使用bridge和host-local CNI插件实现一个基本的cbr0。
为了在Kubernetes集群中使用网络插件,需要在kubelet服务的启动参数上设置下面两个参数:
- --network-plugin-dir:kubelet启动时扫描网络插件的目录。
- --network-plugin:网络插件名称,对于CNI插件,设置为cni即可,无须关注--network-plugin-dir的路径。对于kubenet插件,设置为kubenet,目前仅实现了一个简单的cbr0 Linux网桥。
在设置--network-plugin="cni"时,kubelet还需设置下面两个参数。
- --cni-conf-dir:CNI插件的配置文件目录,默认为/etc/cni/net.d。该目录下配置文件的内容需要符合CNI规范。
- --cni-bin-dir:CNI插件的可执行文件目录,默认为/opt/cni/bin。
开源容器网络方案
Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平网络空间中。若需要实现这个网络假设,需要实现不同节点上的Docker容器之间的互相访问,然后运行Kubernetes。目前已经有多个开源组件支持容器网络模型。如Flannel、Open vSwitch、直接路由和Calico。
Flannel
Flannel是一种基于overlay网络的跨主机容器网络解决方案,即将TCP数据包封装在另一种网络包里面进行路由转发和通信,Flannel是CoreOS开发,Flannel之所以可以搭建Kubernetes依赖的底层网络,是因为它能实现以下两点:
- 它能协助Kubernetes,给每一个Node上的Docker容器都分配互相不冲突的IP地址;
- 它能在这些IP地址之间建立一个Overlay Network,通过这个网络将数据包原封不动地传递到目标容器内
Flannel首先创建了一个名为flannel0的网桥,而且这个网桥的一端连接docker0网桥,另一端连接一个叫作flanneld的服务进程。flanneld进程上连etcd,利用etcd来管理可分配的IP地址段资源,同时监控etcd中每个Pod的实际地址,并在内存中建立了一个Pod节点路由表;flanneld进程下连docker0和物理网络,使用内存中的Pod节点路由表,将docker0发给它的数据包包装起来,利用物理网络的连接将数据包投递到目标flanneld上,从而完成Pod到Pod之间的直接地址通信。
Flannel之间的底层通信协议的可选技术包括UDP、VxLan、AWS VPC等多种方式。通过源flanneld封包、目标flanneld解包,最终docker0收到的就是原始的数据,对容器应用来说是透明的,感觉不到中间Flannel的存在。
Flannel每次分配的地址段都在同一个公共区域获取,存储在集中的etcd上,从而实现不同Node上的Pod分配的IP不产生冲突。Flannel通过修改Docker的启动参数将分配给它的地址段传递进去。
通过如上方式,Flannel就控制了每个Node上的docker0地址段的地址,从而保障了所有Pod的IP地址在同一个水平网络中且不产生冲突。Flannel完美地实现了对Kubernetes网络的支持,但是它引入了多个网络组件,在网络通信时需要转到flannel0网络接口,再转到用户态的flanneld程序,到对端后还需要走这个过程的反过程,所以也会引入一些网络的时延损耗。另外,Flannel模型默认采用了UDP作为底层传输协议,UDP本身是非可靠协议,虽然两端的TCP实现了可靠传输,但在大流量、高并发的应用场景下还建议多次测试。
Calico
Calco组件简介
Calico是一个基于BGP的纯三层的网络方案,与OpenStack、Kubernetes、AWS、GCE等云平台都能够良好地集成。Calico在每个计算节点都利用Linux Kernel实现了一个高效的vRouter来负责数据转发。每个vRouter都通过BGP1协议把在本节点上运行的容器的路由信息向整个Calico网络广播,并自动设置到达其他节点的路由转发规则。
Calico保证所有容器之间的数据流量都是通过IP路由的方式完成互联互通的。Calico节点组网时可以直接利用数据中心的网络结构(L2或者L3),不需要额外的NAT、隧道或者Overlay Network,没有额外的封包解包,能够节约CPU运算,提高网络效率。
Calico在小规模集群中可以直接互联,在大规模集群中可以通过额外的BGP route reflector来完成。Calico基于iptables还提供了丰富的网络策略,实现了Kubernetes的Network Policy策略,提供容器间网络可达性限制的功能。
Calico架构
Calico的主要组件:
Felix:Calico Agent,运行在每个Node上,负责为容器设置网络资源(IP地址、路由规则、iptables规则等),保证跨主机容器网络互通;
etcd:Calico使用的后端存储;
BGP Client:负责把Felix在各Node上设置的路由信息通过BGP协议广播到Calico网络;
Route Reflector:通过一个或者多个BGP Route Reflector来完成大规模集群的分级路由分发;
CalicoCtl:Calico命令行管理工具;