写在开头
服务发现在SOA架构中是一个很重要的概念,是支撑大规模 SOA 的核心服务,在应用Docker容器集群的实践中也是非常重要的功能。对于Docker容器之间跨主机访问这个难题,服务发现是目前较为实用的解决方案。
服务发现
设计原则
服务发现的功能是管理一个集群中的进程或服务之间的通信,涉及到服务列表,在服务列表中注册服务,并且能够在服务列表中查找并连接服务。服务发现的基本思想是任何一个应用的实例能够以编程的方式获取当前环境的细节,这是为了让新的实例可以嵌入到现有的应用环境而不需要人工干预。当有任何进程监听TCP或UDP端口时,服务发现组件都应该能够察觉,并且能够根据名字查找到并连接上这个端口。复杂系统的服务发现组件要提供更多的功能,例如,服务元数据存储、健康监控、多种查询和实时更新等。总的来说,服务发现提供了一种协调机制,方便服务的发布和查找。
设计目标
目标是减少或消除组件之间的“手动”的连接。当你把你的应用程序推送进生产的时候,所有的这些事情都可以配置:数据库服务器的主机和端口,REST服务的 URL等等,在一个高可扩展的架构中,这些连接可以动态改变。一个新的后端可以被添加,一个数据库节点可以被停止。你的应用需要适应这种动态环境。普遍原则是:当启动的时候,一个服务的实例必须注册进配置服务器。当停止的时候(完美停止或是 Crash 了),节点必须从配置服务中移除掉。注册后,其他服务可以在配置服务器中搜索到提供制度服务的实例列表(主机和端口)。
服务发现工作过程
每一个服务发现工具都会提供一套API,使得组件可以用其来设置或搜索数据。正是如此,对于每一个组件,服务发现的地址要么强制编码到程序或容器内部,要么在运行时以参数形式提供。通常来说,发现服务用键值对形式实现,采用标准http协议交互。
服务发现门户的工作方式是:当每一个服务启动上线之后,他们通过发现工具来注册自身信息。它记录了一个相关组件若想使用某服务时的全部必要信息。例如,一个MySQL数据库服务会在这注册它运行的ip和端口,如有必要,登录时的用户名和密码也会留下。
当一个服务的消费者上线时,它能够在预设的终端查询该服务的相关信息。然后它就可以基于查到的信息与其需要的组件进行交互。负载均衡就是一个很好的例子,它可以通过查询服务发现得到各个后端节点承受的流量数,然后根据这个信息来调整配置。
这可将配置信息从容器内拿出。一个好处是可以让组件容器更加灵活,并不受限于特定的配置信息。另一个好处是使得组件与一个新的相关服务实例交互时变得简单,可以动态进行调整配置。
配置存储是如何关联起来的
全局分布式服务发现系统的一个主要优势是它可以存储任何类型的组件运行时所需的配置信息。这就意味着可以从容器内将更多的配置信息抽取出去,并放入更大的运行环境中。
通常来说,为了让这个过程更有效率,应用在设计时应该赋上合理的默认值,并且在运行时可以通过查询配置存储来覆盖这些值。这使得运用配置存储跟在执行命令
行标记时的工作方式类似。区别在于,通过一个全局配置存储,可以不做额外工作就能够对所有组件的实例进行同样的配置操作。
故障检测
故障检测的实现方式也有很多种。需要考虑的是如果一个组件出现故障,服务发现能否更新状态指出该组件不能再提供服务。这种信息是至关重要的,关系到将应用或服务故障可能性降到最低。
许多服务发现平台允许赋值时带一个可配置的超时时间。组件可以设置一个超时时间,并能定期去请求服务发现来重置超时时间。如果该组件出现故障,超时时间达到设定值,那么这个组件的连接信息就会从服务发现的存储中被去掉。超时时间长度在很大程度上是快速应用程序对组件故障的函数。
这也可以通过将一个基本的“助手”容器与每一个组件相连来实现,而它们唯一的责任是定期的健康检查组件以及组件出现故障时更新注册表。这种类型的架构值得担忧的是,如果辅助容器出现故障,将导致不正确的信息在存储中。一些系统解决这个问题的方法是在服务发现的工具中定义健康检查。这样,发现平台本身就可以定期检查已注册组件是否仍然可用。
细节变更时的服务发现
对于基本的服务发现模型来说,一个关键的改进就是动态重新配置。普通服务发现工具允许用户通过检查在启动时的信息来影响组件的初始配置,而动态重新配置涉及配置组件来反映配置存储中的新信息。例如,当你在运行一个负载均衡,后端服务器上的健康检查可能会提示集群中的某一个成员出现故障了。运行中的成员机器需要知道这个信息,并调整配置信息和重新加载它的负载。
这个有多种方式来实现。由于负载均衡的例子是这个功能的主要应用场景之一,许多现有的项目专注在当配置变动时重新配置负载均衡。常见的是HAProxy配置调整,这要归结于在负载均衡领域内它的普遍性。
某些项目更加灵活,它们可在任何类型的软件中被用来触发变更。这些工具周期性的去请求服务发现工具,并且当变更被发现时,利用模板系统和服务发现工具中的值来生成新配置文件。当配置文件生成结束,相应的服务将被重新加载。
这种类型的动态配置在构建过程中需要更多的规划和配置,因为这些所有的策略都需要存在于组件容器之中。这使得组件容器负责调整自身的配置。找出需要存在服务发现工具中的必要参数值并设计一个适当的数据结构以便使用,这是该系统的另一个技术挑战,但是它可以带来可观的效益和灵活性。
安全性
许多人初次接触全局配置存储时担心的一个问题是访问的安全性。将连接信息存储在全局可访问的存储中真的合适么?
这个问题的答案很大程度上依赖于你准备在存储中存放的内容以及保护你的数据需要多少层的安全等级。几乎所有的服务发现工具可以采用SSL/TLS加密链接。对于一些服务,隐私性可能不是最重要的,而发现服务放在内网中也可能让人满意。但是,大多数的应用会从它额外的安全性上获益。
有许多不同的方法来解决这个问题,同时各种项目也都提供他们自己的解决方案。一个项目的解决方案是继续允许开放发现服务平台本身,但是对于写入数据进行加密,使用者使用的信息必须用相应的密钥解码从服务发现中获取。其他组件不可以获取到未加密的数据。
还有不同的方法,一些服务发现工具实现了访问控制列表,将不同的键值切分到不同的分组中。他们可以根据访的问需要来制定不同的秘钥从而访问相应的分组。这种简单的方式既保证了能够给特定组件提供信息又保证了对其他组件的不可访问性。每个组件都可以被配置为只允许访问它所需要的连接信息。
常见的服务发现工具
etcd
这是CoreOS的创建者提供的工具,面向容器和宿主机提供服务发现和全局配置存储功能。它在每个宿主机上有基于http协议的API和命令行的客户端。
etcd是一个年轻的项目,正在高速迭代和开发中,这样也带来一个缺点,就是版本的迭代导致其使用的可靠性无法保证,无法得到大项目长时间使用的检验。然而,目前CoreOS、Kubernetes和Cloud Foundry等知名项目均在生产环境中使用了etcd。
实际上,etcd作为一个受到Zookeeper与doozer启发而催生的项目,除了拥有与之类似的功能外,还具有自身的优势:使用简单,基于HTTP+JSON的API,用curl命令就可以使用;安全,可选SSL客户认证机制;快速,每个实例每秒支持一千次写操作;可信,使用Raft算法充分实现了分布式,保证分布式系统数据的一致性。
etcd的架构并不复杂,etcd主要分为四个部分,如下图所示。
etcd:微服务协同工作
随着Docker容器的流行,多种微服务共同协作构成一个功能相对强大的架构的案例越来越多,透明化地动态添加这些服务的需求也日益强烈。通过服务发现机
制,在etcd中注册某个服务名字的目录,在该目录下存储可用的服务节点的IP。在使用服务的过程中,只要从服务目录下查找可用的服务节点去使用即可。如
下图所示,以Docker为承载的前端在服务发现的目录下查找可用的中间件,中间件再找到服务后端,以此快速构建起一个动态的高可用的架构。
etcd:PaaS平台应用多实例
这种情况下,通常采用某种代理服务,这样消费者总是向固定IP地址或动态配置的IP地址的代理发送请求,代理再依次使用服务发现来查找服务提供方信息并重
定向请求。这样就可以应对故障恢复和多实例访问的场景:首先,当一个服务实例发生故障时,代理可以从服务实现的列表中选择其他的实例,然后重定向请求到这
个健康的实例。其次,应对多实例的情况,代理服务可以从服务实例的列表中选择一个适当的实例发送请求,具体的选择算法由代理服务实现,使用优秀的选择算法
就可以实现负载均衡。同时,理论上负载均衡也可以由调用者客户端实现,由客户端决定流量分配到哪个实例。详细架构如下图所示:
Zookeeper
Zookeeper是最早做服务发现的开源项目之一,起源于Hadoop,帮助在Hadoop集群中维护各种组件。它非常成熟、可靠,被许多大公司(YouTube、eBay、雅虎等)使用。其数据存储的格式类似于文件系统,如果运行在一个服务器集群中,Zookeeper实现了跨所有节点共享配置状态。
然而,Zookeeper的缺点也很明显。首先,它采用Java开发,尽管Java在许多方面非常优秀,但对于服务发现这种工作还是太沉重了。java本身就偏向于重型应用,会引入大量的依赖,而运维人员也普遍希望机器集群尽可能地简单,维护起来也不易出错;其次, Zookeeper的部署、维护和使用都很复杂,管理员需要掌握一系列知识和技能,一方面Zookeeper是功能丰富,但从另一个角度分析,丰富的特性反而将其从优势转变为累赘;最后,Apache基金会结构庞大,导致Zookeeper项目发展缓慢。
Zookeeper为其他项目的改进铺平了道路,现在,服务发现工具有了更好的选择。
consul
这个服务发现平台有很多高级的特性,使得它脱颖而出,例如:配置健康检查、ACL功能HAProxy配置等等。
Consul是强一致性的数据存储,与Zookeeper和etcd不一样,Consul内嵌实现了服务发现系统,所以这样就不需要构建自己的系统或使用第三方系统,另外,Consul还包括节点健康检查和运行在其上的服务。
Zookeeper和etcd只提供原始的键/值队存储,要求应用程序开发人员构建他们自己的系统提供服务发现功能。而Consul提供了一个内置的服务发现的框架。客户只需要注册服务并通过DNS或HTTP接口执行服务发现。其他两个工具需要一个亲手制作的解决方案或借助于第三方工具。
Consul使用Raft算法来保证一致性,与etcd相同,而zookeeper 采用的是 Paxos。相比较而言,Raft算法比复杂的 Paxos 算法更直接。Consul支持多数据中心,内外网的服务采用不同的端口进行监听。多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。zookeeper 和 etcd 均不提供多数据中心功能的支持。Consul支持健康检查,而etcd不提供此功能。而且Consul支持 http 和 dns 协议接口,比zookeeper 的集成更为简单, etcd只支持 http 协议。另外,Consul官方提供web管理界面, etcd 则没有此功能。
值得注意的是,Consul为服务提供了DNS查询的接口,DNS那一节内容中提到过DNS做服务发现的不足就是它只能提供主机的IP,并不知道端口,除非是使用SRV记录,但是查询SRV记录又需要调用者来支持。Consul为服务解决了查询SRV记录这个难题。一般的环境中DNS是不会查询SRV记录的,但Consul内置了DNS服务器,并提供标准的API支持SRV记录,这就意味着任何轻量级的客户端都可以很方便地查询SRV记录,从而利用DNS实现服务查询的功能。
总结
服务发现工具使得Docker容器可以适应它们当前所处环境并嵌入现有的组件。这是实现提供方便、容易扩展和部署的功能的一个重要的先决条件,该功能通过允许组件跟踪和应对他们所在环境变化来实现。