服务0侵入方式的服务注册与发现选型

项目背景
      2016年第四季度,随着业务规模扩大,单个系统包含的功能不断增长,系统复杂度显著增加,给开发和运维工作带来了很大的挑战,于是我们展开了服务化的研究与探索。
在之前的开发中,我们的业务系统主要用Java编写,并大规模使用了Spring框架,几乎所有Web应用都基于Spring MVC开发。在决定进行服务化之前,我们已经开始推广使用Spring Boot来提高应用开发效率。基于上述原因,经过一段时间的讨论和研究,我们初步决定采用Spring Cloud作为我们微服务架构的基础,使用Spring Boot来做服务的开发。
     在开始服务化以前,我们已经有了一些容器化的实践,并收到了较好的效果;以云主机为主的基础设施也能够较好地发挥容器技术的优势,因此我们决定将微服务架构搭建在Docker容器集群中。
为了减少开发人员的学习成本,尽量缩减代码修改需求,让服务化尽可能快速平滑,我们需要在服务化的过程中设法减少微服务架构对服务内部的影响。
本文主要讨论我们关于服务注册与发现部分做的一些工作。
技术选型(Consul vs Eureka)
   经过初步讨论,我们确定了服务注册中心的两个备选方案:Consul和Eureka。
根据我们的需求,主要归纳了两个方案在我们使用场景下的优势与不足。
Eureka:Netflix的服务注册于发现工具,Spring Cloud Netflix提供了Eureka的服务器和客户端。
•    优势:
o    完全基于Java实现,部署要求和现有其他服务相近。
o    定制开发更便于Java程序员掌握。
o    和我们选用的其他组件(Zuul,Hystrix,Ribbon)都源自Netflix,具备先天的良好适配性。
•    不足:
o    需要在服务中添加Eureka的客户端
o    心跳包机制,服务状态改变不及时
Consul:HashiCorp出品的服务注册与发现工具,Spring Cloud提供了支持Consul的注册和发现客户端。
•    优势:
o    服务注册独立于服务本身
o    主动检测服务状态
o    通过Regstrator可以提供基于容器的服务注册
o    多数据中心支持
•    不足:
o    Consul用GO语言开发,定制开发需要重新学习
o    需要单独运行,无法直接加入其它组件

Eureka和Consul节点结构的差异
      考虑到我们未来可能有跨语言的需求,Consul能够更好地适应我们技术团队以Java和Python为主的技术背景;并且,我们正在布局混合云方案,Consul对多数据中心的支持在混合云环境下也具有重要意义;Consul对服务自身的影响也相对更小,符合我们对低侵入性的需求。
     在我们对组件的选择过程中,另一个重要考量是和容器技术的相容性,Consul和Eureka自身和Docker容器都没有冲突,但在容器技术的帮助下,Consul在部署方面的特殊要求不再是一个问题,我们可以像其他服务一样把Consul部署在容器中,而无需考虑独立的监控和维护。
      影响我们选择的还有一个第三方工具:Regstrator,通过把服务注册交给容器层面,它让我们的服务注册对服务自身的侵入几乎为零,并且该工具对Consul支持较好。
最终在一段时间的测试和研究之后,我们选择了Consul作为我们服务注册与发现的解决方案。
基于容器的服务注册(Registrator)
       在进行服务注册的时候,我们关心的问题是服务如何能把自己的信息和变化及时地报告给服务发现中心,以便维护实时有效的服务目录。服务启动时,需要向注册中心提供自己的信息,包括地址,端口,服务标识符等。然而在Docker容器中,服务一般无法了解自己在宿主机上开放的端口,也无法准确判断该服务的真实网络地址。这就给服务自身完成注册操作造成了一些困难。
      从另一个方面来看,服务所在的地址和端口等信息其实是和业务无关的,不应该也不适合由服务去处理。否则不仅背离了服务化的初衷,也加深了对服务的侵入。
      在面对上述困难时,我们发现了一个“神器”——Registrator(http://gliderlabs.com/registrator)。
Registrator通过监控Docker事件,根据docker容器的启动和停止向注册中心发送注册和注销的消息,无需服务自身参与即可完成。由于是容器层面的注册,Registrator能够掌握服务真正对外开放的端口;通过订阅容器的状态信息,Registrator即使在服务无响应,甚至直接删除容器的情况下也可以完成对服务的注销,避免已经删除的服务留存在服务目录中,占用Consul的资源。
     另外一些对需要注册的附加信息,如健康检查的设定等,则可以通过容器的环境变量或标签来设定,最大程度减少了对业务逻辑的侵入。
图形化容器集群管理(Rancher)
      我们采用了Rancher(http://rancher.com/rancher/)进行容器集群管理,这是一个图形化的集群管理工具,支持多种集群作为后端,包括Kubernetes、Docker Swarm、Mesos和Rancher内建的Cattle等。值得一提的是,Rancher的服务器端和代理端都完全运行在Docker容器中,这给我们的部署提供了极大的便利。
Rancher提供了一个应用商店,从商店中可以添加许多为集群设计的服务,有数据库、集群网络管理工具、DNS等各类应用,其中也包括了我们需要的Consul集群。这个商店本质上是一些预制的服务编排,帮助我们自动在集群中部署相应的容器。
       借助Rancher的环境管理和访问控制功能,我们可以对集群管理进行更细致的权限控制,比如把开发环境交给开发团队去管理。这为DevOps的引入创造了条件。
Rancher自身也提供了一些服务化相关的功能。目前版本中,Rancher提供了基于DNS的服务发现、基于HTTP或TCP的健康检查、集群网络中的负载均衡等。相信在未来逐步完善以后,Rancher提供的方案也会成为服务化解决方案中的一支重要力量。
Consul+Docker服务注册与发现实践
      前面介绍了我们所使用的各类组件,下面从实际应用的角度记述我们在构架这套服务注册与发现体系的实践过程。容器集群搭建首先,准备至少3台服务器,一台用于集群管理(下文称ServerA),两台用来部署服务(下文称ServerB,ServerC)。我们选用的是装有Centos7 64位操作系统的阿里云服务器作为实验平台,如果仅做简单研究,在本地部署三台虚拟机也可以满足要求。
      在三台主机上安装docker,由于V1.4.1版本的Rancher稳定支持的Docker版本为1.12.3或更高,尚不支持1.13和17.03,因此我们使用了Docker 1.12.5。(Rancher1.5已于17年3月6日发布,该版本还未进行尝试)
在ServerA上运行命令启动rancher-server容器
docker run -d --restart=unless-stopped -p 8080:8080 rancher/server:stable
rancher-server启动成功后,即可通过ServerA的8080端口访问Rancher的管理页面。
•    首先进入系统管理->访问控制,添加一个本地账号,开启访问控制功能,然后用设置好的管理员账号登录。
•    进入基础架构->主机,根据提示添加ServerA、ServerB、ServerC。
•    Rancher会自动添加一些基础服务,等待这些基础服务启动完成。
至此,一个简易的以Rancher管理的Docker集群搭建完成,如果需要搭建高可用的Rancher,请参阅Rancher文档中高可用部署的部分。
Consul集群搭建
Consul集群的基本单位是agent,agent运行于主机集群中每个节点上,多个Consul agent组成集群。Consul agent有两个运行模式,server和client,一个agent可以同时运行在两种模式上。client模式agent主要职责是做健康检查,对外提供HTTP和DNS接口以及同步数据,以及向server模式的agent转发请求;server模式的agent额外的需要维护集群的状态跨数据中心通信等。一般每个数据中心建议由3-5个Server模式的agent组成高可用的集群,其余节点则可以使用client模式的agent以减少资源开销。
考虑到我们只部署3个节点,那么这三个节点的agent都将设定为运行在server模式,后续如果继续添加,可以使用client;而如果集群规模较大,则用专门的主机来运行server模式的agent。
在Rancher中新建一个应用栈Consul,并在此应用栈中添加服务。服务的各项设置如下:
数量:总是在每台主机上运行一个此容器的示例
名称:consul
选择镜像:consul
命令:agent -ui -data-dir /tmp/data -server -bootstrap-expect 3 -client 0.0.0.0 -retry-join [此处为ServerA的地址]
网络:主机
其中,名为镜像consul为官方提供的Consul agent镜像;启动命令中 -ui 表示开启网页GUI功能,可以通过访问节点的8500端口查看consul的状态信息;-server表示开启该agent的server运行模式;-bootstrap-expect 3设定该集群的最小规模为三个节点,有三个节点加入集群时开始leader选举,-client 0.0.0.0开启agent的client模式,绑定到所有端口,-retry-join [ServerA的地址]该处需要填写一个集群中节点都能访问到的agent的地址,此处选择ServerA作为各节点加入集群的入口,注意,由于使用bootstrap-expect模式启动,ServerA不一定会被选为leader节点。网络设定为主机网络以便集群中的节点互相通信,并让consul能够取得本节点主机的ip。
修改好设定,点击创建服务开始服务的创建,Rancher会自动下载镜像并启动容器。然而,consul服务创建过程中,容器报错无法成功启动。查看日志后发现,consul容器启动脚本运行出现错误,提示有多个ip地址,请指定一个绑定地址。经过对官方consul镜像中启动脚本的研究,我们找到了设置绑定端口的方法:设定容器的环境变量:CONSUL_BIND_INTERFACE=eth1(需要绑定的端口,此处绑定eth1)。修改完成后重新启动服务,这次服务成功启动了,经过一小段时间的通信和协商,三个节点均加入集群,完成了leader选举,开始提供服务。此时通过任意节点的8500端口,可以查看consul集群中各节点和各服务的状态。
通过Rancher的应用市场,也可以完成Consul集群的搭建,此处不展开讨论。
Registrator部署
Consul集群搭建完成后,即可提供正常的服务注册和发现功能,但为了实现基于容器的服务注册,我们还需要安装Registrator。
和Consul agent一样,Registrator也需要在集群中每个节点部署一个容器,通过挂载docker.sock,容器中运行的Registrator可以控制主机的Docker守护进程,通过Docker events达到监控容器变化的目的,并向本节点的Consul agent发送注册消息。 并在Consul应用栈中添加服务。服务的各项设置如下:
数量:总是在每台主机上运行一个此容器的示例
名称:registrator
选择镜像:gliderlabs/registrator
命令:consul://localhost:8500
网络:主机
卷:/var/run/docker.sock:/tmp/docker.sock
此处值得注意的是要把主机的/var/run/docker.sock挂载进容器中。registrator的其他详细设定可以通过修改启动命令来修改,如果需要对registrator做特殊的设定,请见Registrator官方文档(http://gliderlabs.com/registrator/)。
完成registrator的部署后,框架的搭建就基本完成了,可以在该环境中新建容器,如果容器通过nat开放了端口,会自动被注册到consul中;在容器停止时,则会从consul中注销。自动虽然方便,但总会有些时候,我们需要对注册的过程进行一些定制。为了在自动注册时加入我们自己设定的一些信息,我们需要对服务所在的容器做一些改动。

框架搭建完成后各主机容器分布和关系
服务准备工作
服务名称是Consul服务目录划分同一种服务的依据,在运用zuul作为服务网关进行反向代理时,如果服务列表取自consul的服务发现,服务名也是访问服务的默认路径,因此我们很有可能需要对服务名称进行定制。要设置自动注册时采用的服务名,只需要给服务容器添加标签SERVICE_NAME=[服务名] 即可,也可以通过设置环境变量 SERVICE_NAME=[服务名]来达到同样的效果。
健康检查是Consul提供的一个十分有用的功能,向Consul注册一个健康检查可以让Consul定期检测服务的健康状态。我们可以通过如下方式让Registrator在注册服务的同时,注册该服务的健康检查。以Http检查为例,设置标签或环境变量SERVICE_CHECK_HTTP=/health设置健康检查路径为/health(可以通过spring-boot-starter-actuator来提供这个Endpoint,也可以自行处理检查请求);设置标签或环境变量SERVICE_CHECK_INTERVAL=15s设置健康检查间隔为15秒;设置标签或环境变量SERVICE_CHECK_TIMEOUT=5s设置健康检查请求超时时间为5秒。通过上述设定,在服务被Registrator注册到Consul时,会附带注册一个间隔为15秒的http方式的健康检查,如果请求超时或失败,Consul会将服务置为故障状态,在取可用服务时不会提供该实例。
服务自身的修改:这个真没有。
将服务运行的容器进行上述设定后(建议通过Dockerfile去构建,直接在镜像中包含这些设置),通过Rancher部署到之前建好的集群中运行,网络模式选择桥接,设定好开放端口,待服务启动后即可在Consul中看到服务的信息,在服务开始启动到服务能够正常处理请求之间的这段时间,由于健康检查失效,在consul中服务将显示为故障状态,直到第一次成功执行健康检查。
后记
后续有待研究的问题有:
•    如何在服务发布时平滑切换,通过Consul的API设定服务为维护状态可以达到目的,但这个操作是针对agent的,有一定局限性。
•    混合云环境下的跨数据中心集群如何部署。
•    其他使用中遇到的问题。
本文完成时Docker已经迎来重大变革,来到了17.03版本;Rancher也发布了1.5版本。这次更新带来了很多新特性有待我们进一步去探索。

0 个评论

要回复文章请先登录注册