一文搞懂 Kubernetes 的负载均衡? 我们学习了 k8s 的短连接负载均衡如何实现,接下来我们来学习长连接的负载均衡如何在 k8s 中实现
长连接在 Kubernetes 中无法开箱即用
从前端到后端的每个 HTTP 请求都会打开和关闭一个新的 TCP 连接。 如果前端每秒向后端发出 100 个 HTTP 请求,则该秒会打开和关闭 100 个不同的 TCP 连接。
如果打开 TCP 连接并将其重新用于任何后续 HTTP 请求,则可以改善延迟并节省资源。
HTTP 协议具有称为 HTTP keep-alive 或 HTTP 连接重用的功能,它使用单个 TCP 连接发送和接收多个 HTTP 请求和响应。
这也造成了我们用浏览器访问NodePort的时候一直打到一个Pod上,因为HTTP 使用了Keep-alive,如果用curl则每次都会选择一个新的Pod去提供服务,则能够实现负载均衡
它不能开箱即用;如果需要使用长连接的话应用程序需要做相应的修改
我们将 keep-alive 与 Kubernetes Service结合使用时会发生什么?假设前端和后端都支持keep-alive。您有一个前端实例和三个后端副本。前端向后端发出第一个请求并打开 TCP 连接。请求到达 Service,并选择其中一个 Pod 作为目的地。后端Pod回复,前端接收响应。但它不会关闭 TCP 连接,而是为后续 HTTP 请求保持打开状态。
当前端发出更多请求时会发生什么? 它们被发送到同一个 Pod。
iptables 不是应该吗分配流量?这个理解并没有错,第一次调用 iptables 规则,三个 Pod 其中的一个被选为目的地,这个时候会有iptables 进行负载均衡。
由于所有后续请求都通过同一 TCP 连接进行传输,因此不再调用 iptables。所以自然就没有了负载均衡。

因此,我们虽然通过长连接实现了更好的延迟和吞吐量,但失去了扩展后端的能力。
即使我们有两个可以接收来自前端 Pod 的请求的后端 Pod,也只有一个被主动使用。
由于 Kubernetes 不知道如何对持久连接进行负载平衡,因此需要我们自己来实现长连接的负载均衡。
Service是 IP 地址和端口的集合------通常称为Endpoints (端点)。
我们的应用程序可以从Service获取Endpoints 的列表并决定如何分发请求。我们可以打开与每个 Pod 的持久连接并向它们发出循环请求或者可以实现更复杂的负载平衡算法。
执行负载均衡的客户端代码应遵循以下逻辑:
-
从Service中获取Endpoints的列表
-
对于每个人,打开一个连接并保持打开状态
-
当您需要发出请求时,选择一个长连接
-
定期刷新Endpoints列表并删除或添加新连接

客户端如何实现长连接负载均衡
HTTP 并不是唯一可以从长连接中受益的协议。
如果我们的应用程序使用数据库,我们希望每次CURD 的时候,连接都不会打开和关闭,TCP 连接建立一次并保持打开状态,这就是我们平时经常接触到的数据库连接池的问题。
如果我们的数据库使用服务部署在 Kubernetes 中,可能会遇到与上一个示例相同的问题。数据库中有一个副本的利用率高于其他副本。Kube-proxy 和 Kubernetes 没有办法帮我们做数据库连接的负载均衡。我们要自己实现数据库的负载均衡。
根据用于连接数据库的库,我们可能有不同的选项。下面我们以 Go 为例,连接到Mysql数据库:
go
package orm
import (
"database/sql"
"fmt"
"sync"
"sync/atomic"
"time"
)
var (
dbEndpoints = make([]*sql.DB, 0)
index atomic.Int64
initDBEndpointsOnce sync.Once
)
func GetDB() (*sql.DB, error) {
initDBEndpointsOnce.Do(func() {
// 初始化所有连接
initDBEndpoints()
go updateEndpoints()
})
// 简单的轮询,这里可以实现更复杂的负载均衡算法
i := int(index.Add(1)) % len(dbEndpoints)
return dbEndpoints[i], nil
}
func updateEndpoints() {
for {
select {
case <-time.After(time.Second):
// 循环查看连接是否正常,也可以通过监听配置中心热更新数据库连接
}
}
}
func initDBEndpoints() {
endpoints := []string{"endpoint1", "endpoint2", "endpoint3"}
for _, endpoint := range endpoints {
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s)/%s", "username", "password", endpoint, "dbname")
poolDB, err := sql.Open("mysql", dataSourceName)
if err != nil {
// 异常处理
}
poolDB.SetMaxOpenConns(10)
poolDB.SetMaxIdleConns(5)
dbEndpoints = append(dbEndpoints, poolDB)
}
}
其他几种协议都在 TCP 长连接上工作。
- Websocket
- HTTP/2
- gRPC
- RSockets
- AMQP
这些协议应用非常广泛,负载平衡有没有标准答案呢?逻辑必须移到客户端吗?Kubernetes 有原生解决方案吗?
服务网格(Service Mesh)的引入就是用来解决这些常见的流量治理问题,通过 Sidecar 的方式,将流量治理从应用程序中再次进行剥离。
Service Mesh 服务网格对流量进行治理
服务网格通过Sidecar能够实现以下功能:
- 自动发现IP地址服务
- 检查 WebSockets 和 gRPC 等连接
- 使用正确的协议负载平衡请求
- 服务网格帮助管理集群内的流量
如果我们忽略负载均衡,则需要考虑以下几种情况。
1.客户端多于服务器
想象一下,我们有五个客户端打开与两台服务器的持久连接。
即使没有负载平衡,两台服务器也可能都会被使用。
客户端多于服务器连接可能分布不均匀(可能有四个最终连接到同一服务器),但总体而言,两个服务器很有可能都被利用。
2.客户端少于服务器
则可能会出现一些未充分利用的资源和潜在的瓶颈。
有两个客户端和五个服务器,最多打开两个到两台服务器的持久连接,其余服务器根本没有使用。
3.服务器多于客户端
如果两台服务器无法处理客户端生成的流量,则水平扩展将无济于事。因为流量只会一直被发送到特定的服务端。
所以忽略负载均衡的做法并不可取,并且由于它与业务可以解耦开,所以我们通过 Service Mesh对流量进行代理,然后再提供可供业务配置的规则,能够帮助我们更好的管理流量,也能充分的增加应用程序的水平可扩展性,也能最大程度的利用我们服务器的资源。
接下来我们看一个Service Mesh 的具体实现 ------ Istio.
Istio
Istio 的流量路由规则可以让您很容易的控制服务之间的流量和 API 调用。 Istio 简化了服务级别属性的配置,比如熔断器、超时和重试,并且能轻松的设置重要的任务, 如 A/B 测试、金丝雀发布、基于流量百分比切分的分阶段发布等。它还提供了开箱即用的故障恢复特性, 有助于增强应用的健壮性,从而更好地应对被依赖的服务或网络发生故障的情况。
这是官方文档给出的解释,那么它是如何实现服务间流量的管理的呢?
主要是所有流量的发送和接收都会经过 Envoy,由Envoy进行代理,有点类似AOP(切面编程)。
istio 也是通过 iptables 对流量进行拦截,然后交给 Sidecar Envoy 进行处理,处理完后再看是否要转发给应用程序。因为流量都被代理了,所以不仅仅可以在这中间做简单的负载均衡,也可以实现复杂逻辑的流量转发,如 AB 测试、熔断等功能。
k8s 和 istio在负载均衡上有什么区别呢?
k8s的负载均衡:

Istio的负载均衡:

从上面的图中可以看到,k8s是在节点上对流量进行负载均衡,而istio 则是在Pod内部实现了负载均衡,所以 它们两者两者并不是互相代替的关系,而是istio是 k8s 服务间流量治理的一个补充。 我们将istio和 k8s合成一张图来看:

接下来我们看一下Istio是如何做到 Envoy流量的治理的。
首先对进入Pod的inbound流量通过 PREROUTING 链进行拦截,通过iptables 将流量拦截后给 Envoy进行流量治理,最终再交由应用程序。

转发给应用程序处理完后,再接着通过iptables OUTPUTING进行拦截,看是否要对数据包进行响应,对响应的内容数据包进行治理。最终POSTROUTING 处理完之后再查找合适的网卡将数据包发送出去。

小结
我们来快速回顾一下从本文中学到的内容
- 在k8s中长连接没办法开箱即用,我们可以通过客户端实现长连接的负载均衡。
- 通过服务网格来将流量的治理与应用程序的逻辑进行剥离,并学习了istio 中的实现。
Refrence
- Go实现长连接:okanexe.medium.com/the-complet...
- istio 中文文档:istio.io/latest/zh/d...