Netflix按需集群发现的零配置服务网格


在这篇文章中,我们将讨论Netflix对服务网格的采用:一些历史,动机,以及我们如何与Kinvolk和Envoy社区合作开发一个简化复杂微服务环境中服务网格采用的特性:按需群集发现。

在云计算的早期几年里,我们为服务发现构建了Eureka,为IPC构建了Ribbon(内部称为NIWS)。

  • Eureka 解决了服务如何发现要与之通信的实例的问题,
  • 而 Ribbon 提供了用于负载平衡的客户端逻辑以及许多其他弹性功能。

这两项技术与许多其他弹性和混沌工具一起产生了巨大的变化:我们的可靠性因此显着提高。

Eureka 和 Ribbon 提供了一个简单但功能强大的界面,这使得采用它们变得很容易。为了使一个服务能够与另一个服务通信,它需要知道两件事:目标服务的名称,以及流量是否应该安全。

在此架构中,服务到服务的通信不再经历负载均衡器的单点故障。缺点是 Eureka 成为一个新的单点故障。

为什么要网格化?
上述架构(SpringCloud)在过去十年中为我们提供了良好的服务。

  • 首先,我们增加了不同 IPC 客户端的数量。我们的内部 IPC 流量现在是普通 REST、 GraphQL和gRPC的混合。
  • 其次,我们已经从纯 Java 环境转变为多语言环境:我们现在还支持node.js、Python以及各种 OSS 和现成软件。
  • 第三,我们继续为IPC客户端添加更多功能:自适应并发限制、熔断等功能、对冲和故障注入已成为我们的工程师为使我们的系统更加可靠而使用的标准工具。

与十年前相比,我们现在支持更多功能、更多语言、更多客户端。保持所有这些实现之间的功能奇偶性并确保它们都以相同的方式运行是具有挑战性的:我们想要的是所有这些功能的单一的、经过良好测试的实现,这样我们就可以在一个地方进行更改和修复错误

这就是服务网格的用武之地:我们可以将 IPC 功能集中在单个实现中,并使每种语言的客户端尽可能简单:它们只需要知道如何与本地代理通信。

Envoy非常适合我们作为代理:
它是一款经过实战考验的 OSS 产品,在行业中大规模使用,具有许多关键的弹性功能,并且在我们需要扩展其功能时提供了良好的扩展点。通过中央控制平面配置代理的能力是一个杀手级功能:这使我们能够动态配置客户端负载平衡,就好像它是中央负载平衡器一样,但仍然避免负载平衡器成为服务中的单点故障到服务请求路径。

转向网格
一旦我们确定迁移到服务网格是正确的选择,下一个问题就变成了:我们应该如何迁移?

我们很快就遇到了无缝迁移的障碍:Envoy 要求将集群指定为代理配置的一部分。如果服务 A 需要与集群 B 和 C 通信,那么您需要将集群 B 和 C 定义为 A 代理配置的一部分。
这在规模上可能具有挑战性:任何给定的服务都可能与数十个集群通信,并且该组集群对于每个应用程序都是不同的。

我们评估了多种不同的方法来填充集群配置:

  1. 让服务所有者定义他们的服务需要与之通信的集群。此选项看起来很简单,但在实践中,服务所有者并不总是知道或想知道他们与哪些服务通信。服务通常会导入其他团队提供的库,这些库在幕后与多个其他服务进行通信,或者与遥测和日志记录等其他运营服务进行通信。这意味着服务所有者需要了解这些辅助服务和库在幕后是如何实现的,并在它们发生变化时调整配置。
  2. 根据服务的调用图自动生成 Envoy 配置。此方法对于现有服务很简单,但在推出新服务或添加新的上游集群进行通信时具有挑战性。
  3. 将所有集群推送到每个应用程序:此选项因其简单性而吸引人,但餐巾纸后面的数学很快就向我们表明,将数百万个端点推送到每个代理是不可行的。

考虑到我们无缝采用的目标,每个选项都有足够明显的缺点,因此我们探索了另一个选项:如果我们可以在运行时按需获取集群信息,而不是预先定义它,会怎么样?

当时,服务网格工作仍处于引导阶段,只有少数工程师致力于此。我们联系了Kinvolk,看看他们是否可以与我们和 Envoy 社区合作来实现此功能。此次合作的成果是按需集群发现(ODCDS)。借助此功能,代理现在可以在第一次尝试连接时查找集群信息,而不是在 config.json 中预定义所有集群。

有了这个功能,我们需要为代理提供集群信息以供查找。

  • 我们已经开发了一个实现 Envoy XDS 服务的服务网格控制平面。
  • 然后我们需要从 Eureka 获取服务信息以便返回代理。
  • 我们将 Eureka VIP 和 SVIP 表示为单独的 Envoy Cluster Discovery Service (CDS) 集群(因此服务myservice可能具有集群myservice.vip和myservice.svip)。
  • 群集中的各个主机表示为单独的端点发现服务 (EDS) 端点。这使我们能够重用相同的 Eureka 抽象,并且像 Ribbon 这样的 IPC 客户端可以通过最小的更改转移到网格。

控制平面和数据平面都发生更改后,流程的工作原理如下:

  1. 客户端请求进入 Envoy
  2. 根据 Host / :authority 标头提取目标集群(这里使用的标头是可配置的,但这是我们的方法)。如果该簇已知,则跳至步骤 7
  3. 该集群不存在,因此我们暂停正在进行的请求
  4. 向控制平面上的集群发现服务 (CDS) 端点发出请求。控制平面根据服务的配置和Eureka注册信息生成定制的CDS响应
  5. Envoy 取回集群 (CDS),这会通过端点发现服务 (EDS) 触发端点拉取。根据该 VIP 或 SVIP 的 Eureka 状态信息返回集群的端点
  6. 客户端请求取消暂停
  7. Envoy 正常处理请求:它使用负载平衡算法选择端点并发出请求

此流程在几毫秒内完成,但仅在向集群发出第一个请求时完成。之后,Envoy 的行为就像在配置中定义了集群一样。

至关重要的是,该系统允许我们无需配置即可将服务无缝迁移到服务网格,满足我们主要的采用限制之一。

按需获取数据有一个缺点:这会增加对集群的第一个请求的延迟。我们遇到过一些用例,其中服务在第一个请求时需要非常低的延迟访问,并且添加额外的几毫秒会增加太多的开销。
对于这些用例,服务需要预先定义与其通信的集群,或者在第一次请求之前启动连接。我们还考虑在代理启动时根据历史请求模式从控制平面预推送集群。总体而言,我们认为系统复杂性的降低证明了一小部分服务的缺点是合理的。

我们仍处于服务网格之旅的早期阶段。将我们的自适应并发限制实现移植到 Envoy 是一个很好的开始