在微服务架构中,多个服务是通过服务注册中心进行管理的,服务需要将自己的IP地址和端口发送给注册中心,这样该服务才能被其它服务感知并调用。但是当服务在docker容器内运行时,服务获取到的自身IP是宿主机分配的内部IP(默认情况下会在172.17.0.0/16子网下),如172.17.0.1, 这个地址只能在宿主机内部使用(通过docker0网桥转发),其它机器是无法ping通的。我们就以服务注册的场景讨论docker容器跨主机通信方案。
端口映射
启动容器时通过 -p 参数将容器内服务监听的端口映射到主机端口中。例如容器运行的web服务监听8080端口,那么当指定-v 8080:80时,外部就可以通过访问宿主机的80端口访问到这个web服务了。这种方式有一个很大的缺点:服务器端口是一种稀缺资源,一台机器往往会运行多个容器,它们之间有可能会出现端口冲突的情况。而且就服务注册这个场景而言,容器内的web服务是无法主动得到宿主机的ip地址的,因此需要我们在启动容器时通过Dockerfile将宿主机IP通过环境变量注入到容器中,然后配置web项目使用我们指定的IP来注册自身。这种方式显然无法应用于大规模集群部署。
性能损失:20%
不进行网络隔离,直接使用宿主机网络配置
通过–net=host参数可指定使用该模式。在这种模式下,容器的网络环境并没有通过Linux内核的Network Namespace进行隔离,在容器内可以*修改宿主机的网络参数,因此是不安全的,但优点是网络性能损失可以忽略不计。对于我们的场景来说,微服务能够像直接部署一样正常获取到主机IP。
性能损失:几乎没有
组建overlay网络
Overlay网络其实就是隧道技术,即将一种网络协议包装在另一种协议中传输的技术。Docker常见的overlay网络实现有flannel, swarm overlay, Open vSwitch等。它们的工作流程基本都是一样的:先通过某种方式保证所有docker容器都有全局唯一的ip, 然后把docker容器的ip和其所在宿主机ip的对应关系存放到第三方存储服务中(如etcd, consul),之后通过在宿主机上修改路由表、创建虚拟网卡的方式,将数据包转发到目标容器所在的宿主机上,最后再由目标宿主机的docker0网桥转发给容器。对于flannel来说,它的工作原理如下图:
10.76.98.1和10.76.98.2是局域网内的两台物理机,它们各运行着容器A(172.17.0.1)和容器B(172.17.0.2)。当容器A要访问容器B时:
- 数据包首先到达docker0, 由于flannel修改了路由表,docker0会将其转发给flannel0
- flannel的守护进程flanneld会持续监听flannel0转出的数据包, 它首先会到 etcd 中查询172.17.0.2所在宿主机的ip(10.76.98.2),然后将原数据包进行封装(可以使用UDP或vxlan封装),把目的ip地址改为对方宿主机ip并交由eth0
- eth0将新数据包通过网络发到10.76.98.2
- 10.76.98.2的eth0收到数据包后转发给flannel0, 由守护进程flanneld进行解包,取出原数据包,得到容器ip地址172.17.0.2, 然后转发给docker0
- docker0将数据包转发至容器进程对应端口
到此容器A就实现了跨主机访问容器B。
overlay网络的性能损耗取决于其实现方式,经老外测试,flannel(vxlan模式), swarm overlay实现的损耗几乎与端口映射持平,但是 docker 1.12版本新加入的 swarm overlay 实现性能损耗高达60%(swarm overlay代码实现质量不高)。因此,在产生环境下不建议使用swarm overlay方案。
Calico和Weave
这两种实现的方式跟overlay不太一样,它会把每台宿主机都当成一个路由器使用,数据包在各个主机之间流动最终被投递到目标主机。为了让主机支持路由功能,它们会向路由表中写入大量记录,因些如果集群中的节点太多,路由表记录数过高(超过1万)时会出现性能问题。
虽然实现原理一样,但它们的性能区别还是很大的。Calico因为使用的是内核特性,能做到在内核态完成路由,因此性能与原生网络非常接近(90%以上),而Weave则是在用户态转发数据包,性能较差,损耗高达70%以上。
总结
overlay方案和Calico, Weave由于可以实现容器IP的直接通信,因此在服务注册的场景下都可以正常运行,但是需要付出一定的性能代价。而端口映射方式则需要强行配置我们的应用使用指定IP,灵活性极差,只适用于小规模的集群部署。而host模式则是通过牺牲隔离性来换取最大化网络性能。在实际应用中我们应该根据业务特点来选择最适合的网络方案。