我记得 Consul 在 2014 年首次问世时的情景。当时,云计算和面向服务的架构(微服务的前身)正在成为主流,每家公司都开始应对在分布式系统中如何路由到服务和处理故障的问题。

Consul 是一项革命性技术,因为它将基于 DNS 的服务发现与强大的故障检测系统结合在一起。一个服务会注册到 Consul 中,其他服务可以使用它的 Consul DNS 条目来路由。例如,前端服务可在 frontend.service.consul 处访问。Consul 还通过使用名为 Serf 的八卦算法(本章后面会介绍)和健康检查来检测故障。如果一个节点或服务宕机,Consul 会快速发现并将其从 DNS 中移除。

Consul 是开源的,免费使用,它优雅地解决了成千上万家公司遇到的问题。业界很快就接受了它。

随着时间的推移,面向服务的架构变得越来越大,并演变成包含更小服务的微服务架构。这引发了 Docker 和 Kubernetes 等容器编排工具的兴起。微服务运动和 Kubernetes 以两种重要方式改变了行业。

首先,DNS 服务发现已不再足够。开发人员需要围绕安全性、可观测性、可靠性和流量控制的更多网络功能来帮助运行所有这些服务。随着服务数量的不断增长,在服务代码中实现这些功能也变得更加具有挑战性。

其次,Kubernetes 通过 pod 模型使运行 sidecar 代理变得更加容易,其中多个容器可以在一个私有网络中一起运行。

这些变化促成了我们今天所知的服务网格技术的发展。

HashiCorp 的 Consul 团队一直在关注这些趋势,并在 2018 年发布了一项名为 Consul Connect 的服务网格功能。Consul Connect 专注于服务之间的安全通信,并增加了对 sidecar 代理的支持,以加密服务之间的流量。从那时起,Consul 已发展成为一个功能齐全的服务网格,不仅在安全通信方面,还包括可观测性、可靠性和流量控制等功能。

了解了这些历史背景之后,您就准备好学习 Consul 的工作原理以及其作为服务网格的独特之处了。以下部分将介绍 Consul 的架构及其保持可靠性和可扩展性的协议。

您会在各种配置设置中看到提到 Consul Connect。这指的是 Consul 的服务网格功能。

架构

一个 Consul 安装由三组组件组成:Consul 服务器、Consul 客户端和 sidecar 代理。sidecar 代理与其本地 Consul 客户端通信,而 Consul 客户端与 Consul 服务器通信。通常会有三到五个 Consul 服务器节点,而工作负载节点可以多达数千个(见图 2-1)。

为了将 Consul 的架构与通用服务网格的架构联系起来,如图 2-2 所示,Consul 服务器和客户端是控制平面的一部分。

第 2 章 - Consul 简介 - 图1

Consul 的安装由 Consul 服务器、Consul 客户端和 sidecar 代理组成。

第 2 章 - Consul 简介 - 图2

Consul 的控制平面由 Consul 服务器和 Consul 客户端组成。

Consul 服务器

Consul 服务器是 Consul 的数据库。Consul 需要存储诸如目录(节点和服务列表)、配置、健康状态等数据。对于生产环境的 Consul 部署,必须运行多个 Consul 服务器(尽管对于测试目的,一个服务器就足够了)。运行多个服务器可以确保高可用性和数据持久性:如果一台服务器宕机,其余服务器仍然可以处理请求且不会丢失数据。

您应该始终运行奇数个服务器,例如三个或五个,因为运行偶数个服务器并不会增加 Consul 的容错能力(因此额外的服务器只是浪费资源)。Consul 必须有大多数服务器在运行才能继续正常工作。如果您运行三个服务器,多数是两个,因此您可以容忍一个服务器宕机。如果您运行四个服务器,多数是三个,因此您仍然只能容忍一个服务器宕机。

运行多个服务器可以确保可靠性,但这使得保持数据一致性变得困难:如果服务器之间存在网络分区并导致它们不同步怎么办?Consul 使用一种称为 Raft 的协议来解决这个问题。

Consul 服务器将其数据存储在磁盘上。在 Kubernetes 上,Consul 服务器以 StatefulSet 形式部署,并使用 PersistentVolumes 来存储数据:

  • StatefulSet:一种类似于 Deployment 的 Kubernetes 资源类型,保证相同的存储卷始终与相同的 pod 配对。
  • PersistentVolume:一种可以挂载到 pod 上的存储块,即使该 pod 重启也不会被删除。

在虚拟机上,Consul 服务器应部署在节点重启时不会丢失的磁盘卷上。 在生产环境中,建议为每个 Consul 服务器专门分配一个节点,以避免资源争用问题。在 Kubernetes 上,可以通过使用污点和容忍度来限制哪些 pod 可以调度到这些节点上,从而实现这一点。

构建 Raft 共识

Consul 服务器不断使用 Raft 进行通信。Raft 是一种共识算法,用于分布式系统中以确保多个服务器可以就世界状态达成一致。Raft 在 Consul 中执行两个任务:领导者选举和复制。

领导者选举是选择一个 Consul 服务器作为领导者的过程。所有对 Consul 的写操作——例如注册新服务——都通过领导者进行。如果没有一个单一的领导者,不同的值可能会同时写入不同的服务器,从而无法确定实际值应该是什么。例如,如果同一个服务在不同服务器上同时注册了不同的 IP 地址,应该使用哪个 IP?通过 Raft,所有注册都通过领导者进行,只有领导者决定服务的真实 IP。Raft 还确保如果领导者宕机,会选举出新的领导者。

复制是确保所有写入领导者的数据都复制到其他 Consul 服务器的过程。如果领导者宕机并选出新的领导者,Raft 复制保证新领导者将拥有与旧领导者相同的数据,因此不会丢失任何数据。

Consul 客户端

Consul 客户端在集群中的每个工作节点上运行。Consul 客户端负责检测其节点上运行的服务的健康状况以及集群中其他节点的健康状况。然后,Consul 客户端将这些信息发送给 Consul 服务器,以保持其服务列表的最新状态。

理论上,Consul 服务器可以检查集群中所有服务和节点的健康状况,从而不需要 Consul 客户端。然而,实际上,这种集中式方法无法扩展到 Consul 旨在支持的成千上万个节点和服务。相反,在每个节点上运行 Consul 客户端,使 Consul 能够使用分布式方法来检查服务和节点的健康状况,这种方法易于扩展,即使在大规模集群中也能在毫秒内检测到故障。

Consul 客户端还负责配置其节点上的 sidecar 代理。在我介绍示例用例时会更详细地介绍这一点。

注意 Consul 服务器也可以充当 Consul 客户端。这意味着您可以在与 Consul 服务器相同的节点上运行服务,而服务器将管理 sidecar 代理而无需额外运行 Consul 客户端。此架构仅推荐用于小型测试集群,因为在这些集群中,使用单独的节点来运行 Consul 服务器和服务工作负载没有意义。

使用 Serf 进行分布式故障检测

Consul 需要检测两种类型的故障:服务故障和节点故障。服务故障是指运行在节点上的服务故障。为检测此故障,Consul 客户端对其节点上的每个服务运行健康检查。这种方法是可扩展的,因为每个节点通常有 1 到 100 个服务。

节点故障是指整个节点离线。如果一个节点离线,该节点上的 Consul 客户端无法告知集群其服务不再运行,因为它已经死了!Consul 服务器可以像 Consul 客户端对服务所做的那样对节点进行类似的健康检查,但这将不可扩展,因为可能有成千上万个节点,而只有三个或五个 Consul 服务器。

相反,为了检测节点故障,Consul 使用一个名为 Serf 的库。Serf 实现了一种 gossip 算法(也称为 epidemic 算法):这种算法通过依赖每个客户端将消息中继给少数其他客户端(默认情况下为三个,随机选择)来在集群中共享数据。这些客户端依次选择另外三个随机客户端进行通信,以此类推(见图 2-3)。这类似于小道消息的传播方式或病毒的传播方式,因此得名。

第 2 章 - Consul 简介 - 图3

gossip 算法的工作原理是客户端与少数其他客户端通信。这些客户端反过来与其他客户端通信,信息迅速传播开来。

在 Serf 中,客户端不断相互检查。如果确定某个客户端宕机,该信息会迅速传播到集群的其余部分。理论上,分布式故障检测比集中式方法花费的时间更长,但实际上,即使在拥有数千个节点的集群中,故障检测和传播给所有客户端的时间也不到两秒。

在 Kubernetes 上,Consul 客户端作为 DaemonSet 运行。DaemonSet 是一种类似于 Deployment 的 Kubernetes 资源类型,保证在集群的每个节点上运行一个 Consul 客户端 pod。这确保始终有一个 Consul 客户端可用于管理 sidecar 代理。在虚拟机上,则由您确保每个工作负载节点都配置了一个 Consul 客户端。

Sidecar 代理

每个服务实例,即服务的特定运行实例,都有一个专用的 sidecar 代理。代理的职责是拦截进出其服务实例的请求,并根据配置对请求执行操作——例如,使用 TLS 加密请求,或拒绝来自某些服务的请求。

Consul 本身不是一个代理。相反,Consul 使用一个名为 Envoy 的代理。Envoy 是由 Lyft 创建的开源代理,现在是 CNCF(云原生计算基金会)的一个项目,被许多服务网格使用。Envoy 使用 C++ 编写,具有非常低的内存占用。这意味着它可以在所有服务旁边运行,对资源和延迟的影响最小。Envoy 支持数百种功能,包括负载均衡、可观测性、健康检查等。Consul 客户端通过其 gRPC API 配置 Envoy。

在 Kubernetes 上,sidecar 代理作为服务 pod 内的独立容器运行。这确保服务容器可以在本地 pod 网络中路由到其 sidecar 代理。在虚拟机上,sidecar 代理作为每个服务实例的独立进程运行。

使用示例

现在你已经熟悉了 Consul 服务器、客户端和 sidecar 代理,让我们通过一个示例用例来了解这三个组件如何协同工作。

设想在一个节点上启动了一个名为 backend 的新服务(见图 2-4 的步骤 1)。接下来,将 backend 服务及其 IP 地址(例如 1.2.3.4)注册到该节点上的 Consul 客户端(步骤 2)。Consul 客户端然后向 Consul 服务器发出请求,通知它们在该节点上运行了一个名为 backend 的新服务,地址为 1.2.3.4(步骤 3)。Consul 服务器然后将该服务添加到目录中(步骤 4)。

第 2 章 - Consul 简介 - 图4

在 node 节点上开启新服务的流程。

与此同时,backend 服务的代理启动。代理与本地 Consul 客户端建立连接(见图 2-5 的步骤 1)。Consul 客户端配置代理,并保持连接以便将来重新配置(步骤 2)。

第 2 章 - Consul 简介 - 图5

Consul 客户端配置 backend 服务的代理。

现在设想在集群中的另一个节点上运行了一个名为 frontend 的服务,并且该 frontend 服务配置为与 backend 服务通信(如图 2-6 所示)。

第 2 章 - Consul 简介 - 图6

frontend 服务已配置为与 backend 服务通信,但它还不知道使用哪个地址。

frontend 的代理需要知道 backend 服务的地址才能发起请求。为此,与 frontend 服务在同一节点上的 Consul 客户端会监视 Consul 服务器目录中关于 backend 服务的新实例(见图 2-7 的步骤 1)。当 backend 服务的新实例被注册到目录中时,Consul 服务器会将新地址发送给 Consul 客户端(步骤 2)。然后,Consul 客户端用 backend 服务的新地址更新 frontend 服务代理的配置(步骤 3)。

第 2 章 - Consul 简介 - 图7

frontend 的代理获取 backend 服务的地址以便发起请求。

现在,frontend 服务的代理知道了 backend 服务的地址。当 frontend 服务发起请求时,frontend 的代理会拦截这个请求(见图 2-8 的步骤 1)。代理发现请求的目标是 backend 服务,由于它知道 backend 服务的地址 1.2.3.4,它将请求转发到该地址(步骤 2)。请求到达 backend 服务,backend 服务的代理拦截了它(步骤 3)。假设 backend 的代理配置为只允许来自 frontend 服务的请求。代理检查请求并发现它来自 frontend 服务,因此允许请求通过(步骤 4)。

第 2 章 - Consul 简介 - 图8

Sidecar 代理捕获流入和流出流量,并根据其配置的规则对这些流量进行处理。

这个例子展示了 Consul 服务器、客户端和 sidecar 代理如何协同工作。Consul 服务器和客户端通过通信共享集群数据并配置 sidecar 代理。Sidecar 代理捕获进出流量,并根据其配置的规则对流量进行处理,例如将流量路由到另一个代理,或因未授权而拒绝流量。

Consul 与其他服务网格的对比

市场上有许多其他服务网格,如 Istio 和 Linkerd。大多数服务网格遵循相似的通用架构,即由控制平面管理 sidecar 代理。Consul 的独特之处在于,其控制平面可以完全独立于 Kubernetes 运行。这意味着如果您在管理 VM 集群,就不需要同时运行 Kubernetes。

每种服务网格都有其优缺点,具体取决于使用场景。选择服务网格的一个好方法是关注您最重要的三个使用场景,如安全性、可观测性和多集群支持,然后测试最受欢迎的服务网格,选择您觉得最舒适的一个。

Consul 的其他功能

Consul 不仅仅是一个服务网格。它还是一个用于服务配置的键值存储和一个 DNS 服务发现解决方案。使用 DNS 时,没有 sidecar 代理。请求直接在服务之间路由。这意味着没有自动加密、可观测性或流量控制功能。

本书重点介绍了 Consul 的服务网格功能,但您可以访问 Consul 的文档以了解更多关于其其他功能的信息。

总结

现在您已经了解了 Consul 的高层工作原理。Consul 服务器是 Consul 的数据库,管理目录——即集群中所有节点和服务的列表。Consul 客户端部署在每个工作负载节点上,并管理其本地节点上运行的 sidecar 代理。Sidecar 代理捕获流量并执行服务网格操作,如加密和生成指标。

您还了解了 Consul 如何通过使用 Raft 协议在 Consul 服务器上实现共识和通过 Serf 实现客户端之间的故障检测,从而作为一个可靠且可扩展的系统运行。

在下一章中,您将实际部署 Consul 到 Kubernetes 或虚拟机上。