容器、微服务和服务网格

在互联网上 用品 о 服务网格 (服务网格),这是另一个。 万岁! 但为什么? 然后我想表达一下我的观点,如果服务网格早在10年前就出现了,早在Docker、Kubernetes等容器平台出现之前就已经出现了,那就更好了。 我并不是说我的观点比其他人更好或更差,但由于服务网格是相当复杂的动物,多种观点将有助于更好地理解它们。

我将讨论 dotCloud 平台,它构建在一百多个微服务之上,支持数千个容器化应用程序。 我将解释我们在开发和启动它时面临的挑战,以及服务网格如何能够(或不能)提供帮助。

点云的历史

我已经写过有关 dotCloud 的历史和该平台的架构选择的文章,但我没有过多谈论网络层。 如果你不想投入阅读 上一篇 关于 dotCloud,简而言之,要点如下:它是一个 PaaS 平台即服务,允许客户运行各种应用程序(Java、PHP、Python...),并支持各种数据服务(MongoDB、MySQL、Redis...)和像 Heroku 这样的工作流程:您将代码上传到平台,它构建容器映像并部署它们。

我将告诉您如何将流量引导至 dotCloud 平台。 并不是因为它特别酷(尽管该系统在当时运行得很好!),而是主要是因为使用现代工具,如果一个适度的团队需要一种在一群人之间路由流量的方法,那么他们可以在短时间内轻松实现这样的设计微服务或一堆应用程序。 这样,您可以比较选项:如果您自己开发所有内容或使用现有的服务网格,会发生什么。 标准的选择是自己制作或购买。

托管应用程序的流量路由

dotCloud 上的应用程序可以公开 HTTP 和 TCP 端点。

HTTP 端点 动态添加到负载均衡器集群配置 希帕切。 这与今天的资源所做的类似 入口 在 Kubernetes 和负载均衡器中 特拉菲克.

客户端通过适当的域连接到 HTTP 端点,前提是域名指向 dotCloud 负载均衡器。 没什么特别的。

TCP端点 与端口号关联,然后通过环境变量传递到该堆栈中的所有容器。

客户端可以使用适当的主机名(例如 gateway-X.dotcloud.com)和端口号连接到 TCP 端点。

该主机名解析为“nats”服务器集群(与 NATS),它将传入的 TCP 连接路由到正确的容器(或者,在负载平衡服务的情况下,路由到正确的容器)。

如果您熟悉 Kubernetes,这可能会让您想起服务 节点端口.

dotCloud平台上没有同等服务 集群IP:为了简单起见,从平台内部和外部访问服务的方式都是相同的。

一切都组织得很简单:HTTP 和 TCP 路由网络的最初实现可能只有几百行 Python 代码。 随着平台的发展和额外需求的出现,简单的(我想说的是幼稚的)算法得到了完善。

不需要对现有代码进行广泛的重构。 尤其, 12 要素应用程序 可以直接使用通过环境变量获得的地址。

这与现代服务网格有何不同?

有限的 能见度。 我们根本没有任何关于 TCP 路由网格的指标。 当谈到 HTTP 路由时,更高版本引入了详细的 HTTP 指标,包括错误代码和响应时间,但现代服务网格走得更远,提供了与 Prometheus 等指标收集系统的集成。

可见性不仅从操作角度(帮助解决问题)很重要,而且在发布新功能时也很重要。 这是关于安全的 蓝绿部署 и 金丝雀部署.

路由效率 也是有限的。 在 dotCloud 路由网格中,所有流量都必须经过一组专用路由节点。 这意味着可能会跨越多个 AZ(可用区)边界并显着增加延迟。 我记得故障排除代码每页进行一百多个 SQL 查询,并为每个查询打开一个到 SQL 服务器的新连接。 在本地运行时,页面会立即加载,但在 dotCloud 中,加载需要几秒钟,因为每个 TCP 连接(以及后续的 SQL 查询)需要数十毫秒。 在这种特殊情况下,持久连接解决了这个问题。

现代服务网格更擅长处理此类问题。 首先,他们检查连接是否已路由 在源头。 逻辑流程是相同的: клиент → меш → сервис,但现在网格在本地工作,而不是在远程节点上工作,因此连接 клиент → меш 是本地的并且非常快(微秒而不是毫秒)。

现代服务网格还实现了更智能的负载平衡算法。 通过监控后端的运行状况,他们可以将更多流量发送到更快的后端,从而提高整体性能。

安全 也更好。 dotCloud 路由网格完全在 EC2 Classic 上运行,并且不加密流量(基于这样的假设:如果有人设法在 EC2 网络流量上放置嗅探器,那么您就已经遇到大麻烦了)。 现代服务网格透明地保护我们的所有流量,例如,通过相互 TLS 身份验证和后续加密。

平台服务的路由流量

好的,我们已经讨论了应用程序之间的流量,但是 dotCloud 平台本身呢?

该平台本身由大约一百个负责各种功能的微服务组成。 有些接受其他人的请求,有些是连接到其他服务但本身不接受连接的后台工作人员。 无论如何,每个服务都必须知道它需要连接的地址的端点。

许多高级服务可以使用上述路由网格。 事实上,dotCloud 的一百多个微服务中的许多都已作为常规应用程序部署在 dotCloud 平台本身上。 但少数低级服务(特别是那些实现此路由网格的服务)需要更简单、依赖性更少的东西(因为它们不能依赖自己来工作——老鸡和蛋的问题)。

这些低级别的关键任务服务是通过直接在几个关键节点上运行容器来部署的。 在这种情况下,没有使用标准平台服务:链接器、调度器和运行器。 如果你想与现代容器平台进行比较,这就像运行一个控制平面 docker run 直接在节点上,而不是将任务委托给 Kubernetes。 在概念上非常相似 静态模块(pod),它使用 库贝德姆 или 引导库 启动独立集群时。

这些服务以简单粗暴的方式公开:一个 YAML 文件列出了它们的名称和地址; 每个客户端都必须获取此 YAML 文件的副本进行部署。

一方面,它非常可靠,因为它不需要外部键/值存储的支持,例如 Zookeeper(请记住,当时 etcd 或 Consul 还不存在)。 另一方面,这使得服务转移变得困难。 每次进行移动时,所有客户端都会收到更新的 YAML 文件(并且可能会重新启动)。 不是很舒服!

随后,我们开始实现一个新方案,每个客户端连接到本地代理服务器。 它不需要地址和端口,只需要知道服务的端口号,并通过 localhost。 本地代理处理此连接并将其转发到实际服务器。 现在,当将后端移动到另一台机器或进行扩展时,您只需更新所有这些本地代理,而不是更新所有客户端; 并且不再需要重新启动。

(还计划将流量封装在 TLS 连接中,并在接收端放置另一个代理服务器,以及在没有接收服务参与的情况下验证 TLS 证书,接收服务配置为仅接受连接 localhost。 稍后会详细介绍)。

这非常类似于 智能堆栈 来自 Airbnb,但显着的区别在于 SmartStack 是实现并部署到生产中的,而 dotCloud 的内部路由系统在 dotCloud 成为 Docker 时被搁置。

我个人认为 SmartStack 是 Istio、Linkerd 和 Consul Connect 等系统的前身之一,因为它们都遵循相同的模式:

  • 在每个节点上运行代理。
  • 客户端连接到代理。
  • 当后端发生变化时,控制平面会更新代理配置。
  • ... 利润!

服务网格的现代实现

如果我们今天需要实现类似的网格,我们可以使用类似的原则。 例如,通过将服务名称映射到空间中的地址来配置内部 DNS 区域 127.0.0.0/8。 然后在集群中的每个节点上运行 HAProxy,接受每个服务地址(在该子网中)的连接 127.0.0.0/8)并将负载重定向/平衡到适当的后端。 HAProxy配置可以控制 信任,允许您将后端信息存储在 etcd 或 Consul 中,并在需要时自动将更新的配置推送到 HAProxy。

这几乎就是 Istio 的工作原理! 但有一些差异:

  • 用途 特使代理 而不是 HAProxy。
  • 通过 Kubernetes API 而不是 etcd 或 Consul 存储后端配置。
  • 服务分配的是内部子网上的地址(Kubernetes ClusterIP 地址),而不是 127.0.0.0/8。
  • 有一个附加组件 (Citadel),用于在客户端和服务器之间添加相互 TLS 身份验证。
  • 支持熔断、分布式追踪、金丝雀部署等新特性。

让我们快速浏览一下其中的一些差异。

特使代理

Envoy Proxy 是由 Lyft [Uber 在出租车市场的竞争对手 - 大约。 车道]。 它在很多方面与其他代理(例如 HAProxy、Nginx、Traefik...)类似,但 Lyft 编写了他们的代理,因为他们需要其他代理所缺乏的功能,并且创建一个新代理似乎比扩展现有代理更明智。

Envoy 可以单独使用。 如果我有一个特定的服务需要连接到其他服务,我可以将其配置为连接到 Envoy,然后使用其他服务的位置动态配置和重新配置 Envoy,同时获得许多很棒的附加功能,例如可见性。 我们不是使用自定义客户端库或将调用跟踪注入到代码中,而是将流量发送到 Envoy,它会为我们收集指标。

但 Envoy 也能够作为 数据平面 (数据平面)用于服务网格。 这意味着 Envoy 现在已为此服务网格配置 控制平面 (控制平面)。

控制平面

对于控制平面,Istio 依赖 Kubernetes API。 这与使用confd没有太大区别,它依赖 etcd 或 Consul 来查看数据存储中的键集。 Istio 使用 Kubernetes API 来查看一组 Kubernetes 资源。

在这之后: 个人觉得很有用 Kubernetes API 说明内容如下:

Kubernetes API Server 是一个“哑服务器”,为 API 资源提供存储、版本控制、验证、更新和语义。

Istio 旨在与 Kubernetes 配合使用; 如果您想在 Kubernetes 之外使用它,那么您需要运行 Kubernetes API 服务器的实例(以及 etcd 帮助程序服务)。

服务地址

Istio 依赖 Kubernetes 分配的 ClusterIP 地址,因此 Istio 服务接收内部地址(不在范围内) 127.0.0.0/8).

没有 Istio 的 Kubernetes 集群中特定服务的 ClusterIP 地址的流量会被 kube-proxy 拦截并发送到该代理的后端。 如果您对技术细节感兴趣,kube-proxy 设置 iptables 规则(或 IPVS 负载均衡器,具体取决于其配置方式)来重写前往 ClusterIP 地址的连接的目标 IP 地址。

一旦 Istio 安装在 Kubernetes 集群上,就不会发生任何变化,直到通过引入容器为给定使用者甚至整个命名空间显式启用它 sidecar 进入自定义 Pod。 该容器将启动 Envoy 的实例并设置一组 iptables 规则来拦截流向其他服务的流量并将该流量重定向到 Envoy。

当与 Kubernetes DNS 集成时,这意味着我们的代码可以通过服务名称进行连接,并且一切都“正常工作”。 换句话说,我们的代码发出如下查询 http://api/v1/users/4242,那么 api 解决请求 10.97.105.48,iptables 规则将拦截来自 10.97.105.48 的连接并将其转发到本地 Envoy 代理,而该本地代理会将请求转发到实际的后端 API。 唷!

额外的装饰

Istio 还通过 mTLS(相互 TLS)提供端到端加密和身份验证。 一个名为 堡垒.

还有一个组件 密炼,特使可以请求 请求根据各种因素(例如标头、后端负载等)对该请求做出特殊决定...(不用担心:有很多方法可以保持 Mixer 运行,即使崩溃,Envoy 也会继续工作作为代理很好)。

当然,我们提到了可见性:Envoy 在提供分布式跟踪的同时收集大量指标。 在微服务架构中,如果单个 API 请求必须经过微服务 A、B、C 和 D,那么在登录时,分布式跟踪将为该请求添加唯一标识符,并通过对所有这些微服务的子请求存储该标识符,从而允许捕获所有相关的呼叫、延迟等。

开发或购买

Istio 因复杂而闻名。 相比之下,使用现有工具构建我在本文开头描述的路由网格相对简单。 那么,创建自己的服务网格有意义吗?

如果我们的需求不大(我们不需要可见性、断路器和其他微妙之处),那么就会考虑开发我们自己的工具。 但如果我们使用 Kubernetes,甚至可能不需要它,因为 Kubernetes 已经提供了服务发现和负载均衡的基本工具。

但如果我们有高级需求,那么“购买”服务网格似乎是一个更好的选择。 (这并不总是“购买”,因为 Istio 是开源的,但我们仍然需要投入工程时间来理解、部署和管理它。)

我应该选择 Istio、Linkerd 还是 Consul Connect?

到目前为止,我们只讨论了 Istio,但这并不是唯一的服务网格。 流行的替代方案 - 链接器,还有更多 领事连接.

有什么选择呢?

老实说,我不知道。 目前我认为自己还没有足够的能力回答这个问题。 有几个 有趣 用品 比较这些工具,甚至 基准.

一种有前途的方法是使用类似的工具 超级格鲁。 它实现了一个抽象层来简化和统一服务网格公开的 API。 我们可以使用 SuperGloo 更简单的构造,而不是学习不同服务网格的特定(并且在我看来相对复杂)API - 并轻松地从一个构造切换到另一个构造,就好像我们有一种描述 HTTP 接口和后端的中间配置格式一样生成 Nginx、HAProxy、Traefik、Apache 的实际配置...

我已经涉足了一些 Istio 和 SuperGloo,在下一篇文章中我想展示如何使用 SuperGloo 将 Istio 或 Linkerd 添加到现有集群,以及后者如何完成工作,即允许您从将一个服务网格移植到另一个服务网格,而无需覆盖配置。

来源: habr.com

添加评论