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


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

关于如何在Kubernetes中重新分配流量 


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

部署描述了在任何给定时间应运行的应用程序副本的数量以及数量。每个应用程序都在(Pod)下部署,并分配了一个IP地址。

功能服务类似于负载均衡器。它们旨在将流量分布在多个炉膛上。

让我们看看它的外观

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

  2. 负载平衡器称为服务,已为其分配IP地址。任何传入的请求都将重定向到其中一个pod:

  3. 部署脚本确定应用程序实例的数量。您几乎永远不必直接在以下位置进行部署:

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



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

如下

  1. 该服务有一个curl请求10.96.45.152:

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

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



如果您的应用程序由一个前端和一个后端组成,那么您将同时拥有一个服务和一个部署。

当前端完成对后端的请求时,它不需要确切知道后端服务多少个炉膛:可以有一个,十个或一百个。

另外,前端对服务于后端的炉床地址一无所知。

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

这是它的样子

  1. Under 1请求后端内部组件。它没有为后端选择特定的对象,而是执行服务请求:

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

  3. 流量从服务选择的炉床1到炉床5:

  4. 在1以下,它不知道该服务背后隐藏了多少5以下的炉床:



但是该服务如何准确地分配请求?似乎使用轮循平衡吗?让我们做对。 

Kubernetes服务的平衡


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

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

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

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

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

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

让我们来看一下。 

  1. 考虑由三个节点组成的集群。每个节点上都有Pod:

  2. 浅米色的针织壁炉是这项服务的一部分。由于该服务不作为进程存在,因此将其灰显:

  3. 第一个请求服务,应该落在以下相关的壁炉中:

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

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

  6. iptables规则知道没有服务,并用与此服务关联的Pod的IP地址之一替换其IP地址:

  7. 该请求会收到一个有效的IP地址作为目标地址,并且通常会被处理:

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



iptables是否能够平衡负载?


不,iptables用于过滤,并非用于平衡。

但是,可以编写一组类似于伪平衡器的规则

而这正是Kubernetes所做的。

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

  1. 选择第一个概率为33%的规则,否则转到下一个规则。
  2. 选择第二个可能性为50%的对象,否则转到下一条规则。
  3. 在下面选择第三个。

这样的系统导致以33%的概率选择每个子的事实。



并且不能保证在文件1之后会在2以下选择它。

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

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

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


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

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

如果打开一个TCP连接并将其用于所有后续HTTP请求,则可以减少请求的处理时间并减少负载。

HTTP协议包含一个称为HTTP保持活动或重新使用连接的功能。在这种情况下,一个TCP连接用于发送和接收许多HTTP请求和响应:



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

设置本身很简单,可用于大多数编程语言和环境。

以下是一些使用不同语言编写的示例的链接:


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

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

与通常的情况不同,当TCP连接在收到响应后关闭时,现在对于以下HTTP请求保持打开状态。

如果前端发送更多的后端请求会怎样?

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

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

在这种情况下不行。

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

由于以下所有请求都通过已经打开的TCP连接进行,因此不再调用iptables规则。

让我们看看它的外观

  1. 第一个子程序向服务发送请求:

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

  3. 后端容器之一将被选为目标地址:

  4. 该请求到达了壁炉。此时,将在两个容器之间建立永久的TCP连接:

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



结果,您获得了更快的响应和更高的带宽,但是失去了扩展后端的能力。

即使您的后端有两个Pod,且连接保持恒定,流量也始终会流向其中一个。

这个可以解决吗?

由于Kubernetes不知道如何平衡持久连接,因此此任务是您的责任。

服务是称为端点的一组IP地址和端口。

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

或者应用更复杂的平衡算法

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

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

这是它的外观

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

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

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

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



现在的问题是:此问题仅适用于HTTP保持活动状态吗?

客户端负载平衡


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
  • gRPC
  • 插座
  • AMQP

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

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

Kube-proxy和iptables旨在关闭Kubernetes的大多数标准部署方案。这是为了方便。

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

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

但是,当然,有些选项可能会有所帮助。

平衡Kubernetes中的长期连接


Kubernetes提供四种服务类型:

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

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

没有IP地址与无头服务关联,它仅提供一种机制,用于获取IP地址列表和关联的炉床(端点)。

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

ClusterIP服务是一项无头服务,其中包括一些附加功能: 

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

因此,您可以忽略kube-proxy,而直接使用从无头服务收到的端点列表来平衡应用程序中的负载。

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

如果您的应用程序已经被部署,那么这样的任务似乎是不可能的。但是,还有另一种选择。

服务网格将为您提供帮助


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

当应用程序启动时,它:

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

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

  1. 使用某种逻辑(例如轮询)选择可用的连接。
  2. 完成请求。

这些步骤适用于WebSockets,gRPC和AMQP。

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

但是,可以改用Istio或Linkerd之类的服务网格。

Service Mesh通过以下过程来补充您的应用程序:

  1. 自动搜索服务的IP地址。
  2. 检查连接,例如WebSockets和gRPC。
  3. 使用正确的协议平衡请求。

Service Mesh可以帮助管理群集中的流量,但是它占用大量资源。其他选项使用的是第三方库(例如Netflix Ribbon)或可编程代理(例如Envoy)。

如果您忽略平衡问题会怎样?


您不能使用负载平衡,也不会注意到任何更改。让我们看一些工作方案。

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

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



连接分布不均:可能有四个客户端连接到同一服务器,但是很有可能同时使用这两个服务器。

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

如果客户端较少,服务器更多,则可能无法充分利用您的资源,并且可能会出现瓶颈。

假设有两个客户端和五个服务器。充其量,将有两个永久连接到五分之二的服务器。

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



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

结论


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

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

这意味着您必须编写具有客户端平衡功能的应用程序。

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

还有什么要阅读的主题

  1. Kubernetes中的三个级别的自动缩放以及如何有效地使用它们。 
  2. Kubernetes本着盗版的精神,提供了一个实现模板
  3. Kubernetes .

All Articles