自从docker容器出现以来,容器的网络通讯就一直是你们关注的焦点,也是生产环境的迫切需求。而容器的网络通讯又能够分为两大方面:单主机容器上的相互通讯,和跨主机的容器相互通讯。而本文将分别针对这两方面,对容器的通讯原理进行简单的分析,帮助你们更好地使用docker。html
基于对net namespace的控制,docker能够为在容器建立隔离的网络环境,在隔离的网络环境下,容器具备彻底独立的网络栈,与宿主机隔离,也可使容器共享主机或者其余容器的网络命名空间,基本能够知足开发者在各类场景下的须要。按docker官方的说法,docker容器的网络有五种模式:node
这些网络模式在相互网络通讯方面的对好比下所示:mysql
模式 | 是否支持多主机 | 南北向通讯机制 | 东西向通讯机制 |
---|---|---|---|
bridge | 否 | 宿主机端口绑定 | 经过Linux bridge |
host | 是 | 按宿主机网络通讯 | 按宿主机网络通讯 |
none | 否 | 没法通讯 | 只能用link通讯 |
其余容器 | 否 | 宿主机端口绑定 | 经过link通讯 |
用户自定义 | 按网络实现而定 | 按网络实现而定 | 按网络实现而定 |
南北向通讯指容器与宿主机外界的访问机制,东西向流量指同一宿主机上,与其余容器相互访问的机制。linux
因为容器和宿主机共享同一个网络命名空间,换言之,容器的IP地址即为宿主机的IP地址。因此容器能够和宿主机同样,使用宿主机的任意网卡,实现和外界的通讯。其网络模型能够参照下图: sql
采用host模式的容器,能够直接使用宿主机的IP地址与外界进行通讯,若宿主机具备公有IP,那么容器也拥有这个公有IP。同时容器内服务的端口也可使用宿主机的端口,无需额外进行NAT转换,并且因为容器通讯时,再也不须要经过linuxbridge等方式转发或者数据包的拆封,性能上有很大优点。固然,这种模式有优点,也就有劣势,主要包括如下几个方面: docker
bridge模式是docker默认的,也是开发者最常使用的网络模式。在这种模式下,docker为容器建立独立的网络栈,保证容器内的进程使用独立的网络环境,实现容器之间、容器与宿主机之间的网络栈隔离。同时,经过宿主机上的docker0网桥,容器能够与宿主机乃至外界进行网络通讯。其网络模型能够参考下图: 安全
从上面的网络模型能够看出,容器从原理上是能够与宿主机乃至外界的其余机器通讯的。同一宿主机上,容器之间都是链接掉docker0这个网桥上的,它能够做为虚拟交换机使容器能够相互通讯。然而,因为宿主机的IP地址与容器veth pair的 IP地址均不在同一个网段,故仅仅依靠veth pair和namespace的技术,还不足以使宿主机之外的网络主动发现容器的存在。为了使外界能够方位容器中的进程,docker采用了端口绑定的方式,也就是经过iptables的NAT,将宿主机上的端口端口流量转发到容器内的端口上。 服务器
举一个简单的例子,使用下面的命令建立容器,并将宿主机的3306端口绑定到容器的3306端口: docker run -tid --name db -p 3306:3306 mysql
网络
在宿主机上,能够经过iptables -t nat -L -n
,查到一条DNAT规则:tcp
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.5:3306
上面的172.17.0.5
即为bridge模式下,建立的容器IP。
很明显,bridge模式的容器与外界通讯时,一定会占用宿主机上的端口,从而与宿主机竞争端口资源,对宿主机端口的管理会是一个比较大的问题。同时,因为容器与外界通讯是基于三层上iptables NAT,性能和效率上的损耗是能够预见的。
在这种模式下,容器有独立的网络栈,但不包含任何网络配置,只具备lo这个loopback网卡用于进程通讯。也就是说,none模式为容器作了最少的网络设置,可是俗话说得好“少便是多”,在没有网络配置的状况下,经过第三方工具或者手工的方式,开发这任意定制容器的网络,提供了最高的灵活性
其余网络模式是docker中一种较为特别的网络的模式。在这个模式下的容器,会使用其余容器的网络命名空间,其网络隔离性会处于bridge桥接模式与host模式之间。当容器共享其余容器的网络命名空间,则在这两个容器之间不存在网络隔离,而她们又与宿主机以及除此以外其余的容器存在网络隔离。其网络模型能够参考下图:
在这种模式下的容器能够经过localhost来同一网络命名空间下的其余容器,传输效率较高。并且这种模式还节约了必定数量的网络资源,但它并无改变容器与外界通讯的方式。在一些特殊的场景中很是有用,例如,kubernetes的pod,kubernetes为pod建立一个基础设施容器,同一pod下的其余容器都以其余容器模式共享这个基础设施容器的网络命名空间,相互之间以localhost访问,构成一个统一的总体。
在用户定义网络模式下,开发者可使用任何docker支持的第三方网络driver来定制容器的网络。而且,docker 1.9以上的版本默认自带了bridge和overlay两种类型的自定义网络driver。能够用于集成calico、weave、openvswitch等第三方厂商的网络实现。
除了docker自带的bridge driver,其余的几种driver均可以实现容器的跨主机通讯。而基于bdrige driver的网络,docker会自动为其建立iptables规则,保证与其余网络之间、与docker0之间的网络隔离。
例如,使用下面的命令建立一个基于bridge driver的自定义网络:
docker network create bri1
则docker会自动生成以下的iptables规则,保证不一样网络上的容器没法互相通讯。
-A DOCKER-ISOLATION -i br-8dba6df70456 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-8dba6df70456 -j DROP
除此以外,bridge driver的全部行为都和默认的bridge模式彻底一致。而overlay及其余driver,则能够实现容器的跨主机通讯。
早期你们的跨主机通讯方案主要有如下几种:
容器使用host模式:容器直接使用宿主机的网络,这样天生就能够支持跨主机通讯。虽然能够解决跨主机通讯问题,但这种方式应用场景颇有限,容易出现端口冲突,也没法作到隔离网络环境,一个容器崩溃极可能引发整个宿主机的崩溃。
端口绑定:经过绑定容器端口到宿主机端口,跨主机通讯时,使用主机IP+端口的方式访问容器中的服务。显而易见,这种方式仅能支持网络栈的四层及以上的应用,而且容器与宿主机紧耦合,很难灵活的处理,可扩展性不佳。
docker外定制容器网络:在容器经过docker建立完成后,而后再经过修改容器的网络命名空间来定义容器网络。典型的就是好久之前的pipework,容器以none模式建立,pipework经过进入容器的网络命名空间为容器从新配置网络,这样容器网络能够是静态IP、vxlan网络等各类方式,很是灵活,容器启动的一段时间内会没有IP,明显没法在大规模场景下使用,只能在实验室中测试使用。
第三方SDN定义容器网络:使用Open vSwitch或Flannel等第三方SDN工具,为容器构建能够跨主机通讯的网络环境。这些方案通常要求各个主机上的docker0网桥的cidr不一样,以免出现IP冲突的问题,限制了容器在宿主机上的可获取IP范围。而且在容器须要对集群外提供服务时,须要比较复杂的配置,对部署实施人员的网络技能要求比较高。
上面这些方案有各类各样的缺陷,同时也由于跨主机通讯的迫切需求,docker 1.9版本时,官方提出了基于vxlan的overlay网络实现,原生支持容器的跨主机通讯。同时,还支持经过libnetwork的plugin机制扩展各类第三方实现,从而以不一样的方式实现跨主机通讯。就目前社区比较流行的方案来讲,跨主机通讯的基本实现方案有如下几种:
基于隧道的overlay网络:按隧道类型来讲,不一样的公司或者组织有不一样的实现方案。docker原生的overlay网络就是基于vxlan隧道实现的。ovn则须要经过geneve或者stt隧道来实现的。flannel最新版本也开始默认基于vxlan实现overlay网络。
基于包封装的overlay网络:基于UDP封装等数据包包装方式,在docker集群上实现跨主机网络。典型实现方案有weave、flannel的早期版本。
基于三层实现SDN网络:基于三层协议和路由,直接在三层上实现跨主机网络,而且经过iptables实现网络的安全隔离。典型的方案为Project Calico。同时对不支持三层路由的环境,Project Calico还提供了基于IPIP封装的跨主机网络实现。
下面,本从网络通讯模型的角度,对这些方案的通讯原理作一个简单的比较,从中能够窥见各类方案在性能上的本质差异。
首先,科普下docker容器的CNM网络模型,calico、weave等第三方实现都是基于CNM模型与docker集成的。CNM网络模型的结构以下图所示:
在上面的图中:
docker官方文档的示例中,overlay网络是在swarm集群中配置的,但实际上,overlay网络能够独立于swarm集群实现,只须要知足如下前提条件便可。
有consul或者etcd,zookeeper的集群key-value存储服务
组成集群的全部主机的主机名不容许重复,由于docker守护进程与consul通讯时,以主机名相互区分
全部主机均可以访问集群key-value的服务端口,按具体类型须要打开进行配置。例如docker daemon启动时增长参数 --cluster-store=etcd://<ETCD-IP>:4001 -
-cluster-advertise=eth0:2376
overlay网络依赖宿主机三层网络的组播实现,须要在全部宿主机的防火墙上打开下列端口
协议 | 端口 | 说明 |
---|---|---|
udp | 4789 | 容器之间流量的vxlan端口 |
tcp/udp | 7946 | docker守护进程的控制端口 |
+ 宿主机内核版本3.10以上(1.9版本时,要求3.16以上)
知足以上条件后,就能够经过docker network
命令来建立跨主机的overlay网络了,例如: docker network create -d overlay overlaynet
在集群的不一样主机上,使用overlaynet
这个网络建立容器,造成以下图所示的网络拓扑:
因为容器和overlay的网络的网络命名空间文件再也不操做系统默认的/var/run/netns
下,只能手动经过软链接的方式查看。 ln -s /var/run/docker/netns /var/run/netns
这样就能够经过ip netns
查看到容器和网络的网络命名空间了。
容器的网络命名空间名称能够经过docker inspect -f '{{.NetworkSettings.SandboxKey}}' <容器ID>
方式查看到。网络的网络命名空间则是经过docker network ls
查看到的网络短ID。
有时候网络的网络命名空间名称前面会带上
1-
、2-
等序号,有时候不带。但不影响网络的通讯和操做。
从这个通讯过程当中来看,跨主机通讯过程当中的步骤以下:
1. 容器的网络命名空间与overlay网络的网络命名空间经过一对veth pair链接起来,当容器对外通讯时,veth pair起到网线的做用,将流量发送到overlay网络的网络命名空间中。
2. 容器的veth pair对端eth2与vxlan设备经过br0这个Linux bridge桥接在一块儿,br0在同一宿主机上起到虚拟机交换机的做用,若是目标地址在同一宿主机上,则直接通讯,若是再也不则经过设置在vxlan1这个vxlan设备进行跨主机通讯。
3. vxlan1设备上会在建立时,由docker daemon为其分配vxlan隧道ID,起到网络隔离的做用。
4. docker主机集群经过key/value存储共享数据,在7946
端口上,相互之间经过gossip
协议学习各个宿主机上运行了哪些容器。守护进程根据这些数据来在vxlan1设备上生成静态MAC转发表。
5. 根据静态MAC转发表的设置,经过UDP端口4789
,将流量转发到对端宿主机的网卡上。
6. 根据流量包中的vxlan隧道ID,将流量转发到对端宿主机的overlay网络的网络命名空间中。
7. 对端宿主机的overlay网络的网络命名空间中br0网桥,起到虚拟交换机的做用,将流量根据MAC地址转发到对应容器内部。
虽然上面的网络通讯模型能够实现容器的跨主机通讯,但仍是有一些缺陷,形成实际使用上的不便,例如:
eth1
做为宿主机与容器通讯的通道。这样在使用容器服务时,就必须根据访问性质的不一样,选择不一样的网卡地址,形成使用上的不便。 weave经过在docker集群的每一个主机上启动虚拟的路由器,将主机做为路由器,造成互联互通的网络拓扑,在此基础上,实现容器的跨主机通讯。其主机网络拓扑参见下图:
如上图所示,在每个部署Docker的主机(多是物理机也多是虚拟机)上都部署有一个W(即weave router,它自己也能够以一个容器的形式部署)。weave网络是由这些weave routers组成的对等端点(peer)构成,而且能够经过weave命令行定制网络拓扑。
每一个部署了weave router的主机之间都会创建TCP和UDP两个链接,保证weave router之间控制面流量和数据面流量的经过。控制面由weave routers之间创建的TCP链接构成,经过它进行握手和拓扑关系信息的交换通讯。控制面的通讯能够被配置为加密通讯。而数据面由weave routers之间创建的UDP链接构成,这些链接大部分都会加密。这些链接都是全双工的,而且能够穿越防火墙。
当容器经过weave进行跨主机通讯时,其网络通讯模型能够参考下图:
从上面的网络模型图中能够看出,对每个weave网络中的容器,weave都会建立一个网桥,而且在网桥和每一个容器之间建立一个veth pair,一端做为容器网卡加入到容器的网络命名空间中,并为容器网卡配置ip和相应的掩码,一端链接在网桥上,最终经过宿主机上weave router将流量转发到对端主机上。其基本过程以下:
weave默认基于UDP承载容器之间的数据包,而且能够彻底自定义整个集群的网络拓扑,但从性能和使用角度来看,仍是有比较大的缺陷的:
weave
命令行来手工构建网络拓扑,在大规模集群的状况下,加剧了管理员的负担。calico是纯三层的SDN 实现,它基于BPG 协议和Linux自身的路由转发机制,不依赖特殊硬件,容器通讯也不依赖iptables NAT或Tunnel 等技术。可以方便的部署在物理服务器、虚拟机(如 OpenStack)或者容器环境下。同时calico自带的基于iptables的ACL管理组件很是灵活,可以知足比较复杂的安全隔离需求。
在主机网络拓扑的组织上,calico的理念与weave相似,都是在主机上启动虚拟机路由器,将每一个主机做为路由器使用,组成互联互通的网络拓扑。当安装了calico的主机组成集群后,其拓扑以下图所示:
每一个主机上都部署了calico/node做为虚拟路由器,而且能够经过calico将宿主机组织成任意的拓扑集群。当集群中的容器须要与外界通讯时,就能够经过BGP协议将网关物理路由器加入到集群中,使外界能够直接访问容器IP,而不须要作任何NAT之类的复杂操做。
当容器经过calico进行跨主机通讯时,其网络通讯模型以下图所示:
从上图能够看出,当容器建立时,calico为容器生成veth pair,一端做为容器网卡加入到容器的网络命名空间,并设置IP和掩码,一端直接暴露在宿主机上,并经过设置路由规则,将容器IP暴露到宿主机的通讯路由上。于此同时,calico为每一个主机分配了一段子网做为容器可分配的IP范围,这样就能够根据子网的CIDR为每一个主机生成比较固定的路由规则。
当容器须要跨主机通讯时,主要通过下面的简单步骤:
从上面的通讯过程来看,跨主机通讯时,整个通讯路径彻底没有使用NAT或者UDP封装,性能上的损耗确实比较低。但正式因为calico的通讯机制是彻底基于三层的,这种机制也带来了一些缺陷,例如:
转载:http://blog.csdn.net/yarntime/article/details/51258824