Docker 网络深度解读

1,775 阅读12分钟
原文链接: click.aliyun.com
什么是Docker网络呢?总的来说,网络中的容器们可以相互通信,网络外的又访问不了这些容器。具体来说,在一个网络中,它是一个容器的集合,在这个概念里面的一个容器,它会通过容器的IP直接去通信,又能保证在这个集合外的一些容器不能够通过这个容器IP去通信,能做到网络隔离。网络这个概念是由网络的驱动去创建、管理的。网络的驱动又分为全局的和本地的,全局的意思是这个网络可以跨主机,没必要说我的两个容器非要在一个主机上才能通过这个网络去通信,我可以在不同主机上还是通过容器的IP相互通信。

本地网络

411be7918155e00b18863881650edb24786648a2

首先我们看一个本地网络的图,这个图中有一台主机,上面跑了四个容器,分别分布在两个网络里面,一个是NET1,一个是NET2。NET1里面的两个容器是可以互相通过自己的IP通信的,但是NET1里面的容器和NET2里面的容器又是隔离的,这样就是Docker的一个网络概念。

全局网络

5d644be114a98f7e2ade739fc756b2fa6f5a4938

全局网络意思是,一个网络里面的容器是可以跨主机的。C1的容器可以和主机2上的C3容器,通过它们IP直接通信,而不用管所在的主机在哪个地方。这个图里面我们还可以看到一个细节,同一个容器可以加入到两个网络里面,比如C4它可以和网络1里面的两个容器通信,也可以和网络2里面的C5进行通信,这样我们就可以实现更加复杂的一些组合。比如C5是一个数据库的容器,我不想让这个数据被前面的WEB应用或者其他的应用去访问到,只想让我的中间键应用去访问到。这样就可以通过让中间键应用加入到一个后台网络、一个前台网络的组合来实现这种互联和网络的控制。

网络服务发现

c3fa6ba34ebe62e770587afb6c5cb0afe29bb97e

在1.10版本增加了网络服务发现,什么意思呢?比如,我的一个网络有两个容器,我们可以直接通过IP进行通信,但是我的容器它可能某一段时间跪掉了,它可能在跪掉之后再去重启,我这时就不知道它的IP了,或者它一关闭之后,我这边感知不到,这样就要触发很多复杂的一些处理流程。在1.10时它去做这样的一件事,它在每个容器里面内置了一个DNS的服务器,在服务器RUN起来之后,会写一个容器名,或者是这个容器的一个别名和它IP的一个解析。外部容器去访问我的DB容器时,它能够通过DB这个名字来解析到DB容器的IP地址,这样在DB容器重启之后,这个解析会动态去调整,就能实现一个服务的发现。

Docker默认的网络驱动

Docker默认的会有一些网络驱动,它本地的网络驱动有这几种。none网络驱动意思是,我创建一个容器,但是没有它的一些网络配置,单独一个命名空间,它不能和其他容器通信,也不能访问外网。host的网络驱动和宿主机共享网络栈、它和宿主机看到同样的接口,包括它自己暴露的端口,都是和宿主机在同一个网络空间里面。

e79f2fead86dfa0e0f7d7868df8f10bffa555d3e

bridge网络驱动,它是一个本地的网络驱动,上图主机上有两个bridge网络,它实际上是通过Linux的bridge去驱动的,通过容器里面挂载一个网卡,对应的是在宿主机上一个veth,容器的请求会直接转发到这个veth上面,bridge上面会对这个请求进行洪泛,它就能到达这个网络里面的另外一个容器。同理,另外一个网络,它跟这个网络里面的容器没有在同一个网桥上面,所以他们之间是互相不能通信的。bridge驱动还会实现另外一个功能,比如说C1的容器,他要访问公网,它通过bridge出去的时候,他最终到达宿主机的接口,他会做一个网络地址的转换,把容器的IP转换成宿主机的IP,这样就能访问公网。或者我的容器可能希望服务映射到外面去,让别的机器或别的公开服务可以访问到,他可以做一个DNAT一个规则,比如我的容器是Nginx,它里面是80端口,我想映射到主机的一个9080,可以直接通过主机的9080去访问,他可以去做一个DNAT的配置,这是bridge的驱动。

40aef45f7ed4d2a262d3dd6e333cbda12a128363

Docker默认还有一个overlay的驱动,它主要是去实现跨主机的一个通信,它会在主机之间通过vxlan的方式打一个隧道,实现我的主机上的容器去访问主机上的容器,最终会转换成一个vxlan里面的通信。

Docker跨主机网络

没有跨主机网络时,比如一个应用有两个服务,两个服务不可能部署到同一个机器上,这样没办法做到高可用,所在的主机一挂,上面所有的服务都挂了。如果让它部署到两个主机上面,但是两个主机上面这个容器的IP在主机外面是看不到的。所以只能把容器的IP转换成了一个宿主机的一个IP,或者宿主机的一个访问端点,才能让外部的主机访问。所以,以前的方式是通过端口映射,把容器的一个端口映射到主机的一个端口,让另外一个主机和这个主机映射到这个容器上去通信。这样会带来哪些问题?首先我通过端口映射,比如说我映射到8090端口,我另外再想起一个容器。他就得映射到另外一个端口,因为宿主机上只有这一个8090端口,它不可能说共享这个端口,所以一个主机上去起容器的时候,需要管理端口的列表。映射出去之后是宿主机的一个端口,这样这个端口就是对外暴露的,可能要去控制一下,这个端口只能说主机要去访问,这样要去做一些安全的配置,所以这样的整个架构是非常复杂的。在Docker1.9之后,增加了跨主机的网络,它就可以直接通过容器的IP去通信,通过这个外部跟它是隔离的,这样又能做到安全,我就算再起一个容器,它容器的IP端口是不会冲突的。

封包模式

封包模式,比如overlay驱动,它是用VXLAN的协议去把容器之间的请求封装成VXLAN的请求,将链路层的以太网包封装到UDP包中进行传输。

f35f75d31aad9ccf8828c5635bfe5d2314d88ea1

这里有一个简单的图来介绍封包模式,首先举个例子,从C1去访问宿主机上C2的容器,先通过C1发出这个包之后,它首先进行封包,要查找中心化存储C2是在哪个主机上的,查到的是这个主机上,就把这个请求的包封装成宿主机之间的包,把这个包送达到对应宿主机上,再通过一定的约定去解包,最终转发到另外一个主机上的一个容器。其缺点是对带宽的损耗比较大,因为看到这里去封包,它肯定是把这个容器的包上面又加了一层包,这个包上面肯定会有一些自己的占用,一般像封包模式,MTU最终都会小于宿主机之间的MTU,它还会造成一些资源占用。比如说在这个地方去封包的时候,它可能会占用CPU去做一下封包操作,还会占用CPU去做解包的操作,所以会增加主机上的负载,但是它的好处是对基础设施要求比较低,它只需要两个宿主机之间可以三层互通。

62f38414dc15f751820a1b54678d7d21b359d5e3

下面我们以Docker的overlay驱动来介绍下封包的实现,它的封包方式是基于VXLAN。上图是它的针格式,VXLAN内部把一个二层的包、链路层的一个请求封装成一个VXLAN的一个包,这里面会有一个约定的信息,比如VXLAN ID,还有一个外部的UDP的头,最终会把这个包通过UDP发往对应的主机上面去,对应的主机再做VXLINE的解包。Docker还会做IP的地址管理和网络信息同步。

路由模式

44237a4c3a450f6c5901bfd36901c9dae8bb380b

如上图,比如,主机1上的容器想要访问主机2的容器,首先这个机器上是没有主机2的IP地址,它出了机器之后到达路由器,再通过路由器上的路由表,去把对应的请求网端转发到对应的主机2上面,主机2上面是有这个容器的IP,所以它就能够做到这个容器的跨主机通信。它的好处在于它没有封包,所以它的性能很好,但是它对于基础设施有一些要求,比如我的基础设施要支持、能够配置这个路由表,如果用Linux路由要支持二层的转发。

40e5882f11b3d30d52b12783205fe115d27485ac

VPC网络驱动是在阿里云的VPC基础上,能够实现容器互通的一种方案。首先在Docker DM启动时,或者是在创建网络时,会从每个机器的一个中心化存储里面,拿到两个自己的网端,比如这个机器上的网端,比如左边主机上的网端就是172.19.1.1/24,它通过阿里云的openapi去配置这个转发表,把这个网端的地址都转发到这个主机上面。另外一个主机启动时,把它拿到一个可用的网端,去配置阿里云的一个路由表,把这个网端的请求转发到自己的主机上面,比如说我这边的一个容器想要去访问主机2上C2的容器时,它这个包最终到达,通过vSwitch到达阿里云VPC的vRouter时,通过查询路由表把实时的请求包转发到主机2上面,最终实现C1和C2的通信。

跨主机网络方案对比

封包模式的优点是对基础设施的要求比较低,但是它性能损耗比较大,路由模式是没有封包的,所以它性能比较好,但是对基础设施有一定要求,比如说要支持路由表,或者要支持二层的转发。

阿里云服务的网络方案

容器服务上面现在有两种集群,一种是阿里云的ECS集群,这种集群的节点都是在阿里云的同地域或者同一个VPC下面的ECS,或者是混合云的集群,阿里云的混合云集群节点是在用户自己建立的机房,或者分布在不同的云供应商上面。

8ce89db02d53270f4db4a61e2ab0b684aeb63e0a

在阿里云的ECS集群上面,网络架构是这样的,容器之间去通信,还是通过overlay驱动,它的好处是对基础设施要求比较低,所以它只需要主机之间互通就可以了,容器去访问外网,或者容器想要暴露端口给外网就通过一个gateway_bridge的网桥、本地的一个驱动,这个是供容器访问外网和容器的端口映射。在ECS集群上面,VPC网络是通过VPC驱动的路由表把对应的请求转发到容器所在机器上面,它去访问外网和做端口映射也是和刚才所介绍的overlay是同样的。

b3c806fe7d84eb80b6cb87ab6a2f22f11583d7fc

混合云集群是结合阿里云的VPC技术去实现容器之间的网络互通。在混合云集群里面是通过专线的方式,把对应的数据中心的请求转发到我的数据中心里面,或者把数据中心的请求访问VPC的,通过专线的一个路由也可以转发到VPC里面,再通过路由表把容器的请求转发到对应的主机上面,最终实现的方式是把我VPC里面的容器和数据中心里面的容器互通。

b60ec28d1c53667c1e09293f5df6913f0689c05b

刚才我们介绍的都是容器之间通信,我们可以不通过端口映射的方式做到端口不冲突,怎么能够做到我想要外部访问时,也能做到就一个负载均衡,怎么能做到一个机器上面部署多个容器,然后端口不冲突呢?这时候阿里云就提供了这样的一个负载均衡方案。比如说我的网站有两个域名,一个是购物的域名,一个是付款的,但是我总共就三台机器,每个服务要做到高可用,所以起了很多的容器,这样在同一个主机上就要去处理,怎么让它保障单个不冲突,容器服务是提供一个这样的方案,我们在集群里面会部署一套路由的一个服务,首先请求,比如说我的公司域名的泛解析会解析到我的路由服务上面去,路由服务再通过请求的域名Host,去把这个请求直接转发到容器上面,最终实现我的一个请求可以转发到容器上面,但是我的容器并没有映射出端口,也不需要去解决端口冲突的问题。