关注微信公众号《云原生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]
参考资料
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/。




