GKE 内部负载均衡器不会在 gRPC 服务器之间分配负载

GKE Internal Load Balancer does not distribute load between gRPC servers

我有一个 API 最近开始接收更多流量,大约 1.5 倍。这也导致延迟加倍:

这让我感到惊讶,因为我已经设置了两个节点的自动缩放和 pods 以及 GKE 内部负载平衡。

我的外部 API 将请求传递给使用大量 CPU 的内部服务器。查看我的 VM 实例,似乎所有流量都发送到我的两个 VM 实例之一(a.k.a。Kubernetes 节点):

有了负载平衡,我原以为 CPU 使用量会在节点之间更均匀地分配。

查看我的部署,第一个节点上有一个 pod:

和第二个节点上的两个pods:

我的服务配置:

$ kubectl describe service model-service
Name:                     model-service
Namespace:                default
Labels:                   app=model-server
Annotations:              networking.gke.io/load-balancer-type: Internal
Selector:                 app=model-server
Type:                     LoadBalancer
IP Families:              <none>
IP:                       10.3.249.180
IPs:                      10.3.249.180
LoadBalancer Ingress:     10.128.0.18
Port:                     rest-api  8501/TCP
TargetPort:               8501/TCP
NodePort:                 rest-api  30406/TCP
Endpoints:                10.0.0.145:8501,10.0.0.152:8501,10.0.1.135:8501
Port:                     grpc-api  8500/TCP
TargetPort:               8500/TCP
NodePort:                 grpc-api  31336/TCP
Endpoints:                10.0.0.145:8500,10.0.0.152:8500,10.0.1.135:8500
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason               Age                  From                Message
  ----    ------               ----                 ----                -------
  Normal  UpdatedLoadBalancer  6m30s (x2 over 28m)  service-controller  Updated load balancer with new hosts

Kubernetes 启动新 pod 的事实似乎是 Kubernetes 自动缩放正在运行的线索。但是第二台虚拟机上的 pods 没有收到任何流量。如何让GKE的负载均衡更均匀?

11 月 2 日更新:

Goli 的回答让我认为它与模型服务的设置有关。该服务公开了 REST API 和 GRPC API,但 GRPC API 是接收流量的服务。

我的服务有对应的转发规则:

$ gcloud compute forwarding-rules list --filter="loadBalancingScheme=INTERNAL"
NAME                              REGION       IP_ADDRESS   IP_PROTOCOL  TARGET
aab8065908ed4474fb1212c7bd01d1c1  us-central1  10.128.0.18  TCP          us-central1/backendServices/aab8065908ed4474fb1212c7bd01d1c1

指向后端服务:

$ gcloud compute backend-services describe aab8065908ed4474fb1212c7bd01d1c1
backends:
- balancingMode: CONNECTION
  group: https://www.googleapis.com/compute/v1/projects/questions-279902/zones/us-central1-a/instanceGroups/k8s-ig--42ce3e0a56e1558c
connectionDraining:
  drainingTimeoutSec: 0
creationTimestamp: '2021-02-21T20:45:33.505-08:00'
description: '{"kubernetes.io/service-name":"default/model-service"}'
fingerprint: lA2-fz1kYug=
healthChecks:
- https://www.googleapis.com/compute/v1/projects/questions-279902/global/healthChecks/k8s-42ce3e0a56e1558c-node
id: '2651722917806508034'
kind: compute#backendService
loadBalancingScheme: INTERNAL
name: aab8065908ed4474fb1212c7bd01d1c1
protocol: TCP
region: https://www.googleapis.com/compute/v1/projects/questions-279902/regions/us-central1
selfLink: https://www.googleapis.com/compute/v1/projects/questions-279902/regions/us-central1/backendServices/aab8065908ed4474fb1212c7bd01d1c1
sessionAffinity: NONE
timeoutSec: 30

其中进行了健康检查:

$ gcloud compute health-checks describe k8s-42ce3e0a56e1558c-node                                          
checkIntervalSec: 8
creationTimestamp: '2021-02-21T20:45:18.913-08:00'
description: ''
healthyThreshold: 1
httpHealthCheck:
  host: ''
  port: 10256
  proxyHeader: NONE
  requestPath: /healthz
id: '7949377052344223793'
kind: compute#healthCheck
logConfig:
  enable: true
name: k8s-42ce3e0a56e1558c-node
selfLink: https://www.googleapis.com/compute/v1/projects/questions-279902/global/healthChecks/k8s-42ce3e0a56e1558c-node
timeoutSec: 1
type: HTTP
unhealthyThreshold: 3

我的列表 pods:

kubectl get pods
NAME                                       READY   STATUS    RESTARTS   AGE
api-server-deployment-6747f9c484-6srjb     2/2     Running   3          3d22h
label-server-deployment-6f8494cb6f-79g9w   2/2     Running   4          38d
model-server-deployment-55c947cf5f-nvcpw   0/1     Evicted   0          22d
model-server-deployment-55c947cf5f-q8tl7   0/1     Evicted   0          18d
model-server-deployment-766946bc4f-8q298   1/1     Running   0          4d5h
model-server-deployment-766946bc4f-hvwc9   0/1     Evicted   0          6d15h
model-server-deployment-766946bc4f-k4ktk   1/1     Running   0          7h3m
model-server-deployment-766946bc4f-kk7hs   1/1     Running   0          9h
model-server-deployment-766946bc4f-tw2wn   0/1     Evicted   0          7d15h
model-server-deployment-7f579d459d-52j5f   0/1     Evicted   0          35d
model-server-deployment-7f579d459d-bpk77   0/1     Evicted   0          29d
model-server-deployment-7f579d459d-cs8rg   0/1     Evicted   0          37d

我如何 A) 确认此健康检查实际上显示 2/3 后端不健康? B) 配置运行状况检查以将流量发送到我的所有后端?

11 月 5 日更新:

在发现过去有几个 pods 由于 RAM 太少而被驱逐后,我将 pods 迁移到一个新的节点池。旧的节点池虚拟机有 4 CPU 和 4GB 内存,新的有 2 CPU 和 8GB 内存。这似乎已经解决了 eviction/memory 问题,但负载均衡器仍然一次只向一个 pod 发送流量。

节点 1 上的 Pod 1:

节点 2 上的容器 2:

负载均衡器似乎根本没有拆分流量,而只是随机选择一个 GRPC 模型服务器并将 100% 的流量发送到那里。是否有一些我错过的配置导致了这种行为?这跟我用GRPC有关系吗?

Google 云提供健康检查以确定后端是否响应 traffic.Health 检查以可配置的方式定期连接到后端。每次连接尝试都称为探测。 Google云记录每次探测的成功或失败。

根据连续成功或失败的可配置数量的探测,为每个后端计算整体健康状态。成功响应配置次数的后端被认为是健康的。

未能在单独配置的次数内成功响应的后端是不健康的。

每个后端的整体健康状况决定了接收新请求或连接的资格。因此,实例未收到请求的可能性之一可能是您的实例不健康。请参阅此文档了解 creating health checks .

您可以配置定义成功探测的条件。 How health checks work.

部分对此进行了详细讨论

编辑1:

由于资源不足或节点故障,Pod被驱逐出节点。如果一个节点出现故障,该节点上的 Pods 会自动安排删除。

所以要知道 pods 被驱逐的确切原因 运行 kubectl describe pod <pod name> 并查找此 pod 的节点名称。接下来是 kubectl describe node <node-name>,它将显示节点在 Conditions: section.

下达到的资源上限类型

根据我的经验,当主机节点 运行 磁盘 space 不足时会发生这种情况。 此外,在启动 pod 后,您应该 运行 kubectl logs <pod-name> -f 并查看日志以获取更多详细信息。

有关 eviction 的更多信息,请参阅此文档。

原来答案是您无法使用 GKE 负载均衡器对 gRPC 请求进行负载均衡。

每次形成新的 TCP 连接时,GKE 负载均衡器(以及 Kubernetes 的默认负载均衡器)都会选择一个新的后端。对于常规的 HTTP 1.1 请求,每个请求都会获得一个新的 TCP 连接,并且负载均衡器工作正常。对于 gRPC(基于 HTTP 2),TCP 连接仅建立一次,所有请求都在同一连接上多路复用。

更多详细信息在此 blog post

要启用 gRPC 负载平衡,我必须:

  1. 安装Linkerd
curl -fsL https://run.linkerd.io/install | sh
linkerd install | kubectl apply -f -
  1. 接收和发送中注入Linkerd代理pods:

kubectl apply -f api_server_deployment.yaml
kubectl apply -f model_server_deployment.yaml
  1. 在意识到 Linkerd 不能与 GKE 负载均衡器一起工作后,我改为将接收部署公开为 ClusterIP 服务。
kubectl expose deployment/model-server-deployment
  1. 将 gRPC 客户端指向我刚刚创建的 ClusterIP 服务 IP 地址,并重新部署客户端。
kubectl apply -f api_server_deployment.yaml