谭林华 分布式实验室
Docker已经成为时下热门的容器技术,各大公司,中小创业者,都选择采用Docker技术架构其下一代的系统和应用。 随着系统规模扩大,单一Docker容器不能应对压力,需要横向扩展到多个容器,我们需要考虑负载均衡的问题;单一职责的Docker容器之间需要相互通信,而容器在每一次重启之后,它的IP都可能发生变化,那么服务和服务之间如何发现对方? 这一次我们一起来交流基于Docker的负载均衡和服务发现。内容的主题涉及到容器集群入口流量的负载均衡方案,4层和7层协议负载的区别处理,容器集群内服务之间的互相发现等。
应用的容器化和微服务化带来的问题
在缺省网络模型中,容器每次重启后,IP会发生变动,在一个大的分布式系统保证IP地址不变是比较复杂的事情。
IP频繁发生变动,动态应用部署无法预知容器的IP地址,client端如何发现server端的访问端点?
解决方案(根据客户端是否有感知进行分类)
客户端的发现。client 订阅注册中心,有一个固定的注册中心地址,client订阅某个服务的注册中心,注册中心根据服务的运行状态推送某个服务的访问端点列表给client端。 该方案的实现举例有Dubbo、DNS的解析等。
服务端的发现。服务端提供某个服务固定的访问端点,客户端直接访问该端点即可达到与服务端通信的目的,该访问端口对接后端具有动态IP的容器,作为请求的入口,负责请求转发到后端的容器。该方案的实现举例就是各种对后端负载均衡的实现,包括LVS/Nginx/HAProxy等。我们可以认为如下公式基本成立:
“服务端的发现” = “负载均衡器” + “路由配置自动更新”
对比客户端的发现,服务端发现对客户端无感知,由于很多已有的应用或者系统并不是按照类似Dubbo这种服务化的框架实现的,这些应用或者系统的客户端对服务发现都是无感知的,因此服务端的发现就表现出了独特的优势。客户端的服务发现方法中,DNS是一个例外,几乎所有的客户端都支持DNS。下面介绍客户端DNS和服务端的负载均衡器做服务发现的几个方案。
服务发现方案
DNS解析到多个IP
优点:Docker版本大于1.10即原生支持容器集群内部DNS的服务发现。
缺点:由于DNS TTL生效时间的存在,解析的结果不能做到实时,即使TTL设置为0,某些应用或者方法库会缓存DNS解析的结果,导致会解析到已经失效的地址上。
内核空间 LVS/IPVS
优点:IPVS的方案是Docker在未来会正式发布的1.12版本中采用的方案,主要是做到了4层的负载均衡,请求的转发实现在内核中,不需要二次拷贝请求和响应的内容,不需要解析和处理7层HTTP协议,效率高。
缺点:缺少7层负载均衡的支持,一个服务的负载均衡会占用主机的一个端口,服务与服务之间暴露的端口如果相同会产生冲突。
用户空间Nginx
优点:同时支持4层和7层负载均衡的代理,多进程模型方便利用多核性能,具有cache功能,亦可作为静态文件Web服务器,7层可以根据区分HOST字段来复用同一个80端口,解决端口冲突的问题。7层负载会解析HTTP协议,支持多层路由,包括根据域名不同进行路由,根据路径不同进行路由,内部重定向等多种HTTP协议层的功能。
缺点:负载均衡调度算法较少,对后端进行健康检查的策略较少。
用户空间 HAProxy
优点:同时支持用户空间4层和7层负载均衡的代理,是一个纯粹的软负载均衡器,支持Round-Robin,static-rr,least-conn,source-IP等多种调度算法,对后端进行健康检查的策略完备,包括TCP端口检查,HTTP请求检查,可执行程序检查等带外检查。7层可以根据区分HOST字段来复用同一个80端口,解决端口冲突的问题。7层负载会解析HTTP协议,支持多层路由,包括根据域名不同进行路由,根据路径不同进行路由,内部重定向等多种HTTP协议层的功能。
缺点:不能作为静态文件服务器,不支持Cache缓存功能。
aliyun容器服务的负载均衡解决方案
经过前面优缺点的分析和结合aliyun自身产品的优势,提供了以下解决方案:
Docker自带的DNS 服务发现,只要Docker版本大于1.10(目前阿里云支持的Docker最新版本为1.11),就支持DNS的服务发现。其特点是为:1)每个容器提供独立的DNS解析;2)可以通过容器名与别名(Aliases)方式在整个网络作用域内解析到某个容器的IP;3)通过链接别名的方式(Link Aliases)在容器的作用域内设置DNS解析,好处是不同容器的相同别名不会冲突。4)对外部的DNS解析请求进行代理。如下图所示:
4层,提供阿里云的云产品SLB的负载均衡方案,底层是基于LVS做了改造和增强,SLB经受了长期线上实战的考验,稳定性和正确性可以得到保证。同时,容器服务在此基础上能做到监控容器部署的情况,随着服务的启停,动态绑定SLB后端的端口(稍后解释原理)。
7层,提供基于HAProxy的负载均衡方案,支持灵活的配置。容器服务在此基础上能做到监控容器部署的情况,随着服务的健康状况,动态调整后端的负载(稍后解释原理)。
虽然我们4层主推SLB来做负载均衡,同时7层主推HAProxy来做负载均衡,但是实际上SLB支持7层的负载均衡,底层基于Tengine,而HAProxy也支持用户空间4层的负载均衡,用户可以根据自己的情况,灵活选择/组合方案。
SLB做到动态绑定的原理:Swarm监听容器的状态,如果容器正常运行,则把容器加入到SLB的后端,如果容器发现异常,则把容器从SLB的后端摘下来。
HAProxy实现动态服务发现的原理:HAProxy容器内除了有HAProxy软件,还有脚本程序监听容器的状态,根据容器的健康状况重新生成负载均衡信息,然后重新加载(reload)HAProxy,使得新的负载均衡信息生效。
实现不停服rolling_update原理:平滑升级的关键在于每一时刻均有至少一个容器还能正常提供服务。1)需要部署多个容器,将容器分为A、B两批更新。2)更新容器时,先将A批容器的路由从SLB或者HAProxy上面摘下来。3) 更新A批容器 4)A批容器健康检查正常后,重新加入路由 5)摘下B批容器的路由 6)更新B批容器。
实现灰度发布原理:不通版本的服务可以共享同一路由信息,通过调整SLB或者HAProxy权重的方式来做到灰度发布。
根据场景提供给用户的服务形态
简单路由服务:基于HAProxy,我们加了一层Wrapper,做到动态发现处于运行状态的容器,加入到负载均衡中,我们称之为简单路由服务(routing service),其公网IP通过一个SLB对外进行暴露。主要解决如下需求:
7层服务端点对公网暴露,即承接公网访问集群内使用7层协议的服务的流量。
7层服务端点对内网暴露,即容器集群内的负载均衡和服务发现:如下图所示,集群内的服务发现利用了Docker自带的DNS resolver配合了HAProxy的负载均衡和健康检查。图中的LB即为简单路由服务下的HAProxy容器,1)首先通过Docker自带的DNS resolver将域名
restserver.local
解析到HAProxy容器的IP,此处会优先选择当前节点的HAProxy容器进行负载均衡;2)RestClient请求域名restserver.local
时,请求先走到代理容器LB;3)LB根据从Discovery Service
获取到的负载均衡信息代理到提供相应服务的容器后端RestServer Contaienr
。不支持4层的路由
自定义路由服务:作为一个可选的容器,实现跟简单路由服务类似,解决如下需求:
通过环境变量和标签提供强大且灵活的配置支持,同时支持用户基于该容器进行扩展(通过
Dockerfile
FROM
的方式)支持用户空间的4层协议转发。
SLB路由服务:将SLB绑定到某个服务上面,后端随服务的启停动态配置。主要解决如下需求:
对外暴露的服务,使用4层协议,通过自定义SLB做4层的转发。
容器间的4层或者7层通信,流量特别大时,推荐使用自定义SLB(内网)服务。
场景和对应的路由服务总结
如上述图表所示,我们将容器集群外进入容器集群内的入口通信称为南北通信,将集群内容器和容器之间的流量成为东西通信。我们根据不同的通信形式和协议层提供不同的服务来满足用户的需求,例如对应南北通信,如果是使用7层协议的服务,我们推荐用户使用集群的SLB进行流量转发,最终的流量会转发到每个主机的HAProxy容器上面,然后在分发到相应的处理请求的服务上。
南北通信:指的是整个容器集群入口的通信。南北通信的特点往往是通信量比较大,因此我们首先用SLB将流量分散到各个主机节点。
东西通信:指的是集群内部,容器和容器间的通信方式。