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 负载平衡,我必须:
- 安装Linkerd
curl -fsL https://run.linkerd.io/install | sh
linkerd install | kubectl apply -f -
- 在接收和发送中注入Linkerd代理pods:
kubectl apply -f api_server_deployment.yaml
kubectl apply -f model_server_deployment.yaml
- 在意识到 Linkerd 不能与 GKE 负载均衡器一起工作后,我改为将接收部署公开为 ClusterIP 服务。
kubectl expose deployment/model-server-deployment
- 将 gRPC 客户端指向我刚刚创建的 ClusterIP 服务 IP 地址,并重新部署客户端。
kubectl apply -f api_server_deployment.yaml
我有一个 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 负载平衡,我必须:
- 安装Linkerd
curl -fsL https://run.linkerd.io/install | sh
linkerd install | kubectl apply -f -
- 在接收和发送中注入Linkerd代理pods:
kubectl apply -f api_server_deployment.yaml
kubectl apply -f model_server_deployment.yaml
- 在意识到 Linkerd 不能与 GKE 负载均衡器一起工作后,我改为将接收部署公开为 ClusterIP 服务。
kubectl expose deployment/model-server-deployment
- 将 gRPC 客户端指向我刚刚创建的 ClusterIP 服务 IP 地址,并重新部署客户端。
kubectl apply -f api_server_deployment.yaml