在Kubernetes上gRPC是如何通过Headless Service负载均衡
@ 归零 | 星期一,十二月 20 日,2021 年 | 3 分钟阅读 | 更新于 星期一,十二月 20 日,2021 年

gRPC 是用于进程间通信的最流行的现代 RPC 框架之一。 它是微服务架构的绝佳选择。 而且,毫无疑问,部署微服务应用程序最流行的方式是 Kubernetes。

Kubernetes部署可以具有相同的后端实例,为许多客户端请求提供服务。 Kubernetes 的 ClusterIP 服务提供负载均衡的IP地址。 但是这种默认的负载平衡不适用于gRPC开箱即用。

如果你使用gRPC并在 Kubernetes 上部署了许多后端服务,那么本文档适合你。

为什么负载均衡?

一个大规模的部署有许多相同的后端实例和许多客户。每个后端服务器都有一定的容量。负载均衡用于将客户端的负载分配到可用的服务器上。

在你开始详细了解Kubernetes中的gRPC负载均衡之前,让我们试着了解一下负载均衡的好处是什么。 负载均衡有很多好处,其中一些是。

  • 容错:如果你的副本之一出现故障,则其他服务器可以为请求提供服务。
  • 提高可扩展性:你可以将用户流量分布在多台服务器上,从而提高可扩展性。
  • 提高吞吐量:你可以通过在各种后端服务器之间分配流量来提高应用程序的吞吐量。
  • 无停机部署:你可以使用滚动部署技术实现无停机部署。

gRPC中的负载均衡选项

gRPC 中有两种类型的负载均衡:

  • 代理
  • 客户端

代理负载均衡

在代理负载平衡中,客户端向负载平衡器(LB)代理发出 RPC。 LB 将 RPC 调用分发到可用的后端服务器之一,这些后端服务器实现了为调用提供服务的实际逻辑。 LB 跟踪每个后端的负载并实现公平分配负载的算法。客户端本身不知道后端服务器。客户可能不受信任。这种架构通常用于面向用户的服务,其中来自开放互联网的客户端可以连接到服务器

客户端负载均衡

在客户端负载均衡中,客户端知道许多后端服务器,并为每个 RPC 选择一个使用。如果客户端希望它可以根据来自服务器的负载报告来实现负载平衡算法。对于简单的部署,客户端可以在可用服务器之间循环请求。 有关 gRPC 负载平衡选项的更多信息,您可以查看文章 gRPC 负载平衡。

gRPC负载平衡相关的挑战

gRPC 适用于 HTTP/2。 HHTP/2 上的 TCP 连接是长期存在的。 单个连接可以多路复用多个请求。 这减少了与连接管理相关的开销。 但这也意味着连接级负载均衡不是很有用。 Kubernetes 中的默认负载均衡基于连接级负载均衡。 因此,Kubernetes 默认负载平衡不适用于 gRPC。

为了证实这个假设,让我们创建一个 Kubernetes 应用程序。 此应用程序包括:

  • 服务器pod:Kubernetes部署,带有三个gRPC服务器pod。
  • 客户端pod:Kubernetes部署,带有一个gRPC客户端pod。
  • Service:一个 ClusterIP服务,它选择所有服务器pod。

image

创建服务端 Deployment

将以下代码保存为deployment-server.yaml

cat <<EOF | kubectl apply -f - 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-server
  labels:
    app: grpc-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: grpc-server
  template:
    metadata:
      labels:
        app: grpc-server
    spec:
      containers:
        - name: grpc-server
          image: techdozo/grpc-lb-server:1.0.0
EOF

以上Deployment创建了一个包含三个副本gRPC服务,服务端口为8001

检查pod是否创建成功

$ kubectl get pods

NAME                           READY   STATUS    RESTARTS   AGE
grpc-server-6c9cd849-5pdbr     1/1     Running   0          1m
grpc-server-6c9cd849-86z7m     1/1     Running   0          1m
grpc-server-6c9cd849-mw9sb     1/1     Running   0          1m

创建 Service

cat <<EOF | kubectl apply -f -

apiVersion: v1
kind: Service
metadata:
  name: grpc-server-service
spec:
  type: ClusterIP
  selector:
    app: grpc-server
  ports:
    - port: 80
      targetPort: 8001

ClusterIP 类型Service提供一个负载均衡的IP地址,它在通过标签选择器匹配的pod端点之间负载平衡流量

Name:              grpc-server-service
Namespace:         default      
Selector:          app=grpc-server
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.28.234
IPs:               10.96.28.234
Port:              <unset>  80/TCP
TargetPort:        8001/TCP
Endpoints:         10.244.0.11:8001,10.244.0.12:8001,10.244.0.13:8001
Session Affinity:  None

如上所示,Pod的IP地址是10.244.0.11:8001,10.244.0.12:8001,10.244.0.13:8001。 如果客户端在端口 80 上调用服务,那么它将跨端点(Pod的IP地址)对调用进行负载平衡。 但这对于 gRPC 来说并非如此。

创建客户端Deployment

cat <<EOF | kubectl apply -f -

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-client
  labels:
    app: grpc-client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grpc-client
  template:
    metadata:
      labels:
        app: grpc-client
    spec:
      containers:
        - name: grpc-client
          image: techdozo/grpc-lb-client:1.0.0
          env:
            - name: SERVER_HOST
              value: grpc-server-service:80

gRPC 客户端应用程序在启动时使用一个通道在 10 个并发线程中对服务器进行 1,000,000 次调用。 SERVER_HOST 环境变量指向服务 grpc-server-service 的 DNS。 在 gRPC 客户端上,通过将 SERVER_HOST ( serverHost) 传递为:

ManagedChannelBuilder.forTarget(serverHost) .defaultLoadBalancingPolicy("round_robin") .usePlaintext() .build();

如果你检查服务器日志,会注意到所有客户端调用仅由一个服务器pod提供服务。

使用Headless Service的客户端负载平衡

可以使用Kubernetes Headless Service进行客户端循环负载平衡。 这种简单的负载平衡与gRPC一起开箱即用。 缺点是它没有考虑到服务器上的负载。

什么是 Headless Service?

幸运的是,Kubernetes 允许客户端通过 DNS 查找来发现 pod IP。通常,当您对服务执行 DNS 查找时,DNS 服务器会返回一个 IP — 服务的集群 IP。但是,如果您告诉 Kubernetes 您的服务不需要集群 IP(您可以通过在服务规范中将 clusterIP 字段设置为 None 来实现),DNS 服务器将返回 pod IP 而不是单个服务 IP。 DNS 服务器将返回服务的多个 A 记录,而不是返回单个 DNS A 记录,每个记录都指向当时支持该服务的单个 Pod 的 IP。因此,客户端可以进行简单的 DNS A 记录查找并获取属于服务的所有 pod 的 IP。然后客户端可以使用该信息连接到其中一个、多个或全部。 将服务规范中的 clusterIP 字段设置为 None 会使服务无头,因为 Kubernetes 不会为其分配集群 IP,客户端可以通过该 IP 连接到支持它的 pod。

                                  <<Kubernetes in Action>> — Marko Lukša

如下定义一个headless service:

apiVersion: v1
kind: Service
metadata:
  name: grpc-server-service
spec:
  clusterIP: None
  selector:
    app: grpc-server
  ports:
    - port: 80
      targetPort: 8001

要使Service成为Headless Service,需要更改的唯一字段是将 .spec.clusterIP 字段设置为 None。

验证DNS

要确认Headless Service的 DNS,请创建一个image为tutum/dnsutils的pod:

kubectl run dnsutils --image=tutum/dnsutils --command --sleep infinity

然后运行命令

kubectl exec dnsutils -- nslookup grpc-server-service

返回Headless service的FQDN如下:


Server:         10.96.0.10
Address:        10.96.0.10#53
Name:   grpc-server-service.default.svc.cluster.local
Address: 10.244.0.22
Name:   grpc-server-service.default.svc.cluster.local
Address: 10.244.0.20
Name:   grpc-server-service.default.svc.cluster.local
Address: 10.244.0.21

如您所见,Headless service解析为所有通过服务连接的Pod的IP地址。 将此与非无头服务返回的输出进行对比。

Server: 10.96.0.10 Address: 10.96.0.10#53 Name: grpc-server-service.default.svc.cluster.local Address: 10.96.158.232

配置客户端

客户端应用程序剩下的唯一变化是指向带有服务器Pod端口的Headless Service,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-client
  labels:
    app: grpc-client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grpc-client
  template:
    metadata:
      labels:
        app: grpc-client
    spec:
      containers:
        - name: grpc-client
          image: techdozo/grpc-lb-client:1.0.0
          env:
            - name: SERVER_HOST
              value: grpc-server-service:8001

注意: SERVER_HOST 现在指向Headless Service grpc-server-service和端口8001.

你也可以配置SERVER_HOST为FQDN如:

name: SERVER_HOST 
value: "grpc-server-service.default.svc.cluster.local:8001"

重新部署client端的Deployment

kubectl delete deployment.apps/grpc-client
kubectl apply -f deployment-client.yaml

可以看到服务的所有Pod打印了日志。

总结

gRPC中有两种可用的负载平衡选项,代理和客户端。 由于gRPC连接是长期存在的,Kubernetes 的默认连接级负载平衡不适用于gRPC。 Kubernetes Headless Service是一种可以实现负载均衡的机制, Headless Service的DNS被解析为Pod的IP。

原文链接:https://pankaj02.medium.com/grpc-load-balancing-on-kubernetes-using-headless-service-13bce0423785

© 2014 - 2022 Lionel's Blog

Powered by Hugo with theme Dream.