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

Last reviewed 2019-05-30 UTC

本教程演示了如何使用外部直通网络负载均衡器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 层)代理,提供许多高级特性。在本教程中,您可以使用它来终止 TLS 连接并将 gRPC 流量路由到适当的 Kubernetes Service。与 Kubernetes Ingress 等其他应用层解决方案相比,使用 Envoy 可直接提供多种自定义选项,例如以下几种:

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

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

架构

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

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

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

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

Envoy 执行以下任务:

  • 终止 TLS 连接。
  • 通过查询内部集群 DNS 服务发现运行 gRPC 服务的 pod。
  • 将流量路由到 gRPC 服务 pod 并进行其负载平衡。
  • 根据 gRPC 健康检查协议对 gRPC 服务进行健康检查。
  • 公开端点,以由外部直通网络负载均衡器对 Envoy 实例进行健康检查。

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 和 代理条目

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

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

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  2. 确保您的 Google Cloud 项目已启用结算功能

  3. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

准备环境

  1. 在 Cloud Shell 中,设置要用于本教程的 Google Cloud 项目:

    gcloud config set project PROJECT_ID
    

    PROJECT_ID 替换为您的 Google Cloud 项目 ID。

  2. 启用 Artifact Registry 和 GKE API:

    gcloud services enable artifactregistry.googleapis.com \
        container.googleapis.com
    

创建 GKE 集群

  1. 在 Cloud Shell 中,创建用于运行 gRPC 服务的 GKE 集群:

    gcloud container clusters create envoy-grpc-tutorial \
        --enable-ip-alias \
        --release-channel rapid \
        --scopes cloud-platform \
        --workload-pool PROJECT_ID.svc.id.goog \
        --zone us-central1-f
    

    本教程使用 us-central1-f 地区。您可以使用其他可用区或区域

  2. 通过列出集群中的节点来验证已设置 kubectl 上下文:

    kubectl get nodes --output name
    

    输出类似于以下内容:

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

创建 Artifact Registry 代码库

  1. 在 Cloud Shell 中,创建新的代码库来存储容器映像:

    gcloud artifacts repositories create envoy-grpc-tutorial-images \
        --repository-format docker \
        --location us-central1
    

    您需要在 GKE 集群所在的区域中创建代码库,以帮助优化节点拉取容器映像时的延迟时间和网络带宽。

  2. 将代码库的 Artifact Registry Reader 角色授予 GKE 集群节点虚拟机使用的 Google 服务账号:

    PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format 'value(projectNumber)')
    
    gcloud artifacts repositories add-iam-policy-binding envoy-grpc-tutorial-images \
        --location us-central1 \
        --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
        --role roles/artifactregistry.reader
    
  3. 将代码库主机名的凭据帮助程序条目添加到 Cloud Shell 主目录中的 Docker 配置文件:

    gcloud auth configure-docker us-central1-docker.pkg.dev
    

    凭据帮助程序条目使 Cloud Shell 中运行的容器映像工具可以向 Artifact Registry 代码库位置进行身份验证,以拉取和推送映像。

部署 gRPC 服务

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

  1. 在 Cloud Shell 中,克隆包含 gRPC 服务的代码库并切换到代码库目录:

    git clone https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial.git ~/grpc-gke-nlb-tutorial
    
    cd ~/grpc-gke-nlb-tutorial
    
  2. 创建自签名的 TLS 证书和私钥:

    openssl req -x509 -newkey rsa:4096 -nodes -sha256 -days 365 \
        -keyout privkey.pem -out cert.pem -extensions san \
        -config \
        <(echo "[req]";
          echo distinguished_name=req;
          echo "[san]";
          echo subjectAltName=DNS:grpc.example.com
         ) \
        -subj '/CN=grpc.example.com'
    
  3. 创建一个名为 envoy-certs 的 Kubernetes Secret,其中包含自签名的 TLS 证书和私钥:

    kubectl create secret tls envoy-certs \
        --key privkey.pem --cert cert.pem \
        --dry-run=client --output yaml | kubectl apply --filename -
    

    Envoy 在终止 TLS 连接时使用此 TLS 证书和私钥。

  4. 使用 Skaffold 为示例应用 echo-grpcreverse-grpc 构建容器映像,将映像推送到 Artifact Registry,并将应用部署到 GKE 集群:

    skaffold run \
        --default-repo=us-central1-docker.pkg.dev/PROJECT_ID/envoy-grpc-tutorial-images \
        --module=echo-grpc,reverse-grpc \
        --skip-tests
    

    Skaffold 是 Google 的一个开源工具,可自动执行开发、构建、推送应用以及将应用部署为容器的工作流。

  5. 使用 Skaffold 将 Envoy 部署到 GKE 集群:

    skaffold run \
        --digest-source=none \
        --module=envoy \
        --skip-tests
    
  6. 验证每个部署都有两个准备就绪的 pod:

    kubectl get deployments
    

    输出类似于以下内容。对于所有部署,READY 的值应为 2/2

    NAME           READY   UP-TO-DATE   AVAILABLE   AGE
    echo-grpc      2/2     2            2           1m
    envoy          2/2     2            2           1m
    reverse-grpc   2/2     2            2           1m
    
  7. 验证 echo-grpcenvoyreverse-grpc 已作为 Kubernetes Service 存在:

    kubectl get services --selector skaffold.dev/run-id
    

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

    NAME           TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)         AGE
    echo-grpc      ClusterIP      None          <none>           8081/TCP        2m
    envoy          LoadBalancer   10.40.2.203   203.0.113.1      443:31516/TCP   2m
    reverse-grpc   ClusterIP      None          <none>           8082/TCP        2m
    

测试 gRPC 服务

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

  1. 在 Cloud Shell 中安装 grpcurl

    go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
    
  2. 获取 envoy Kubernetes Service 的外部 IP 地址,并将其存储在环境变量中:

    EXTERNAL_IP=$(kubectl get service envoy \
        --output=jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  3. echo-grpc 示例应用发送一个请求:

    grpcurl -v -d '{"content": "echo"}' \
        -proto echo-grpc/api/echo.proto \
        -authority grpc.example.com -cacert cert.pem \
        $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, 02 Jun 2021 07:18:22 GMT
    hostname: echo-grpc-75947768c9-jkdcw
    server: envoy
    x-envoy-upstream-service-time: 3
    
    Response contents:
    {
      "content": "echo"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

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

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

    grpcurl -v -d '{"content": "reverse"}' \
        -proto reverse-grpc/api/reverse.proto \
        -authority grpc.example.com -cacert cert.pem \
        $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, 02 Jun 2021 07:20:15 GMT
    hostname: reverse-grpc-5c9b974f54-wlfwt
    server: envoy
    x-envoy-upstream-service-time: 1
    
    Response contents:
    {
      "content": "esrever"
    }
    
    Response trailers received:
    (empty)
    Sent 1 request and received 1 response
    

Envoy 配置

如需更好地了解 Envoy 配置,您可以查看 Git 代码库中的配置文件 envoy/k8s/envoy.yaml

route_config 部分指定如何将传入请求路由到 echo-grpcreverse-grpc 示例应用。

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains:
    - "*"
    routes:
    - match:
        prefix: "/api.Echo/"
      route:
        cluster: echo-grpc
    - match:
        prefix: "/api.Reverse/"
      route:
        cluster: reverse-grpc

示例应用被定义为 Envoy 集群

clusters:
- name: echo-grpc
  connect_timeout: 0.5s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  http2_protocol_options: {}
  load_assignment:
    cluster_name: echo-grpc
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: echo-grpc.default.svc.cluster.local
              port_value: 8081
  health_checks:
    timeout: 1s
    interval: 10s
    unhealthy_threshold: 2
    healthy_threshold: 2
    grpc_health_check: {}

集群定义中的 type: STRICT_DNSlb_policy: ROUND_ROBIN 字段指定 Envoy 对 address 字段中指定的主机名执行 DNS 查找,并在 DNS 查找响应中的 IP 地址之间进行负载均衡。响应包含多个 IP 地址,因为定义示例应用的 Kubernetes Service 对象指定无头服务。

http2_protocol_options 字段指定 Envoy 使用 HTTP/2 作为示例应用的协议。

health_checks 部分中的 grpc_health_check 字段指定 Envoy 使用 gRPC 健康检查协议来确定示例应用的健康状况。

问题排查

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

您还可以浏览 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 提供的一些应用层功能:

  • 您可以使用全球外部应用负载均衡器或区域外部应用负载均衡器,而不是外部直通网络负载均衡器和自行管理的 Envoy。与外部直通网络负载均衡器相比,使用外部应用负载均衡器具有多项优势,例如高级流量管理功能、代管式 TLS 证书以及与其他 Google Cloud 产品(例如 Cloud CDN、Google Cloud Armor 和 IAP)的集成。

    如果全球外部应用负载均衡器或区域外部应用负载均衡器提供的流量管理功能符合您的使用场景,并且您不需要支持基于客户端证书的身份验证(也称为双向 TLS (mTLS) 身份验证),则我们建议您使用上述负载均衡器。有关详情,请参阅以下文档:

  • 如果您使用 Anthos Service Mesh 或 Istio,则可以使用其特性来路由 gRPC 流量并进行负载平衡。Anthos Service Mesh 和 Istio 都提供入站流量网关,它部署为带有 Envoy 后端的外部直通网络负载均衡器,这与本教程中的架构类似。它们之间的主要区别在于 Envoy 是通过 Istio 的流量路由对象进行配置的。

    如要使本教程中的示例服务可在 Anthos Service Mesh 或 Istio 服务网格中路由,您必须从 Kubernetes 服务清单(echo-service.yamlreverse-service.yaml)中移除行 clusterIP: None。这意味着使用 Anthos Service Mesh 或 Istio 的服务发现和负载均衡功能,而不是 Envoy 中的类似功能。

    如果您已使用 Anthos Service Mesh 或 Istio,我们建议您使用入站流量网关路由到您的 gRPC 服务。

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

内部 VPC 网络连接

如果要在 GKE 集群之外但仅在 VPC 网络内公开服务,您可以使用内部直通网络负载均衡器内部应用负载均衡器

如需使用内部直通网络负载均衡器代替外部直通网络负载均衡器,请将注解 cloud.google.com/load-balancer-type: "Internal" 添加到 envoy-service.yaml 清单。

如需使用内部应用负载均衡器,请参阅配置适用于内部应用负载均衡器的 Ingress 文档。

清理

完成本教程后,您可以清理您创建的资源,让它们停止使用配额,以免产生费用。以下部分介绍如何删除或关闭这些资源。

删除项目

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

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

删除资源

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

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

    cd ; rm -rf ~/grpc-gke-nlb-tutorial
    
  2. 删除 GKE 集群:

    gcloud container clusters delete envoy-grpc-tutorial \
        --zone us-central1-f --async --quiet
    
  3. 删除 Artifact Registry 中的代码库:

    gcloud artifacts repositories delete envoy-grpc-tutorial-images \
        --location us-central1 --async --quiet
    

后续步骤