转载自:https://mp.weixin.qq.com/s/axpDkWCEkvwudOylzNc-8g  微信公众号:Docker


                                                                                       640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

容器的网络是在CaaS集群中无法避免的话题,作为当下最主流的一种容器集群解决方案,Kubernetes对网络进行了合理的抽象,并采用了开放的CNI模型。面对各种容器网络实现,他们有什么不同,应该如何选择?本文将带大家回顾Kubernetes各种主流网络方案的发展历程,并透过现象清本质,用形象的例子展示Weave、Flannel、Calico和Romana等网络解决方案背后的原理。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


这次讲一讲容器集群中的网络。其实不同的容器集群解决方案,在网络方面的核心原理都是相似的,只不过这次我们将以Kubernetes为线索,来窥斑见豹的一睹容器网络的发展过程。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


我是来自ThoughtWorks的林帆,我们从Docker的0.x版本开始就在对容器的应用场景进行探索,积累了一线的容器运用经验。这次分享会用简洁易懂的方式告诉大家我们对容器网络方面的一些知识归纳。



初入容器集群的人往往会发现,和单节点的容器运用相比,容器的网络和存储是两个让人望而却步的领域。在这些领域里,存在大量的技术名词和术语,就像是一道道拒人于门外的高门槛。



为了便于理解,我们将这些名称简单的分个类别,从简单到复杂,依次递增。今天的话题会涉及的深度大致在这个大池子的中间,希望大家看完以后会对目标线以上的内容不再陌生,目标线以下的内容我们也会依据需要适当的提及。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


此外,这个话题会按照Kubernetes的网络发现过程作为时间主线,其中重点介绍CNI标准和它的主流实现。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


在早期的Kubernetes中,对网络是没有什么标准的。文档中对网络的描述只有很简单的几句话,其实就是让用户在部署Kubernetes之前,预先准备好容器互联的网络解决方案。Kubernetes只对网络提出设计假设,这三条假设总结起来就是:所有容器都可以和集群里任意其他容器或者主机通信,并且通信双方看到的对方IP地址就是实际的地址(即不经过网络地址转换)。


基于这样的底层网络,Kubernetes设计了Pod - Deployment - Service的经典三层服务访问机制。


既然Kubernetes不提供底层网络实现,在业界就出现了很多企业级的以及开源的第三方解决方案,其中下面这页图中展示了这个时期的主流开源方案。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


我们将这些方案依据配置的复杂度,分为“全自动”和“半自动”两类,就像是汽车中的自动挡和手动挡的差别。


“全自动”的解决方案使用起来简单,适用于标准网络环境的容器跨节点通信。


“半自动”的解决方案实际上是对底层协议和内核模块功能的封装,提供了选择十分丰富的网络连接方法,但对使用者的网络原理知识有一定要求。



在Kubernetes的1.1版本中,发生了一件很重要的变化,那就是Kubernetes全面采纳CNI网络标准。


CNI诞生于2015年4月,Kubernetes的1.1版本诞生于2015年9月,之间仅隔5个月。在这个时期,Docker也设计了一套网络标准,称为CNM(即Libnetwork)。Kubernetes采用CNI而非CNM,这背后有很长的一段故事,核心的原因就是CNI对开发者的约束更少,更开放,不依赖于Docker工具,而CNM对Docker有非常强的依赖,无法作为通用的容器网络标准。在Kubernetes的官方博客里有一篇博文详细讨论了个中细节,InfoQ上有一篇该博客的翻译,有兴趣的读者不妨一读。



几乎在Kubernetes宣布采纳CNI以后的1个月,之前提到的“全自动”网络方案就悉数实现了CNI的插件,这也间接说明了CNI的简单。


那么CNI到底有多简单呢?举几个数字。


实现一个CNI插件需要的内容包括一个Json配置文件和一个可执行的文件(脚本或程序),配置文件描述插件的版本、名称、描述等基本信息,可执行文件就是CNI插件本身会在容器需要建立网络和需要销毁容器的时候被调用。


当一个CNI插件被调用时,它能够通过6个环境变量以及1个命令行参数(Json格式文本)来获得需要执行的操作、目标网络Namespace、容器的网卡必要信息,每个CNI插件只需实现两种基本操作:创建网络的ADD操作,和删除网络的DEL操作(以及一个可选的VERSION查看版本操作)。


最新的CNI规范可以通过上图中的链接访问,只有一页网页的内容而已。同时Kuberntes社区也提供了一个利用“docker network”命令实现的简单CNI插件例子,只用了几十行Bash脚本。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


那么面对这么多的社区CNI插件,我们怎样选择呢?


直观的说,既然是网络插件,在功能差不多的情况下,我们当然先关心哪个的速度快。


为此我此前专门做过一次对比测试,不过由于使用了公有云的网络环境(云上环境的不同主机之间相对位置不固定),在汇总数据的时候才发现测试结果偏离理论情况过于明显。


这个数据大家且当娱乐,不过对于同一种插件的不同工作模式(比如Flannel的三种模型)之间,由于是使用的相同的虚拟机测试,还是具有一定可参考性。


先抛开测试结果,从理论上说,这些CNI工具的网络速度应该可以分为3个速度等级。


最快的是Romana、Gateway模式的Flannel、BGP模式的Calico。


次一级的是IPIP模式的Calico、Swarm的Overlay网络、VxLan模式的Flannel、Fastpath模式的Weave。


最慢的是UDP模式的Flannel、Sleeve模式的Weave。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


这里我也提供出测试容器网络速度的具体方法,以便大家重复这个测试。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


要解释这些网络插件和模式速度不同的原因,我们需要先回到这些工具最初要解决的问题上来。那就是跨节点的网络不通。


如果仔细观察,会发现在3种网络速度模式中都有Flannel的身影。因此我们不妨先以Flannel为例来看这些网络工具(和相应的CNI插件)是如何解决网络不通问题的。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


跨节点网络不同有几个方面的原因,首先是容器的地址重复。


由于Docker等容器工具只是利用内核的网络Namespace实现了网络隔离,各个节点上的容器是在所属节点上自动分配IP地址的,从全局来看,这种局部地址就像是不同小区里的门牌号,一旦拿到一个更大的范围上看,就可能是会重复的。



为了解决这个问题,Flannel设计了一种全局的网络地址分配机制,即使用Etcd来存储网段和节点之间的关系,然后Flannel配置各个节点上的Docker(或其他容器工具),只在分配到当前节点的网段里选择容器IP地址。


这样就确保了IP地址分配的全局唯一性。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


是不是地址不重复网络就可以联通了呢?


这里还有一个问题,是对于不同的主机、以及网络上的路由设备之间,并不知道这些IP容器网段的地址是如何分配的,因此数据包即使被发送到了网络中,也会因为无法进行路由而被丢掉。


虽然地址唯一了,依然无法实现真正的网络通信。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


为此,Flannel采用了几种处理方法(也就是Flannel的几种网络模式)。


早期时候用的比较多的一种方式是Overlay网络。


在这种方式下,所有被发送到网络中的数据包会被添加上额外的包头封装。这些包头里通常包含了主机本身的IP地址,因为只有主机的IP地址是原本就可以在网络里路由传播的。


根据不同的封包方式,Flannel提供了UDP和Vxlan两种传输方法。


UDP封包使用了Flannel自定义的一种包头协议,数据是在Linux的用户态进行封包和解包的,因此当数据进入主机后,需要经历两次内核态到用户态的转换。


VxLAN封包采用的是内置在Linux内核里的标准协议,因此虽然它的封包结构比UDP模式复杂,但由于所有数据装、解包过程均在内核中完成,实际的传输速度要比UDP模式快许多。


但比较不幸的是,在Flannel开始流行的时候,大概2014年,主流的Linux系统还是Ubuntu 14.04或者CentOS 6.x,这些发行版的内核比较低,没有包含VxLAN的内核模块。因此多数人在开始接触Flannel时候,都只能使用它的UDP模式,因此Flanned一不小心落得了一个“速度慢”的名声,特别是在之后的Calico出来以后(其实Flannel的Gateway模式与Calico速度相当,甚至理论上还要快一点点,稍后解释)。


这是第一种解决网络无法路由的方法。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


第二种思路是,既然在无法进行路由是因为网络中的节点之间没有路由信息,但Flannel是知道这个信息的,能不能把这个信息告诉网络上的节点呢?


这就是Flannel的Host-Gateway模式,在这种模式下,Flannel通过在各个节点上的Agent进程,将容器网络的路由信息刷到主机的路由表上,这样一来所有的主机就都有整个容器网络的路由数据了。


Host-Gateway的方式没有引入像Overlay中的额外装包解包操作,完全是普通的网络路由机制,它的效率与虚拟机直接的通信相差无几。


然而,由于Flannel只能够修改各个主机的路由表,一旦主机直接隔了个其他路由设备,比如三层路由器,这个包就会在路由设备上被丢掉。


这样一来,Host-Gateway的模式就只能用于二层直接可达的网络,由于广播风暴的问题,这种网络通常是比较小规模的,但近年来也出现了一些专门的设备能够构建出大规模的二层网络(就是我们经常听到的“大二层”网络)。


那么其他的CNI插件呢?


由于Flannel几乎是最早的跨网络通信解决方案,其他的方案都可以被看做是Fannel的某种改进版。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


比如Weave的工作模式与Flannel是很相似的,它最早只提供了UDP(称为sleeve模式)的网络方式,后来又加上了fastpass方式(基于VxLAN),不过Weave消除了Flannel中用来存储网络地址的额外组件,自己集成了高可用的数据存储功能。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


Calico的设计比较新颖,之前提到Flannel的Host-Gateway模式之所以不能跨二层网络,是因为它只能修改主机的路由,Calico把改路由表的做法换成了标准的BGP路由协议。


相当于在每个节点上模拟出一个额外的路由器,由于采用的是标准协议,Calico模拟路由器的路由表信息就可以被传播到网络的其他路由设备中,这样就实现了在三层网络上的高速跨节点网络。


不过在现实中的网络并不总是支持BGP路由的,因此Calico也设计了一种IPIP模式,使用Overlay的方式来传输数据。IPIP的包头非常小,而且也是内置在内核中的,因此它的速度理论上比VxLAN快一点点,但安全性更差。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


Cannal将Calico和Flannel做了一下组合,同时支持两者的特性。



Romana只支持与Flannel相同的Host-Gateway模式,但它在网络策略方面做了比较多的增强,通过额外引入的租户概念简化了网络策略所需的IPtables规则数量。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


这是几种主流CNI工具的横向对比。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


在Kubernetes的1.2版本以后开始引入了一个新的工具,叫做 kubernet,它实现了内置的网络地址分配功能。结合一些云平台上的内网路由表功能,就能够直接执行跨网络通信,相当于把跨网络功能内建到Kubernetes里面了。


这是一个从“只做假设”到“设定标准”到“内置实现”的很大的改变。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


在Kubernetes的1.3版本以后,开始加入网络策略相关的支持。并且在1.7版本后结束了Beta阶段,成为正式API的一部分。


值得一说的是,Kubernetes的网络策略采用了比较严格的单向流控制,即假如允许服务A访问服务B,反过来服务B并不一定能访问服务A。这与Docker内置的Network命令实现的隔离有很大不同。


640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


纵向的对比一下主流的容器集群对今天提到的这些网络特性的支持情况和时间点。


Q&A

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1


Q:Kubernetes的网络策略采用了比较严格的单向流控制,即假如允许服务A访问服务B,反过来服务B并不一定能访问服务A。为什么要设计成严格单向流呢?A:主要是安全性的原因,这是一种更精细的权限控制策略,除了方向,Kuberetes还允许对可访问的端口进行控制。


Q:Open vSwitch有测过么?

A:没有测试,Open vSwitch同样可以配置成Overlay网络或者Macvlan网络等不同的通信模式,速度也会落到相应的档位上。那个测试例子只是为了说明网络速度与采用的通信原理有关,同时引出几种主流的通信模式原理,测试数据是不准确的,建议以在自己的实际环境中测试结果为准。


Q:Calico怎么做网段间的隔离?

A:各种网络工具的网络策略基本上都是基于内核的Iptables模块实现的。比如Calico只是根据用户配置,自动管理各个节点上的Iptables规则。其他有些改进的,比如Romana设计了一种基于“租户”的规则管理机制,可以用更少的Iptables规则实现网络隔离。


Q:如果在Kubernetes里面需要做到平行网络,让每一个Pod获取一个业务IP,除了Bridge+Vlan的方式,还有更好的建议么?

A:这次讲的这些CNI插件都会让每一个Pod获得一个独立业务IP。可以根据实际网络情况和对速度的需求选择。


Q:Calico BGP IPIP NAT三种模式分别怎么配置?原理是怎样的?其中IPIP还有两种模式,区别在哪?

A:在Calico的配置中设置spec.ipip.enabled: ture就会开启IPIP模式,否则默认是纯BGP模式。IPIP的两种模式其实是指纯IPIP(ipip always模式)或者混合IPIP和BGP(ipip cross-subnet),后者指的是“同子网内路由采用BGP,跨子网路由采用IPIP”,主要用于即想在内网获得高速,又想在跨公网时能保持联通的情况。这种模式需要每个节点启动时用--ip参数预先配置节点的子网,并且所有节点版本都在v2.1以上。


Q:能麻烦具体介绍一下kube-proxy这种网络模式的优缺点吗,在测试过程中发现很不稳定,但是又没发现足够的证据。

A:kube-proxy是Kubernetes的一个组件,提供通过Service到Pod的路由,并不是一种网络模式,不同的网络插件都会使用到这个组件。