写点什么

在 Kubernetes 中实现 gRPC 流量负载均衡

  • 2023-10-27
    福建
  • 本文字数:3153 字

    阅读完需:约 10 分钟

在Kubernetes中实现gRPC流量负载均衡

在尝试将 gRPC 服务部署到 Kubernetes 集群中时,一些用户(包括我)面临的挑战之一是实现适当的负载均衡。在深入了解如何平衡 gRPC 的方式之前,我们首先需要回答一个问题,即为什么需要平衡流量,如果 Kubernetes 已经完成了这项工作。


本文关注于 Kubernetes 和 Golang。

为什么在 Kubernetes 中无法适当地平衡 gRPC 流量?


之所以难以平衡 gRPC 流量的主要原因是人们将 gRPC 视为 HTTP,这就是问题的根源。设计上它们是不同的,虽然 HTTP 为每个请求创建和关闭连接,但 gRPC 使用 HTTP2 协议,在长时间的 TCP 连接上运行,使得平衡更加困难,因为多个请求通过同一个连接进行多路复用。然而,这并不是配置 gRPC 服务在 Kubernetes 中出现平衡问题的唯一原因,以下是一些常见的错误配置:


  • 错误的 gRPC 客户端配置

  • 错误的 Kubernetes 服务配置

错误的 gRPC 客户端配置


设置 gRPC 客户端时常见的情况是选择默认配置,这对于 1-1 连接类型完全有效,但对于生产环境来说并不如我们所希望的有效。这背后的原因是因为默认的 gRPC 客户端提供了使用简单的 IP/DNS 记录连接的可能性,这只会创建一个与目标服务的连接。


因此,需要为与多个服务器建立连接进行不同的设置,将连接类型从 1-1 转换为 1-N。


默认设置:

func main(){  conn, err := grpc.Dial("my-domain:50051", grpc.WithInsecure())  if err != nil {    log.Fatalf("error connecting with gRPC server: %v", err)  }    defer conn.Close()  cli := test.NewTestServiceClient(conn)  rs, err := cli.DoSomething(context.Background(), ...)  .  .  .}
复制代码


新的设置:

func main(){  conn, err := grpc.Dial("my-domain:50051", grpc.WithInsecure())  if err != nil {    log.Fatalf("error connecting with gRPC server: %v", err)  }    defer conn.Close()  cli := test.NewTestServiceClient(conn)  rs, err := cli.DoSomething(context.Background(), ...)  .  .  .}
复制代码


这里有两个重要的更改需要注意:

  • 地址: 最终解析的地址将类似于 dns:///my-domain:50051,之所以使用这种格式是因为 Dial 函数允许我们使用由 Scheme://Authority/Endpoint 组成的目标,而在我们的情况下,我跳过了 Authority。因此,首先我添加了 dns 作为方案,因为我希望解析一个域并持续观察其更改,解析器选项有透传(默认)、dns 和手动,更多详情请参阅这里。

  • 负载均衡器选项: 如果我们的客户端现在连接到多个服务器,那么我们的 gRPC 客户端可以根据所选择的负载均衡算法平衡请求。


总结一下,我们的 gRPC 客户端现在能够创建不同的连接,前提是域名解析为多个 A 或 AAAA 记录,而且不仅如此,现在还能够将请求均匀地分配到不同的服务器。


现在让我们看看如何让它与 Kubernetes 一起工作的缺失部分。

错误的 Kubernetes 服务配置


在 Kubernetes 中创建服务非常简单,我们只需要定义服务名称、端口和选择器,以便服务可以动态地将 Pod 分组并自动平衡请求,如下所示:

apiVersion: v1kind: Servicemetadata:  name: my-servicespec:  selector:    app: my-app  ports:    - name: grpc      protocol: TCP      port: 50051      targetPort: 50051
复制代码


那么,对于先前的设置,问题在于默认的 Kubernetes 服务只创建了一个 DNS 记录,链接到单个 IP。因此,当您执行类似 nslookup my-service.{namespace}.svc.cluster.local 的操作时,返回的是一个单个 IP,这使得在常见的 gRPC 实现中连接图看起来像这样:



例如,使用默认的 Kubernetes 服务的连接图:


绿线表示与客户端的活动连接,黄色表示未活动的 Pod。客户端与 Kubernetes 服务创建了持久连接,同时服务也与其中一个 Pod 创建了连接,但这并不意味着服务与其余的 Pod 没有连接。


让我们使用一个无头服务来解决这个问题:

apiVersion: v1kind: Servicemetadata:  name: my-servicespec: clusterIP: None **this is the key*** selector:    app: my-app  ports:    - name: grpc      protocol: TCP      port: 50051      targetPort: 50051
复制代码


创建了无头服务后,nslookup 看起来有些不同,现在它返回与之关联的记录(将 Pod 的 IP 分组到服务中),从而使 gRPC 客户端更好地了解需要连接的服务器数量。


现在您已经看到了 gRPC 客户端的配置,您必须知道为什么 Kubernetes 服务返回与一组 Pod 关联的 IP 非常重要。原因是客户端可以看到所有需要建立连接的服务器。在这一点上,您可能已经意识到了一个注意事项,即平衡的责任现在在客户端部分,而不在 Kubernetes 的一侧。我们现在需要从 Kubernetes 那里得到的主要任务是保持与服务关联的 Pod 列表的最新状态。



例如,在具有无头 Kubernetes 服务的连接图中,可以看到连接发生了一些变化,现在我们不通过 Kubernetes 服务来访问 Pod,而是使用 Kubernetes 服务来检索与域名关联的 Pod 列表,然后直接与 Pod 建立连接。但是不要因为直接连接到 Pod 而感到惊慌,因为我们在客户端中设置了 DNS 解析器类型,该解析器将持续监视与无头服务的更改,并将与可用的 Pod 保持最新的连接。

为什么不使用服务网格?


如果可以的话,请使用服务网格,因为在服务网格中,所有这些设置都是透明的,而且最重要的是它是与编程语言无关的。关键区别在于服务网格利用了 Sidecar 模式和控制平面来编排入站和出站流量,还可以看到所有网络和流量类型(HTTP、TCP 等),从而能够正确平衡请求。简而言之,如果您不使用服务网格,那么您需要直接从每个客户端连接到多个服务器,或者连接到一个 L7 代理来帮助平衡请求。

附加信息


尽管先前的设置可以工作,但我在尝试在 alpine Linux 映像中进行 Pod 轮换或扩展时重新平衡连接时遇到了问题。经过一些研究,我意识到我并不是唯一遇到这种问题的人,可以查看这里和这里的一些相关的 GitHub 问题。这就是为什么我决定创建自己的解析器的原因,您可以在这里查看我创建的自定义解析器,我创建的自定义解析器非常基础,但现在可以正常工作,gRPC 客户端现在可以再次监听域名的更改,我还为该库添加了一个可配置的监听器,它每隔一段时间查找域名并更新提供给 gRPC 连接管理器的 IP 集合,如果您想贡献,欢迎加入。


另一方面,因为我想深入了解,所以我决定创建自己的 gRPC 代理(我也学到了很多东西),利用了 gRPC 的 http2 基础,我可以创建一个代理,而无需更改 proto 负载消息或甚至不知道 proto 文件的定义(还使用了前面提到的自定义解析器)。


最后,我想说的是,如果您的 gRPC 客户端需要与许多服务器连接,我强烈建议使用代理作为平衡的机制,因为将这个机制放在主应用程序中将增加复杂性和资源消耗,尝试保持许多打开的连接并重新平衡它们,想象一下,如果最终的平衡在应用程序中,您将有一个与 N 个服务器连接的实例(1-N),但是使用代理,您将有一个与 M 个代理连接到 N 个服务器的实例(1-M-N),其中 M<N,因为每个代理实例可以处理与不同服务器的许多连接。

最后,介绍一款软件开发工具


JNPF 开发平台,很多人都用过它,它是功能的集大成者,任何信息化系统都可以基于它开发出来。原理是将开发过程中某些重复出现的场景、流程,具象化成一个个组件、api、数据库接口,避免了重复造轮子。因而极大的提高了程序员的生产效率。


官网:https://www.jnpfsoft.com?infoq ,如果你有闲暇时间,可以做个知识拓展。


这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架,采用业内领先的 SpringBoot 微服务架构、支持 SpringCloud 模式。前后端封装了上千个常用类,方便扩展;采用微服务、前后端分离架构,集成了代码生成器,支持前后端业务代码生成,满足快速开发;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3,平台即可私有化部署,也支持 K8S 部署。


为了支撑更高技术要求的应用开发,从数据库建模、Web API 构建到页面设计,与传统软件开发几乎没有差异,只是通过低代码可视化模式,减少了构建“增删改查”功能的重复劳动。

发布于: 刚刚阅读数: 3
用户头像

IT领域从业者 分享见解 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
在Kubernetes中实现gRPC流量负载均衡_golang_树上有只程序猿_InfoQ写作社区