Kubernetes 中的负载平衡和扩展长期连接

Kubernetes 中的负载平衡和扩展长期连接
本文将帮助您了解负载均衡在 Kubernetes 中的工作原理、扩展长期连接时会发生什么,以及为什么在使用 HTTP/2、gRPC、RSockets、AMQP 或其他长期协议时应考虑客户端平衡。 

关于 Kubernetes 中流量如何重新分配的一些信息 

Kubernetes 为部署应用程序提供了两个方便的抽象:服务和部署。

部署描述了在任何给定时间应用程序的运行方式和数量。 每个应用程序都部署为 Pod 并分配一个 IP 地址。

服务在功能上与负载均衡器类似。 它们旨在跨多个 Pod 分配流量。

让我们看看它是什么样子.

  1. 在下图中,您可以看到同一应用程序的三个实例和一个负载均衡器:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 负载均衡器称为服务并分配有 IP 地址。 任何传入请求都会重定向到其中一个 Pod:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 部署场景决定了应用程序的实例数量。 您几乎永远不需要直接在以下位置展开:

    Kubernetes 中的负载平衡和扩展长期连接

  4. 每个 Pod 都分配有自己的 IP 地址:

    Kubernetes 中的负载平衡和扩展长期连接

将服务视为 IP 地址的集合很有用。 每次访问该服务时,都会从列表中选择一个 IP 地址并用作目标地址。

看起来像这样.

  1. 服务收到一个curl 10.96.45.152请求:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 该服务选择三个 pod 地址之一作为目标:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 流量被重定向到特定的 Pod:

    Kubernetes 中的负载平衡和扩展长期连接

如果您的应用程序由前端和后端组成,那么您将为每个应用程序提供一个服务和一个部署。

当前端向后端发出请求时,它不需要确切知道后端服务了多少个 Pod:可能有 XNUMX 个、XNUMX 个或 XNUMX 个。

此外,前端不知道为后端提供服务的 Pod 的地址。

当前端向后端发出请求时,使用后端服务的IP地址,该IP地址不会改变。

下面是它的外观.

  1. 1下请求内部后端组件。 它不是为后端选择特定的后端,而是向服务发出请求:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 该服务选择后端 Pod 之一作为目标地址:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 流量从 Pod 1 流向 Pod 5,由服务选择:

    Kubernetes 中的负载平衡和扩展长期连接

  4. Under 1 并不确切知道有多少像 under 5 这样的 pod 隐藏在服务后面:

    Kubernetes 中的负载平衡和扩展长期连接

但该服务到底如何分发请求呢? 好像是用了循环平衡? 让我们弄清楚一下。 

Kubernetes 服务中的平衡

Kubernetes 服务不存在。 没有为服务分配 IP 地址和端口的进程。

您可以通过登录集群中的任何节点并运行 netstat -ntlp 命令来验证这一点。

您甚至无法找到分配给该服务的 IP 地址。

服务的IP地址位于控制层,在控制器中,并记录在数据库-etcd中。 另一个组件 - kube-proxy 使用相同的地址。
Kube-proxy 接收所有服务的 IP 地址列表,并在集群中的每个节点上生成一组 iptables 规则。

这些规则规定:“如果我们看到服务的 IP 地址,我们需要修改请求的目标地址并将其发送到其中一个 Pod。”

服务 IP 地址仅用作入口点,侦听该 IP 地址和端口的任何进程都不会提供服务。

让我们看看这个

  1. 考虑一个包含三个节点的集群。 每个节点都有 pod:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 涂成米色的捆绑豆荚是服务的一部分。 由于该服务不作为进程存在,因此显示为灰色:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 第一个 Pod 请求服务,并且必须转到关联的 Pod 之一:

    Kubernetes 中的负载平衡和扩展长期连接

  4. 但服务不存在,进程不存在。 它是如何工作的?

    Kubernetes 中的负载平衡和扩展长期连接

  5. 在请求离开节点之前,它会经过 iptables 规则:

    Kubernetes 中的负载平衡和扩展长期连接

  6. iptables 规则知道该服务不存在,并将其 IP 地址替换为与该服务关联的 Pod 的 IP 地址之一:

    Kubernetes 中的负载平衡和扩展长期连接

  7. 该请求收到一个有效的IP地址作为目的地址并正常处理:

    Kubernetes 中的负载平衡和扩展长期连接

  8. 根据网络拓扑,请求最终到达 pod:

    Kubernetes 中的负载平衡和扩展长期连接

iptables 可以负载均衡吗?

不,iptables 用于过滤,并不是为了平衡而设计的。

但是,可以编写一组像这样工作的规则 伪平衡器.

这正是 Kubernetes 中实现的。

如果你有三个 pod,kube-proxy 将编写以下规则:

  1. 以33%的概率选择第一个子,否则进入下一条规则。
  2. 选择第二个的概率为 50%,否则进入下一条规则。
  3. 选择下面的第三个。

该系统导致每个 pod 被选中的概率为 33%。

Kubernetes 中的负载平衡和扩展长期连接

并且不能保证 Pod 2 将在 Pod 1 之后被选中。

注意:iptables使用随机分布的统计模块。 因此,平衡算法是基于随机选择的。

现在您了解了服务的工作原理,让我们看看更有趣的服务场景。

Kubernetes 中的长连接默认情况下不会扩展

从前端到后端的每个 HTTP 请求都由一个单独的 TCP 连接提供服务,该连接可以打开和关闭。

如果前端每秒向后端发送 100 个请求,则打开和关闭 100 个不同的 TCP 连接。

您可以通过打开一个 TCP 连接并将其用于所有后续 HTTP 请求来减少请求处理时间和负载。

HTTP 协议有一个称为 HTTP keep-alive 或连接重用的功能。 在这种情况下,单个 TCP 连接用于发送和接收多个 HTTP 请求和响应:

Kubernetes 中的负载平衡和扩展长期连接

默认情况下不启用此功能:服务器和客户端都必须进行相应配置。

设置本身很简单,并且适合大多数编程语言和环境。

以下是一些不同语言示例的链接:

如果我们在 Kubernetes 服务中使用 keep-alive 会发生什么?
假设前端和后端都支持 keep-alive。

我们有一份前端副本和三份后端副本。 前端发出第一个请求并打开到后端的 TCP 连接。 请求到达服务,选择后端 Pod 之一作为目标地址。 后端发送响应,前端接收响应。

与 TCP 连接在收到响应后关闭的通常情况不同,它现在保持打开状态以接受进一步的 HTTP 请求。

如果前端向后端发送更多请求会发生什么?

为了转发这些请求,将使用开放的 TCP 连接,所有请求都将发送到第一个请求所在的同一后端。

iptables 不应该重新分配流量吗?

不是在这种情况下。

创建 TCP 连接时,它会通过 iptables 规则,选择流量将前往的特定后端。

由于所有后续请求都位于已打开的 TCP 连接上,因此不再调用 iptables 规则。

让我们看看它是什么样子.

  1. 第一个 pod 向服务发送请求:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 你已经知道接下来会发生什么。 该服务不存在,但有 iptables 规则将处理该请求:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 将选择后端 Pod 之一作为目标地址:

    Kubernetes 中的负载平衡和扩展长期连接

  4. 请求到达 Pod。 此时,两个 pod 之间将建立持久的 TCP 连接:

    Kubernetes 中的负载平衡和扩展长期连接

  5. 来自第一个 pod 的任何后续请求都将通过已建立的连接:

    Kubernetes 中的负载平衡和扩展长期连接

结果是更快的响应时间和更高的吞吐量,但您失去了扩展后端的能力。

即使后端有两个 pod,且连接持续,流量也将始终流向其中之一。

这可以解决吗?

由于 Kubernetes 不知道如何平衡持久连接,所以这个任务就落到了你的身上。

服务是称为端点的 IP 地址和端口的集合。

您的应用程序可以从服务获取端点列表,并决定如何在它们之间分发请求。 您可以打开与每个 Pod 的持久连接,并使用循环法平衡这些连接之间的请求。

或者申请更多 复杂的平衡算法.

负责平衡的客户端代码应遵循以下逻辑:

  1. 从服务获取端点列表。
  2. 为每个端点打开持久连接。
  3. 当需要发出请求时,使用打开的连接之一。
  4. 定期更新端点列表,创建新端点或在列表发生变化时关闭旧的持久连接。

这就是它的样子.

  1. 您可以在客户端平衡请求,而不是第一个 pod 将请求发送到服务:

    Kubernetes 中的负载平衡和扩展长期连接

  2. 您需要编写代码来询问哪些 pod 是服务的一部分:

    Kubernetes 中的负载平衡和扩展长期连接

  3. 获得列表后,将其保存在客户端并使用它连接到 Pod:

    Kubernetes 中的负载平衡和扩展长期连接

  4. 您负责负载平衡算法:

    Kubernetes 中的负载平衡和扩展长期连接

现在问题来了:这个问题只适用于HTTP keep-alive吗?

客户端负载均衡

HTTP 不是唯一可以使用持久 TCP 连接的协议。

如果您的应用程序使用数据库,则每次您需要发出请求或从数据库检索文档时,不会打开 TCP 连接。 

相反,会打开并使用与数据库的持久 TCP 连接。

如果您的数据库部署在 Kubernetes 上,并且访问作为服务提供,那么您将遇到上一节中描述的相同问题。

一个数据库副本将比其他数据库副本负载更多。 Kube-proxy 和 Kubernetes 不会帮助平衡连接。 您必须注意平衡对数据库的查询。

根据您用来连接数据库的库,您可能有不同的选项来解决此问题。

下面是从 Node.js 访问 MySQL 数据库集群的示例:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

还有许多其他协议使用持久 TCP 连接:

  • WebSocket 和安全 WebSocket
  • HTTP / 2的
  • 远程过程调用
  • RSockets
  • 空气质量计划

您应该已经熟悉其中的大多数协议。

但如果这些协议如此受欢迎,为什么没有标准化的平衡解决方案呢? 为什么客户端逻辑需要改变? 有原生的 Kubernetes 解决方案吗?

Kube-proxy 和 iptables 旨在涵盖部署到 Kubernetes 时的最常见用例。 这是为了方便。

如果您使用的是公开 REST API 的 Web 服务,那么您很幸运 - 在这种情况下,不使用持久 TCP 连接,您可以使用任何 Kubernetes 服务。

但是一旦开始使用持久 TCP 连接,您就必须弄清楚如何在后端之间均匀分配负载。 Kubernetes 不包含针对这种情况的现成解决方案。

然而,当然有一些选择可以提供帮助。

平衡 Kubernetes 中的长期连接

Kubernetes 中有四种类型的服务:

  1. 集群IP
  2. 节点端口
  3. 负载均衡器
  4. 无头

前三个服务基于虚拟 IP 地址运行,kube-proxy 使用该地址来构建 iptables 规则。 但所有服务的根本基础是无头服务。

无头服务没有任何与其关联的 IP 地址,仅提供一种用于检索与其关联的 Pod(端点)的 IP 地址和端口列表的机制。

所有服务均基于无头服务。

ClusterIP 服务是一个无头服务,并添加了一些内容: 

  1. 管理层为其分配一个IP地址。
  2. Kube-proxy 生成必要的 iptables 规则。

这样您就可以忽略 kube-proxy 并直接使用从无头服务获取的端点列表来平衡您的应用程序的负载。

但是我们如何为集群中部署的所有应用程序添加类似的逻辑呢?

如果您的应用程序已经部署,那么此任务似乎是不可能的。 然而,还有一个替代选择。

服务网格将为您提供帮助

您可能已经注意到客户端负载平衡策略是相当标准的。

当应用程序启动时,它:

  1. 从服务获取 IP 地址列表。
  2. 打开并维护连接池。
  3. 通过添加或删除端点定期更新池。

一旦应用程序想要发出请求,它:

  1. 使用某种逻辑(例如循环)选择可用连接。
  2. 执行请求。

这些步骤适用于 WebSocket、gRPC 和 AMQP 连接。

您可以将此逻辑分离到一个单独的库中并在您的应用程序中使用它。

但是,您可以使用 Istio 或 Linkerd 等服务网格来代替。

Service Mesh 通过以下流程增强您的应用程序:

  1. 自动搜索服务IP地址。
  2. 测试 WebSockets 和 gRPC 等连接。
  3. 使用正确的协议平衡请求。

Service Mesh 有助于管理集群内的流量,但它非常消耗资源。 其他选项包括使用 Netflix Ribbon 等第三方库或 Envoy 等可编程代理。

如果忽略平衡问题会发生什么?

您可以选择不使用负载平衡,但仍然不会注意到任何变化。 让我们看几个工作场景。

如果您的客户端多于服务器,那么这不是一个大问题。

假设有五个客户端连接到两个服务器。 即使没有平衡,两台服务器都会被使用:

Kubernetes 中的负载平衡和扩展长期连接

连接可能不均匀分布:可能有四个客户端连接到同一服务器,但很有可能两台服务器都会被使用。

更成问题的是相反的情况。

如果您的客户端较少而服务器较多,您的资源可能未得到充分利用,并且会出现潜在的瓶颈。

假设有两个客户端和五个服务器。 在最好的情况下,五台服务器中的两台将有两个永久连接。

其余服务器将处于空闲状态:

Kubernetes 中的负载平衡和扩展长期连接

如果这两台服务器无法处理客户端请求,则水平扩展将无济于事。

结论

Kubernetes 服务旨在在大多数标准 Web 应用程序场景中工作。

但是,一旦您开始使用使用持久 TCP 连接的应用程序协议(例如数据库、gRPC 或 WebSocket),服务就不再适合。 Kubernetes 不提供平衡持久 TCP 连接的内部机制。

这意味着您在编写应用程序时必须考虑客户端平衡。

团队准备的翻译 Mail.ru 的 Kubernetes aaS.

关于该主题还可以阅读什么:

  1. Kubernetes 中的三个级别的自动缩放以及如何有效地使用它们
  2. 本着盗版精神的 Kubernetes 提供了实施模板.
  3. 我们关于数字化转型的 Telegram 频道.

来源: habr.com

添加评论