暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Kubernetes 上的 gRPC 负载平衡

云原生CTO 2021-11-16
951

CTO

 
 

 
 


 

Go
Rust
Python
Istio
containerd
CoreDNS
Envoy
etcd
Fluentd
Harbor
Helm
Jaeger
Kubernetes
Open Policy Agent
Prometheus
Rook
TiKV
TUF
Vitess
Argo
Buildpacks
CloudEvents
CNI
Contour
Cortex
CRI-O
Falco
Flux
gRPC
KubeEdge
Linkerd
NATS
Notary
OpenTracing
Operator Framework
SPIFFE
SPIRE
  Thanos





Kubernetes 上的 gRPC 负载平衡

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

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

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

为什么要负载均衡?

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

在开始详细了解Kubernetes
中的gRPC
负载平衡之前,让我们先了解一下负载平衡的好处。

负载平衡有很多好处,其中一些是:

  • 故障容忍度:如果您的一个副本出现故障,那么其他服务器可以为该请求提供服务。
  • 增加的可伸缩性:您可以跨多个服务器分配用户流量,从而增加可伸缩性。
  • 提高吞吐量:您可以通过将流量分布到不同的后端服务器来提高应用程序的吞吐量。
  • 部署没有缺点:使用滚动部署技术可以实现无停机部署。

负载平衡还有许多其他好处。你可以在这里阅读更多关于负载均衡器的内容。

https://www.appviewx.com/education-center/load-balancer/

gRPC的负载均衡选项

gRPC
中有两种类型的负载平衡选项——代理和客户端。

代理负载平衡

在代理负载均衡中,客户端将rpc
发送给LB (load Balancer)
代理。LB
RPC
调用分发到一个可用的后端服务器,该后端服务器实现为调用提供服务的实际逻辑。LB
跟踪每个后端的负载,并实现公平分配负载的算法。客户端本身并不知道后台服务器。客户端是不可信的。这种体系结构通常用于面向用户的服务,其中来自开放互联网的客户端可以连接到服务器

客户端负载均衡

在客户端负载平衡中,客户端知道许多后端服务器,并为每个RPC
选择一个后端服务器。如果客户端希望实现基于服务器负载报告的负载均衡算法。对于简单的部署,客户机可以在可用的服务器之间轮询请求。

有关gRPC
负载均衡选项的更多信息,可以查看文章gRPC
负载均衡。

https://grpc.io/blog/grpc-load-balancing/

与gRPC负载均衡相关的挑战

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

为了确认这个假设,让我们创建一个Kubernetes
应用程序。这个应用程序由-组成

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

创建服务端部署

要创建部署,请将以下代码保存在 YAML
文件中,例如 deployment-server.yaml
,然后运行命令kubectl apply -f deployment-server.yaml

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

这将创建一个具有三个副本的 gRPC
服务端。该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

您可以运行命令kubectl logs --follow grpc-server-<>
查看日志。

创建服务

要创建服务,请将以下代码保存在 YAML
文件中,例如service.yaml
,然后运行命令kubectl apply -f service.yaml

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

ClusterIP
服务提供负载平衡的 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
而言,情况并非如此,您很快就会看到。

创建客户端部署

要创建客户端部署,请将以下代码保存在 YAML
文件中,例如 deployment-client.yaml
,然后运行命令 kubectl apply -f deployment-client.yaml

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
环境变量指向DNS
服务的grpc-server-service
。在 gRPC
客户端上,通过将SERVER_HOST
(serverHost
)传递为:

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

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

使用无头服务的客户端负载平衡

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

什么是无头服务?

幸运的是,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

将无头服务定义为:

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

要使服务成为无头服务,您唯一需要更改的.spec.clusterIP
字段是将字段设置为None

验证 DNS

要确认无头服务的 DNS
,请创建一个镜像为tutum/dnsutils
pod

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

然后运行命令

kubectl exec dnsutils -- nslookup grpc-server-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

如您所见,无头服务解析为所有通过 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

配置客户端 客户端应用程序剩下的唯一变化是指向带有服务器 pods
端口的无头服务,如下所示:

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
现在指向无头服务 grpc-server-service
和服务器端口 8001
。您还可以使用 SERVER_HOST
作为 FQDN

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

如果您通过首先删除客户端部署来再次部署客户端:

kubectl delete deployment.apps/grpc-client

然后再次部署客户端:

kubectl apply -f deployment-client.yaml

您可以看到 pod
打印的日志。

代码示例

本文的工作代码示例列在GitHub
上 。您可以使用kind
在本地 Kubernetes
集群上运行代码。

https://github.com/techdozo/grpc-lb

https://techdozo.dev/2021/getting-started-with-kind-quick-start-a-multi-node-local-kubernetes-cluster/

概括

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

进一步阅读[1]

进一步阅读[2]

参考资料

[1]

gRPC 负载均衡: https://grpc.io/blog/grpc-load-balancing/。

[2]

Kubernetes 上的 gRPC 负载均衡: https://kubernetes.io/blog/2018/11/07/grpc-load-balancing-on-kubernetes-without-tears/。


文章转载自云原生CTO,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论