该小节介绍了各种技术和它们的优缺点,并提供了网上的更多资源(如果你想获得这些技术的实践经验,你可以看看Adrian Mouat的书Using Docker)。

ZooKeeper

Apache ZooKeeper是ASF的顶级项目,基于JVM的集中式配置管理工具,提供了与Google的Chubby相兼容的功能。ZooKeeper(ZK)将数据载荷组织成文件系统,成为znodes的层级结构。在集群中,选举出一个leader节点,客户端能够连接到服务器中的任意一个来获取数据。一个ZK集群中需要2n+1个节点。最常见的配置是3、5、7个节点。


ZooKeeper是经战场证明的、成熟的、可扩展的解决方案,但是也有一下缺点。有些人认为ZK集群的安装和管理不是一个令人愉快的体验。我碰到的大多数ZK问题都是因为某个服务(我想到了Apache Storm)错误地使用了它。它们可能在znodes里放入了太多数据,更糟糕的是,他们的读写率很不健康(unhealthy read-write ratio),特别是写得太快。如果你打算使用ZK的话,至少考虑使用高层接口。例如,Apache Curator封装了ZK,提供了大量的方法;Netflix的Exhibitor可以管理和监控一个ZK集群。

从图4-1可以看出,你可以看到两个组件:R/W(作为注册监控器(registration watcher),你需要自己提供)和NGINX(被R/W控制)。当一个容器被调度到一个节点上时,它会在ZK中注册,使用一个路径为/$nodeID/$containerID的znode,IP地址作为数据载荷。R/W监控znodes节点的变化,然后相应地配置NGINX。这种方法对于HAProxy和其他负载均衡器也同样有效。

图 4-1

etcd

etcd是由CoreOS团队使用Go语言编写的。它是一个轻量级的、分布式键值对数据库,使用Raft算法实现一致性(带有leader选举的leader-follower模型),在集群中使用复制日志(replicated log)向followers分发lead收到的写请求。从某种意义上说,在概念上,etcd和ZK是相当类似的。虽然负载数据是任意的,但是etcd的HTTP API是基于JSON的。就像ZK一样,你可以观察到etcd保存的值的变化。etcd的一个非常有用的特性是键的TTL,是服务发现的重要的一个结构单元。和ZK一样,一个etcd集群至少需要2n+1个节点。


etcd的安全模型支持基于TLS/SSL和客户端证书加密的线上加密(on-the-wire encryption),加密可以发生在客户端和集群之间,也可以在etcd节点之间。

在图4-2中,你可以看到etcd服务发现的搭建和ZK是相当类似的。主要区别在于etcd使用confd来配置NGINX,而不是使用自己编写的脚本。和ZK一样,这种搭建方法也适用于HAProxy或者其他负载均衡器。

图 4-2

Consul

Consul是HashiCorp的产品,也是用Go语言写的,功能有服务注册、服务发现和健康检查,可以使用HTTP API或者DNS来查询服务。Consul支持多数据中心的部署。


Consul的其中一个特性是一个分布式键值对数据库,与etcd相似。它也是用Raft一致性算法(同样需要2n+1个节点),但是部署方式是不同的。Consul有agent的概念,有两种运行方式:服务器模式(提供键值对数据库和DNS)和客户端模式(注册服务和健康检查),使用serf实现了成员和节点发现。

使用Consul,你有四种方式来实现服务发现(从最可取到最不可取):

  • 使用服务定义配置文件(service definition config file),由Consul agent解释。
  • 使用traefik等工具,其中有Consul后端(backend)。
  • 编写你自己的进程通过HTTP API注册服务。
  • 让服务自己使用HTTP API来注册服务。

想要学**Consul来做服务发现吗?请阅读这两篇文章:Consul Service Discovery with Docker和Docker DNS & Service Discovery with Consul and Registrator。 

纯基于DNS的解决方案在互联网中,DNS经受了数十年的战场验证,是很健壮的。DNS系统的最终一致性、特定的客户端强制性地(aggressively)缓存DNS查询结果、对于SRV记录的依赖这些因素使你明白这是正确的选择。

本小节之所以叫做“纯基于DNS的解决方案”的原因是,技术上讲Consul也是用了DNS服务器,但这只是Consul做服务发现的其中一个选项。以下是著名的、常用的、纯粹的基于DNS的服务发现解决方案:

Mesos-DNS该解决方案是专门用于Apache Mesos的服务发现的。使用Go编写,Mesos-DNS下拉任意任务的有效的Mesos Master进程,并通过DNS或HTTP API暴露IP:PORT信息。对于其他主机名或服务的DNS请求,Mesos-DNS可以使用一个外部的域名服务器或者你的现有DNS服务器来转发Mesos任务的请求到Mesos-DNS。

SkyDNS使用etcd,你可以将你的服务通告给SkyDNS,SkyDNS在etcd中保存了服务定义,并更新DNS记录。你的客户端应用发送DNS请求来发现服务。因此,在功能层面,SkyDNS与Consul是相当类似的,只是没有健康检查。

WeaveDNSWeaveDNS由Weave 0.9引入,作为Weave网络的服务发现解决方案,允许容器按照主机名找到其他容器的IP地址。在Weave 1.1中,引入了所谓的Gossip DNS,通过缓存和超时功能使得查询更加快速。在最新的实现中,注册是广播到所有参与的实例上,在内存中保存所有条目,并在本地处理查询。

Airbnb的SmartStack和Netflix的Eureka在本小节中,我们将会看一下两个定做的系统,它们是用来解决特定的需求。这并不意味着你不能或者不应该使用它们,你只是需要意识到它们。

Airbnb的SmartStack是一个自动的服务发现和注册框架,透明地处理创建、删除、故障和维护工作。在容器运行的同一个宿主机上,SmartStack使用了两个分离的服务:Nerve(写到ZK)用于服务注册,Synapse(动态配置HAProxy)用于查询。这是一个针对非容器环境的完善的解决方案,随着实践推移,你会看到对于Docker,SmartStack也是有用的。

Netflix的Eureka则不同,它运行在AWS环境中(Netflix全部运行在AWS上)。Eureka是一个基于REST的服务,用于定位服务以便负载均衡和中间件层服务器的故障迁移;Eureka还有一个基于Java的客户端组件,可以直接与服务交互。这个客户端有一个内置的负载均衡器,可以做基本的round-robin的负载均衡。在Netflix,Eureka用于做red/black部署、Cassandra和memcached部署、承载应用中关于服务的元数据。

Eureka集群在参与的节点之间异步地复制服务注册信息;与ZK、etcd、Consul不同,Eureka相对于强一致性更倾向于服务可用性,让客户端自行处理过时的读请求,优点是在网络分区上更有弹性。你也知道的:网络是不可靠的。

负载均衡服务发现的一个方面是负载均衡,有的时候负载均衡被认为是正交的,有的时候负载均衡被认为是服务发现的一部分。它可以在很多容器之间分散负载(入站服务请求)。在容器和微服务的语境下,负载均衡同时具有以下功能:
最大化吞吐量,最小化响应时间防止热点(hotspotting),例如单一容器过载可以处理过度激进的DNS缓存(overly aggressive DNS caching)
以下列举了一些有名的Docker中的负载均衡解决方案:
* NGINX
* HAProxy
* Bamboo
* Kube-Proxy
* vulcand
* Magnetic.io的vamp-router
* moxy
* HAProxy-SRV
* Marathon的servicerouter.py
* traefik

如果你想了解更多关于负载均衡的信息,请查看Mesos meetup视频和nginx.conf 2014上关于NGINX和Consul的负载均衡的演讲。

更多话题在本章的最后,我列举了服务发现解决方案的一览表。我并不想评出一个优胜者,因为我相信这取决于你的用例和需求。因此,把下表当做一个快速定位和小结:


最后请注意:服务发现领域在不断变化中,每周都会出现新工具。例如,Uber最近开源了它的内部解决方案Hyperbahn,一个路由器的overlay网络,用来支持TChannel RPC协议。因为容器服务发现在不断发展,因此你可能要重新评估最初的选择,直到稳定下来为止。

容器和编排就像上一章中介绍的那样,使用cattle方法来管理基础架构,你不必手动为特定应用分配特定机器。相反,你应该使用调度器来管理容器的生命周期。尽管调度是一个重要的活动,但是它其实只是另一个更广阔的概念——编排的一部分。

从图5-1,可以看到编排包括健康检查(health checks)、组织原语(organizational primitives,如Kubernetes中的labels、Marathon中的groups)、自动扩展(autoscaling)、升级/回滚策略、服务发现。有时候base provisioning也被认为是编排的一部分,但是这已经超出了本书的讨论范围,例如安装Mesos Agent或Kubernetes Kubelet。

服务发现和调度是同一枚硬币的两面。决定一个特定的容器运行在集群的哪个节点的实体,称之为调度器。它向其他系统提供了 containers->locations的映射关系,可以以各种方式暴露这些信息,例如像etcd那样的分布式键值对数据库、像Mesos-DNS那样的DNS。

图 5-1
本章将从容器编排解决方案的角度讨论服务发现和网络。背后的动机很简单:假设你使用了某个平台,如Kubernetes;然后,你主要的关注点是平台如何处理服务发现和网络。

 注意:
接下来,我会讨论满足以下两个条件的容器编排系统:开源的和相当大的社区。
你可以看一下以下几个不同的解决方案:Fackebook的Bistro或者托管的解决方案,如Amazon EC2的容器服务ECS。
另外,你如果想多了解一下分布式系统调度这个主题,我推荐阅读Google关于Borg和Omega的研究论文
在我们深入探讨容器编排系统之前,让我们先看一下编排的核心组件——调度器到底是做什么的。
调度器到底是做什么的?分布式系统调度器根据用户请求将应用分发到一个或多个可用的机器上。例如,用户可能请求运行应用的100个实例(或者Kubernetes中的replica)。

在Docker中,这意味着:(a)相应的Docker镜像存在宿主机上;(b)调度器告诉本地的Docker Daemon基于该镜像开启一个容器。

在图5-2中,你可以看到,对于集群中运行的应用,用户请求了三个实例。调度器根据它对于集群状态的了解,决定将应用部署在哪里。比如,机器的使用情况、成功启动应用所必须的资源、约束条件(该应用只能运行在使用SSD的机器)等。进一步地,服务质量也是考量因素之一。

图 5-2
通过John Wilkes的Cluster Management at Google了解更多内容。

 警告:
对于调度容器的限制条件的语义,你要有概念。例如,有一次我做了一个Marathon的demo,没有按照预期工作,原因是我没有考虑布局的限制条件:我使用了唯一的主机名和一个特定的角色这一对组合。它不能扩展,原因是集群中只有一个节点符合条件。同样的问题也可能发生在Kubernetes的label上。
Vanilla Docker and Docker Swarm创造性地,Docker提供了一种基本的服务发现机制:Docker连接(links)。默认情况下,所有容器都可以相互通信,如果它们知道对方的IP地址。连接允许用户任何容器发现彼此的IP地址,并暴露端口给同一宿主机上的其他容器。Docker提供了一个方便的命令行选项 --link,可以自动实现这一点。

但是,容器之间的硬连接(hard-wiring of links)并不有趣,也不具扩展性。事实上,这种方法并不算好。长久来说,这个特性会被弃用。

让我们看一下更好的解决方案(如果你仍然想要或者必须使用连接的话):ambassador模式。

Ambassadors如图5-3所示,这个模式背后的想法是使用一个代理容器来代替真正的容器,并转发流量。它带来的好处是:ambassador模式允许你在开发阶段和生产阶段使用不同的网络架构。网络运维人员可以在运行时重写应用,而不需要更改应用代码。

简单来说,ambassador模式的缺点是它不能有效地扩展。ambassador模式可以用在小规模的、手动的部署中,但是当你使用真正的容器编排工具(如Kubernetes或Apache Mesos)时,应该避免使用ambassador模式。

图 5-3
 注意:
如果你想要了解如何在Docker中部署ambassador模式,我再次建议你参考Adrian Mouat的书Using Docker。事实上,在图5-3中,我使用的就是他的amouat/ambassador镜像。
Docker Swarm除了容器的静态链接(static linking),Docker有一个原生的集群工具Docker Swarm。Docker Swarm基于Docker API构建,并通过以下方式工作:有一个Swarm manager负载调度,在每一个宿主机上运行了一个agent,负责本地资源管理(如图5-4所示)。

Swarm中有趣的地方在于,调度器是插件式的(plug-able),所以你可以使用除了内置以外的其他调度器,如Apache Mesos。在写本书的过程中,Swarm发布了1.0版本,并完成了GA(General Availability);新的特性(如高可用性)正在进行开发中。

图 5-4

 网络在本书的第2章和第3章中,我们介绍了Docker中的单宿主机和多宿主机中的网络。

 服务发现Docker Swarm支持不同的后端:etcd、Consul和Zookeeper。你也可以使用静态文件来捕捉集群状态。最近,一个基于DNS的服务发现工具wagl被引入到了Swarm中。

如果你想更多了解Docker Swarm,可以读一下Rajdeep Dua的幻灯片。

KubernetesKubernetes(请看图5-5)是一个opinionated的开源框架,弹性地管理容器化的应用。简单来说,它吸取了Google超过十年的运行容器负载的经验,我们会简要地介绍一下。进一步地,你总是可以选择其他开源或闭源的方法来替换Kubernetes的默认实现,比如DNS或监控。

图 5-5
以下讨论假设你已经熟悉Kubernetes和它的技术。如果你还不熟悉Kubernetes的话,我建议看一下Kelsey HighTower的Kubernetes Up and Running。

Kubernetes中,调度单元是一个pod,这是a tightly coupled set of containers that is always collocated。pod运行实例的数目是由Replication Controller定义和指定的。pods和services的逻辑组织是由labels定义的。

在每个Kubernetes节点上,运行着一个称为Kubelet的agent,负责控制Docker daemon,向Master汇报节点状态,设置节点资源。Master节点提供API(例如,图5-6中的web UI),收集集群现在的状态,并存储在etcd中,将pods调度到节点上。

图 5-6

 网络在Kubernetes中,每个pod都有一个可路由的IP,不需要NAT,集群节点上的pods之间也可以相互通信。pod中的所有容器共享一个端口命名空间(port namespace)和同一个notion localhost,因此没有必要做端口代理(port brokering)。这是Kubernetes的基本要求,可以通过network overlay来实现。

在pod中,存在一个所谓的infrastructure容器,kubelet实例化的第一个容器,它会获得pod的IP,并设置网络命名空间。pod中的所有其他容器会加入到infra容器的网络和IPC命名空间。infra容器的网络启用了bridge模式(请参考第九页的bridge模式网络),pod中的所有其他容器使用container模式(请参考第11页的container模式网络)来共享它的命名空间。infra容器中的初始进程实际上什么也没做,因为它的目的只是提供命名空间的载体。关于端口转发的最近的work around可能在infra容器中启动额外的进程。如果infra容器死亡的话,那么Kubelet会杀死pod中所有的进程,然后重新启动进程。

进一步地,Kubernetes的命名空间会启动所有control points。其中一个网络命名空间的例子是,Calico项目使用命名空间来强化coarse-grained网络策略(policy)。

 服务发现在Kubernetes的世界里,有一个服务发现的canonical抽象,这是service primitive。尽管pods随时可能启动和销毁因为他们可能失败(或者pods运行的宿主机失败),服务是长时间运行的:它们提供集群层面的服务发现和某种级别的负载均衡。它们提供了一个稳定的IP地址和持久化名字,compensating for the shortlivedness of all equally labelled pods。Kubernetes提供了两种发现机制:通过环境变量(限制于一个特定节点上)和DNS(集群层面上的)。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐