GKE Internal Load Balancer无法在gRPC服务器间均衡分配负载
初始问题描述
我部署的API近期流量上涨约1.5倍后,接口延迟直接翻倍:
我已配置节点、Pod自动扩缩容以及GKE Internal Load Balancer,该现象不符合预期。
外部API将请求转发到高CPU占用的内部服务,观察虚拟机实例(即Kubernetes节点)发现所有流量都被转发到两台节点中的其中一台:
按照负载均衡的预期,节点CPU占用应当更均匀。当前Pod分布为:第一个节点上部署1个Pod,第二个节点上部署2个Pod:


我的服务配置如下:
$ 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创建,但第二个节点上的Pod完全收不到流量,请问如何让GKE实现更均匀的负载均衡?
11月2日更新
根据答复推测问题和模型服务的配置有关,该服务同时暴露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
我的Pod列表如下:
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日更新
排查发现过往多个Pod因内存不足被驱逐,已将Pod迁移到新节点池,旧节点池VM配置为4核4GB内存,新节点池为2核8GB内存,驱逐/内存问题已解决,但负载均衡器仍每次仅将流量转发到单个Pod:
节点1上的Pod1负载:
节点2上的Pod2负载:
当前负载均衡器完全不拆分流量,只是随机选择一个gRPC模型服务器转发100%流量,请问是否有遗漏的配置导致该现象?是否和使用gRPC有关?
解决方案
问题A:健康检查状态确认方法
- 查看GCP控制台负载均衡后端服务的健康状态页,可直接看到每个后端实例的健康检查结果
- 执行
gcloud compute backend-services get-health [后端服务名] --region [区域],返回结果中会明确标注每个后端的健康状态 - 查看健康检查日志:健康检查已开启日志,可直接在GCP日志浏览器中过滤对应健康检查资源的日志,查看每次检查的返回状态
- 登录节点本地访问健康检查端口验证:
curl http://localhost:10256/healthz,查看返回是否为200,异常的节点会返回非200状态码
问题B:健康检查配置调整
你当前使用的是GKE默认的节点级健康检查(端口10256是kube-proxy的健康检查端口),如果节点上存在至少一个对应服务的就绪Pod,该接口就会返回200。如果确认节点本身健康检查异常,可先排查节点的kube-proxy状态、防火墙规则是否放通了VPC内部对10256端口的访问。
如果需要更精准的Pod级健康检查,可在Service注解中添加cloud.google.com/load-balancer-type: "Internal"的同时,配置cloud.google.com/backend-config: '{"default": "my-backendconfig"}',通过BackendConfig资源自定义健康检查规则,直接探测Pod的业务端口就绪状态,避免节点级健康检查的误差。
11月5日更新问题解决
你遇到的单Pod独占流量问题确实和gRPC协议直接相关:
gRPC基于HTTP/2实现,默认会在客户端和服务端之间建立长连接,所有请求都会复用同一个TCP连接,而你当前的后端服务负载均衡模式是CONNECTION(基于连接数分配),一旦连接建立成功,后续所有请求都会走同一个连接,不会再做负载均衡。
修复方法如下:
- 调整负载均衡调度模式:将后端服务的
balancingMode改为UTILIZATION或者RATE,如果是gRPC服务建议使用基于请求量的调度策略 - 客户端侧配置gRPC连接策略:开启客户端侧的负载均衡,配置
service_config中的loadBalancingPolicy为round_robin,同时设置合理的连接最大存活时间(max_connection_age),强制客户端定期重建连接,分散到不同的后端Pod - 如果是GKE 1.21+版本,可直接使用gRPC专用的负载均衡配置,在BackendConfig中指定
protocol: GRPC,开启gRPC级的请求负载均衡,而不是TCP连接级的负载均衡
内容的提问来源于stack exchange,提问作者Johan Wikström

