使用 Envoy Proxy 对 GKE 上的 gRPC 服务进行负载平衡

本教程演示了如何使用网络负载平衡Envoy 代理通过单个外部 IP 地址公开部署在 Google Kubernetes Engine (GKE) 上的多个 gRPC 服务。本教程重点介绍 Envoy 提供给 gRPC 的一些高级功能。

简介

gRPC 是基于 HTTP/2 且与语言无关的开源 RPC 框架,它使用协议缓冲区实现高效的传输中表示和快速序列化。受内部 Google RPC 框架 Stubby 启发,gRPC 支持微服务之间以及移动客户端和 API 之间的低延迟通信。

gRPC 通过 HTTP/2 运行,与通过 HTTP/1.1 运行相比具有若干优点,例如高效的二进制编码,通过单个连接复用请求和响应,以及自动流控制。gRPC 还提供了几种负载平衡选项。本教程重点介绍客户端不受信任的情况,例如移动客户端和在服务提供商的信任边界外运行的客户端。gRPC 提供多种负载平衡选项,而本教程使用基于代理的负载平衡。

本教程将带您部署 TYPE=LoadBalancer 的 Kubernetes 服务,该服务在 Google Cloud 上公开为传输层(第 4 层)的网络负载平衡。此服务具有单一公共 IP 地址,并将 TCP 连接直接传递到配置的后端。本教程中的后端是一个 Kubernetes Deployment of Envoy 实例。

Envoy 是一个开源应用层(第 7 层)代理,提供许多高级特性。在本教程中,您可以使用它来终止 SSL/TLS 连接并将 gRPC 流量路由到适当的 Kubernetes 服务。与 Kubernetes Ingress 等其他应用层解决方案相比,使用 Envoy 可直接提供多种自定义选项,例如以下几种:

  • 服务发现
  • 负载平衡算法
  • 转换请求和响应 - 例如,转换为 JSON 或 gRPC-Web
  • 通过验证 JWT 令牌来验证请求
  • gRPC 运行状况检查

通过综合使用网络负载平衡与 Envoy,您可以设置端点(外部 IP 地址),将流量转发到在 GKE 集群中运行的一组 Envoy 实例。然后,这些实例使用应用层信息对发送到集群中运行的不同 gRPC 服务的请求进行代理。Envoy 实例使用集群 DNS 识别向每个服务正常运行的 pod 传入的 gRPC 请求并实现其负载平衡。这意味着每个 pod 的流量根据 RPC 请求进行负载均衡,而不是根据客户端的 TCP 连接。

费用

本教程使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用量来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 登录您的 Google 帐号。

    如果您还没有 Google 帐号,请注册一个新帐号

  2. 在 Cloud Console 的项目选择器页面上,选择或创建 Cloud 项目。

    转到项目选择器页面

  3. 确保您的 Google Cloud 项目已启用结算功能。 了解如何确认您的项目已启用结算功能

  4. 启用 Cloud Build, Container Registry, and Container Analysis API。

    启用 API

架构

在本教程中,您需要在 Google Kubernetes Engine (GKE) 集群中部署两种 gRPC 服务,即 echo-grpcreverse-grpc,并通过公共 IP 地址将它们公开到互联网上。下图显示了通过单个端点公开这两种服务的架构:

通过单个端点公开“echo-grpc”和“reverse-grpc”的架构

网络负载平衡接受从互联网传入的请求(例如,来自移动客户端或公司外部的服务使用者的请求)。网络负载平衡会执行以下任务:

  • 负载平衡到池中工作器节点的传入连接。流量会转发给在集群中所有工作器节点上公开的 envoy Kubernetes 服务。Kubernetes 网络代理将这些连接转发到运行 Envoy 的 pod。
  • 对集群中的工作器节点执行 HTTP 运行状况检查。

Envoy 执行以下任务:

  • 终止 SSL/TLS 连接。
  • 通过查询内部集群 DNS 服务发现运行 gRPC 服务的 pod。
  • 将流量路由到 gRPC 服务 pod 并进行其负载平衡。
  • 根据 gRPC 运行状况检查协议对 gRPC 服务进行运行状况检查。
  • 使用网络负载平衡公开进行运行状况检查的端点。

gRPC 服务(echo-grpcreverse-grpc)公开为 Kubernetes 无头服务。这意味着没有分配 clusterIP 地址,而且 Kubernetes 网络代理不会对通向 pod 的流量进行负载平衡。但系统会在集群 DNS 服务中创建包含 pod IP 地址的 DNS A 记录。Envoy 会从此 DNS 条目中发现该 pod IP 地址并根据在 Envoy 中配置的政策对其进行负载平衡。

下图展示了本教程中涉及到的 Kubernetes 对象:

本教程中使用的 Kubernetes 对象,包括服务、YAML 文件、DNS A 记录、Secret、pod 和 代理条目

初始化环境

在本部分中,您可以设置本教程中稍后使用的环境变量。

  1. 打开 Cloud Shell:

    转到 Cloud Shell

    您可以使用 Cloud Shell 运行本教程中的所有命令。

  2. 在 Cloud Shell 中,显示当前项目 ID:

    gcloud config list --format 'value(core.project)'
    
  3. 如果该命令未返回您选择的项目的 ID,请配置 Cloud Shell 以使用您的项目,同时使用您的项目的名称替代 project-id

    gcloud config set project project-id
    
  4. 为您要在本教程中使用的区域和地区定义环境变量:

    REGION=us-central1
    ZONE=$REGION-b
    

    本教程使用 us-central1 区域和 us-central1-b 地区。但是,您可以更改区域和地区以满足您的需要。

创建 GKE 集群

  1. 创建运行 gRPC 服务的 GKE 集群:

    gcloud container clusters create grpc-cluster --zone $ZONE
    
  2. 通过列出集群中的工作节点来验证是否已设置 kubectl 上下文:

    kubectl get nodes -o name
    

    输出类似于以下内容:

    node/gke-grpc-cluster-default-pool-c9a3c791-1kpt
    node/gke-grpc-cluster-default-pool-c9a3c791-qn92
    node/gke-grpc-cluster-default-pool-c9a3c791-wf2h
    

部署 gRPC 服务

为了将流量路由到一个负载平衡器后面的多种 gRPC 服务,您需要部署两种简单的 gRPC 服务:echo-grpcreverse-grpc。这两种服务都会公开一种一元方法,这种方法在内容请求字段中使用字符串。echo-grpc 返回未经更改的内容,而 reverse-grpc 返回经过翻转的内容字符串。

  1. 克隆包含 gRPC 服务的代码库并切换到工作目录:

    git clone https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial
    cd grpc-gke-nlb-tutorial
    
  2. 使用 Cloud Build,为 Echo 和 Reverse gRPC 服务创建容器映像,并将它们存储在 Container Registry 中:

    gcloud builds submit -t gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc echo-grpc
    
    gcloud builds submit -t gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc reverse-grpc
    
  3. 验证 Container Registry 中是否存在这些映像:

    gcloud container images list --repository gcr.io/$GOOGLE_CLOUD_PROJECT
    

    输出类似于以下内容:

    NAME
    gcr.io/grpc-gke-nlb-tutorial/echo-grpc
    gcr.io/grpc-gke-nlb-tutorial/reverse-grpc
    
  4. echo-grpcreverse-grpc 创建 Kubernetes 部署:

    sed s/GOOGLE_CLOUD_PROJECT/$GOOGLE_CLOUD_PROJECT/ \
        k8s/echo-deployment.yaml | kubectl apply -f -
    
    sed s/GOOGLE_CLOUD_PROJECT/$GOOGLE_CLOUD_PROJECT/ \
        k8s/reverse-deployment.yaml | kubectl apply -f -
    
  5. 检查每个部署是否都有两个可用的 pod:

    kubectl get deployments
    

    输出类似于以下内容。对于两个部署,DESIREDCURRENTUP-TO-DATEAVAILABLE 的值都应为 2

    NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    echo-grpc      2         2         2            2           1m
    reverse-grpc   2         2         2            2           1m
    
  6. echo-grpcreverse-grpc 创建 Kubernetes 无头服务。这些命令在集群的 DNS 服务中创建 DNS A 记录,但不分配虚拟 IP 地址

    kubectl apply -f k8s/echo-service.yaml
    kubectl apply -f k8s/reverse-service.yaml
    
  7. 检查 echo-grpcreverse-grpc 是否作为 Kubernetes 服务存在:

    kubectl get services
    

    输出类似于以下内容。echo-grpcreverse-grpc 都应具有 TYPE=ClusterIPCLUSTER-IP=None

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
    echo-grpc      ClusterIP   None         <none>        8081/TCP   35s
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP    47m
    reverse-grpc   ClusterIP   None         <none>        8082/TCP   21s
    

设置网络负载平衡

  1. 在集群中创建类型 LoadBalancer 的 Kubernetes 服务:

    kubectl apply -f k8s/envoy-service.yaml
    

    此命令配置网络负载平衡所需的资源,并分配临时公共 IP 地址。分配公共 IP 地址可能需要几分钟的时间。

  2. 运行以下命令,然后等待 envoy 服务的 EXTERNAL-IP 值从 <pending> 更改为公共 IP 地址:

    kubectl get services envoy --watch
    
  3. Control+C 即可停止等待。

创建自签名的 SSL/TLS 证书

Envoy 在终止 SSL/TLS 连接时使用证书和密钥。您首先需要创建一个自签名的 SSL/TLS 证书。

  1. 创建一个环境变量来存储您在上一节创建的 Envoy 服务的公共 IP 地址:

    EXTERNAL_IP=$(kubectl get service envoy -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  2. 创建自签名的 SSL/TLS 证书和密钥:

    openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
        -keyout privkey.pem -out cert.pem -subj "/CN=$EXTERNAL_IP"
    
  3. 创建一个名为 envoy-certs 的 Kubernetes TLS Secret,其中包含自签名的 SSL/TLS 证书和密钥:

    kubectl create secret tls envoy-certs \
        --key privkey.pem --cert cert.pem \
        --dry-run -o yaml | kubectl apply -f -
    

部署 Envoy

  1. 创建 Kubernetes ConfigMap 以存储 Envoy 配置文件 (envoy.yaml):

    kubectl apply -f k8s/envoy-configmap.yaml
    
  2. 为 Envoy 创建 Kubernetes 部署:

    kubectl apply -f k8s/envoy-deployment.yaml
    
  3. 验证两个 envoy pod 是否正在运行:

    kubectl get deployment envoy
    

    输出类似于以下内容:

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    envoy     2         2         2            2           1m
    

您现在随时可以测试 gRPC 服务了。

测试 gRPC 服务

如需测试服务,请使用 grpcurl 命令行工具。

  1. 在 Cloud Shell 中安装 grpcurl

    go get github.com/fullstorydev/grpcurl
    go install github.com/fullstorydev/grpcurl/cmd/grpcurl
    
  2. 向 Echo gRPC 服务发送请求:

    grpcurl -d '{"content": "echo"}' -proto echo-grpc/api/echo.proto \
        -insecure -v $EXTERNAL_IP:443 api.Echo/Echo
    

    输出类似于以下内容:

    Resolved method descriptor:
    rpc Echo ( .api.EchoRequest ) returns ( .api.EchoResponse );
    
    Request metadata to send:
    (empty)
    
    Response headers received:
    content-type: application/grpc
    date: Wed, 27 Feb 2019 04:40:19 GMT
    hostname: echo-grpc-5c4f59c578-wcsvr
    server: envoy
    x-envoy-upstream-service-time: 0
    
    Response contents:
    {
      "content": "echo"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

    hostname 响应标头显示处理请求的 echo-grpc pod 的名称。如果重复几次该命令,您应该会看到 hostname 响应标头的两个不同的值,与 echo-grpc 的名称相对应。

  3. 验证与 Reverse gRPC 服务行为相同:

    grpcurl -d '{"content": "reverse"}' -proto reverse-grpc/api/reverse.proto \
        -insecure -v $EXTERNAL_IP:443 api.Reverse/Reverse
    

    输出类似于以下内容:

    Resolved method descriptor:
    rpc Reverse ( .api.ReverseRequest ) returns ( .api.ReverseResponse );
    
    Request metadata to send:
    (empty)
    
    Response headers received:
    content-type: application/grpc
    date: Wed, 27 Feb 2019 04:45:56 GMT
    hostname: reverse-grpc-74cdc4849f-tvsfb
        server: envoy
    x-envoy-upstream-service-time: 2
    
    Response contents:
    {
      "content": "esrever"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

问题排查

如果您在使用本教程时遇到问题,我们建议您查看以下文档:

您还可以浏览 Envoy 管理界面以诊断 Envoy 配置相关问题。

  1. 如需打开管理界面,请设置从 Cloud Shell 到其中一个 Envoy Pod 的 admin 端口的端口转发:

    kubectl port-forward \
        $(kubectl get pods -o name | grep envoy | head -n1) 8080:8090
    
  2. 等到您在控制台上看到以下输出:

    Forwarding from 127.0.0.1:8080 -> 8090
    
  3. 点击 Cloud Shell 中的网页预览按钮,然后选择在端口 8080 上预览。此时系统会打开一个新的浏览器窗口,显示管理界面。

    已选择预览的 Envoy 管理界面

  4. 完成后,切换回 Cloud Shell 并按 Control+C 以结束端口转发。

路由 gRPC 流量的其他方式

您可以通过多种方式修改此解决方案,以适应您的环境。

其他应用层负载平衡器

其他负载平衡解决方案也可以提供 Envoy 提供的一些应用层功能:

  • 您可以使用 Kubernetes Ingress 对象配置 HTTP(S) 负载平衡,并使用它来替代网络负载平衡和 Envoy。与网络负载平衡相比,使用 HTTP(S) 负载平衡具有多种优势,例如托管 SSL/TLS 证书以及与其他 Google Cloud 产品(例如 Cloud CDN 和 IAP)的集成。

    如果不需要任何以下支持,我们建议您使用 HTTP(S) 负载平衡:

    • gRPC 运行状况检查
    • 对负载平衡算法的精细控制
    • 公开 50 多个服务

    如需详细了解如何使用示例 gRPC 服务部署 HTTP(S) 负载平衡,请参阅 GitHub 上的 Ingress 相关 Google Kubernetes Engine 文档GKE gRPC Ingress 负载平衡教程

  • 如果您使用 Istio,则可以使用其特性来路由 gRPC 流量并进行负载平衡。您可以将 Istio 的 Ingress Gateway 部署为网络负载平衡,带有一个 Envoy 后端,这与本教程中的架构类似。它们之间的主要区别在于 Envoy 代理是通过 Istio 的流量路由对象进行配置的。如要使本教程中的示例服务可在 Istio 服务网格中路由,您必须从 Kubernetes 服务清单(echo-service.yamlreverse-service.yaml)中移除行 clusterIP: None。这意味着使用 Istio 的服务发现和负载平衡功能,而不是 Envoy 中的类似功能。如果您已使用 Istio,我们建议您使用 Ingress Gateway 路由到您的 gRPC 服务。

  • 您可以使用 NGINX 代替 Envoy,作为部署或使用 NGINX Ingress Controller for Kubernetes。本教程中使用 Envoy 是因为它提供更高级的 gRPC 功能,例如支持 gRPC 运行状况检查协议

  • 您可以使用 AmbassadorContour,它们提供 Kubernetes Ingress Controller 并基于 Envoy。

  • 您可以使用 Voyager,它是基于 HAProxy 的 Kubernetes Ingress Controller。

内部 VPC 网络连接

如果要在 GKE 集群之外但仅在 VPC 网络内公开服务,您可以使用内部 TCP/UDP 负载平衡代替网络负载平衡。为此,请将注释 cloud.google.com/load-balancer-type: "Internal" 添加到 envoy-service.yaml 清单中。

Envoy 部署与 DaemonSet

本教程将 Envoy 配置为 Kubernetes 部署。这样的配置意味着部署清单中的 replica 设置决定了 Envoy pod 的数量。如果负载平衡器将传入的请求转发到未运行 Envoy pod 的工作器节点上,Kubernetes 网络代理会将请求转发到运行 Envoy pod 的工作器节点。

DaemonSet 是 Envoy 部署的替代方案。使用 DaemonSet 时,Envoy pod 会在 GKE 集群中的各工作器节点上运行。这种替代方案意味着在大型集群中的资源使用率更高(更多 Envoy pod),但这也意味着传入的请求总是送达运行 Envoy pod 的工作器节点。结果就是集群中的网络流量减少,同时平均延迟时间降低,这是因为请求不在工作器节点之间转发即可送达 Envoy pod。

清理

学完当前教程后,您可以清理在 Google Cloud 上创建的资源,以免这些资源占用配额,日后产生费用。以下部分介绍如何删除或关闭这些资源。

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”页面

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除资源

如果您希望保留在本教程中使用的 Google Cloud 项目,请删除单个资源:

  1. 在 Cloud Shell 中,删除克隆的本地 Git 代码库:

    cd ; rm -rf ~/grpc-gke-nlb-tutorial
    
  2. 删除 Container Registry 中的映像:

    gcloud container images list-tags gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc \
        --format 'value(digest)' | xargs -I {} gcloud container images delete \
        --force-delete-tags --quiet gcr.io/$GOOGLE_CLOUD_PROJECT/echo-grpc@sha256:{}
    
    gcloud container images list-tags gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc \
        --format 'value(digest)' | xargs -I {} gcloud container images delete \
        --force-delete-tags --quiet gcr.io/$GOOGLE_CLOUD_PROJECT/reverse-grpc@sha256:{}
    
  3. 删除 Google Kubernetes Engine 集群:

    gcloud container clusters delete grpc-cluster --zone $ZONE --quiet  --async
    

后续步骤