Como usar o Envoy Proxy para balancear a carga de serviços do gRPC no GKE

Neste tutorial, demonstramos como expor vários serviços gRPC implantados no Google Kubernetes Engine (GKE) em um único endereço IP externo usando o balanceamento de carga de rede e o Envoy Proxy (em inglês). O tutorial destaca alguns dos recursos avançados do Envoy para gRPC.

Introdução

O gRPC é um framework de RPC de código aberto, independente de linguagem, baseado em HTTP/2 e que usa buffers de protocolo para uma representação de transmissão eficiente e uma serialização rápida. Inspirado no Stubby, um framework interno de RPC do Google, o gRPC permite comunicação de baixa latência entre microsserviços e entre clientes móveis e APIs.

Ele é executado via HTTP/2 e oferece várias vantagens em relação ao HTTP/1.1, como codificação binária eficiente, multiplexação de solicitações e respostas em uma única conexão e controle de fluxo automático. Além disso, também oferece várias opções de balanceamento de carga. Este tutorial se concentra em situações em que os clientes não são confiáveis, como clientes móveis e aqueles executados fora do limite de confiança do provedor de serviços. Dentre todas as opções de balanceamento de carga fornecidas pelo gRPC, você usará neste tutorial o balanceamento de carga baseado em proxy.

No tutorial, você implanta um serviço do Kubernetes de TYPE=LoadBalancer, que é exposto como balanceamento de carga de rede da camada de transporte (camada 4) no Google Cloud. Esse serviço fornece um endereço IP público e transmite conexões TCP diretamente para os back-ends configurados. Nesse caso, o back-end será uma implantação do Kubernetes de instâncias do Envoy.

O Envoy é um proxy de camada de aplicativo de código aberto (camada 7) que oferece muitos recursos avançados. Neste tutorial, ele será utilizado para encerrar conexões SSL/TLS e encaminhar o tráfego do gRPC ao serviço do Kubernetes apropriado. Comparando com outras soluções de camada de aplicativo, como o Kubernetes Ingress, o Envoy proporciona várias opções de personalização, como as seguintes:

  • Descoberta de serviço
  • Algoritmos de balanceamento de carga
  • Transformação de solicitações e respostas, por exemplo, para JSON ou gRPC-Web
  • Autenticação de solicitações validando tokens JWT
  • Verificações de integridade do gRPC

Ao combinar o balanceamento de carga de rede com o Envoy, é possível configurar um endpoint (endereço IP externo) que encaminha o tráfego para um conjunto de instâncias do Envoy em execução em um cluster do GKE. Então, essas instâncias usam as informações da camada de aplicativo sobre solicitações de proxy nos diferentes serviços do gRPC em execução no cluster. As instâncias do Envoy usam o DNS do cluster para identificar e balancear a carga das solicitações de gRPC de entrada para os pods íntegros e em execução de cada serviço. Isso significa que a carga de tráfego é balanceada para os pods por solicitação de RPC, e não por conexão TCP do cliente.

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Como fazer a limpeza.

Antes de começar

  1. Faça login na sua conta do Google.

    Se você ainda não tiver uma, inscreva-se.

  2. No Console do Cloud, na página do seletor de projetos, selecione ou crie um projeto do Cloud.

    Acessar a página do seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud. Saiba como confirmar se a cobrança está ativada para o seu projeto.

  4. Ative as APIs Cloud Build, Container Registry, and Container Analysis.

    Ative as APIs

Arquitetura

Neste tutorial, você implanta dois serviços do gRPC, echo-grpc e reverse-grpc, em um cluster do Google Kubernetes Engine (GKE) e os expõe à Internet em um endereço IP público. Veja no diagrama a seguir a arquitetura de envio desses dois serviços por meio de um único endpoint:

arquitetura para exibição de

O balanceamento de carga de rede aceita solicitações de entrada da Internet, por exemplo, de clientes móveis ou consumidores de serviço externos à sua empresa. Ele executa as seguintes tarefas:

  • Balanceia a carga das conexões de entrada para os nós de trabalho no pool. O tráfego é encaminhado para o serviço envoy do Kubernetes, que é exposto em todos os nós de trabalho no cluster. O proxy de rede do Kubernetes encaminha as conexões para os pods que estão executando o Envoy.
  • Executa verificações de integridade HTTP para os nós de trabalho no cluster.

O Envoy executa as seguintes tarefas:

  • Encerra as conexões SSL/TLS.
  • Descobre os pods que executam os serviços do gRPC consultando o serviço de DNS do cluster interno.
  • Encaminha e balanceia a carga de tráfego para os pods de serviço do gRPC.
  • Executa verificações de integridade dos serviços do gRPC de acordo com o protocolo de verificação de integridade do gRPC (em inglês).
  • Exibe um endpoint para verificação de integridade usando o balanceamento de carga de rede.

Os serviços do gRPC (echo-grpc e reverse-grpc) são expostos como serviços headless do Kubernetes (em inglês). Isso significa que nenhum endereço clusterIP é atribuído, e o proxy de rede do Kubernetes não faz o balanceamento de carga do tráfego para os pods. Em vez disso, um registro A de DNS que contém os endereços IP dos pods é criado no serviço de DNS do cluster. O Envoy descobre os endereços a partir dessa entrada de DNS e divide a carga entre eles de acordo com a política configurada.

Veja no diagrama a seguir os objetos do Kubernetes usados neste tutorial:

Objetos do Kubernetes usados neste tutorial, incluindo serviços, arquivos YAML, registros A de DNS, secrets, pods e entrada de proxy

Como inicializar o ambiente

Nesta seção, você definirá as variáveis de ambiente que serão usadas posteriormente no tutorial.

  1. Abra o Cloud Shell:

    ACESSE o Cloud Shell

    Use o Cloud Shell para executar todos os comandos neste tutorial.

  2. Exiba o código do projeto atual no Cloud Shell:

    gcloud config list --format 'value(core.project)'
    
  3. Se o comando não retornar o ID do projeto selecionado, configure o Cloud Shell para usar o projeto substituindo project-id pelo nome do projeto:

    gcloud config set project project-id
    
  4. Defina as variáveis de ambiente para a região e a zona que você quer usar neste tutorial:

    REGION=us-central1
    ZONE=$REGION-b
    

    Neste tutorial, usamos a região us-central1 e a zona us-central1-b. No entanto, você pode alterar essas informações de acordo com as suas necessidades.

Criar o cluster do GKE

  1. Crie um cluster do GKE para executar seus serviços do gRPC:

    gcloud container clusters create grpc-cluster --zone $ZONE
    
  2. Verifique se o contexto kubectl foi configurado listando os nós trabalhados no cluster:

    kubectl get nodes -o name
    

    A saída é semelhante a:

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

Implantar os serviços do gRPC

Para rotear o tráfego para vários serviços do gRPC por trás de um balanceador de carga, implante dois serviços simples do gRPC: echo-grpc e reverse-grpc. Ambos os serviços expõem um método unário que usa uma string no campo de solicitação de conteúdo. echo-grpc responde com o conteúdo inalterado, enquanto reverse-grpc responde com a string de conteúdo invertida.

  1. Clone o repositório que contém os serviços do gRPC e alterne para o diretório de trabalho:

    git clone https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial
    cd grpc-gke-nlb-tutorial
    
  2. Usando o Cloud Build, crie as imagens do contêiner para os serviços do gRPC Echo e Reverse e armazene-as no 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. Verifique se as imagens existem no Container Registry:

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

    A saída é semelhante a:

    NAME
    gcr.io/grpc-gke-nlb-tutorial/echo-grpc
    gcr.io/grpc-gke-nlb-tutorial/reverse-grpc
    
  4. Crie implantações do Kubernetes para echo-grpc e reverse-grpc:

    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. Verifique se há dois pods disponíveis para cada implantação:

    kubectl get deployments
    

    A saída será semelhante à mostrada abaixo. Os valores para DESIRED, CURRENT, UP-TO-DATE e AVAILABLE precisam ser 2 para ambas as implantações.

    NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    echo-grpc      2         2         2            2           1m
    reverse-grpc   2         2         2            2           1m
    
  6. Crie serviços headless do Kubernetes para echo-grpc e reverse-grpc. Esses comandos criam registros A de DNS no serviço de DNS do cluster (em inglês), mas não alocam endereços IP virtuais (em inglês).

    kubectl apply -f k8s/echo-service.yaml
    kubectl apply -f k8s/reverse-service.yaml
    
  7. Verifique se echo-grpc e reverse-grpc existem como serviços do Kubernetes:

    kubectl get services
    

    A saída será semelhante à mostrada abaixo. echo-grpc e reverse-grpc devem ter TYPE=ClusterIP e CLUSTER-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
    

Configure o balanceamento de carga de rede.

  1. Crie um serviço do Kubernetes do tipo LoadBalancer no cluster:

    kubectl apply -f k8s/envoy-service.yaml
    

    Esse comando provisiona os recursos necessários para o balanceamento de carga de rede e atribui um endereço IP público temporário. A atribuição do endereço IP público pode levar alguns minutos.

  2. Execute o seguinte comando e aguarde até que o valor de EXTERNAL-IP para o serviço envoy seja alterado de <pending> para um endereço IP público:

    kubectl get services envoy --watch
    
  3. Pressione Control+C interromper a espera.

Criar um certificado SSL/TLS autoassinado

O Envoy usa um certificado e uma chave ao encerrar conexões SSL/TLS. Primeiro, crie um certificado SSL/TLS autoassinado.

  1. Crie uma variável de ambiente para armazenar o endereço IP público do serviço de "envoy" que você criou na seção anterior:

    EXTERNAL_IP=$(kubectl get service envoy -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  2. Crie uma chave e um certificado SSL/TLS autoassinados:

    openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
        -keyout privkey.pem -out cert.pem -subj "/CN=$EXTERNAL_IP"
    
  3. Crie um secret TLS do Kubernetes chamado envoy-certs que contenha o certificado e a chave SSL/TLS autoassinados:

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

Implantar o Envoy

  1. Crie um ConfigMap do Kubernetes para armazenar o arquivo de configuração do Envoy (envoy.yaml):

    kubectl apply -f k8s/envoy-configmap.yaml
    
  2. Crie uma implantação do Kubernetes para o Envoy:

    kubectl apply -f k8s/envoy-deployment.yaml
    
  3. Verifique se dois pods envoy estão em execução:

    kubectl get deployment envoy
    

    A resposta é semelhante a esta:

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

Agora você já pode atestar os serviços do gRPC.

Testar os serviços do gRPC

Para testar os serviços, use a ferramenta de linha de comando grpcurl.

  1. No Cloud Shell, instale grpcurl:

    go get github.com/fullstorydev/grpcurl
    go install github.com/fullstorydev/grpcurl/cmd/grpcurl
    
  2. Envie uma solicitação para o serviço Echo do gRPC:

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

    A saída é semelhante a:

    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
    

    O cabeçalho de resposta hostname mostra o nome do pod echo-grpc que processou a solicitação. Se você repetir o comando algumas vezes, verá dois valores diferentes para o cabeçalho de resposta hostname, correspondentes aos nomes dos pods de echo-grpc.

  3. Verifique o mesmo comportamento com o serviço Reverse do gRPC:

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

    A saída é semelhante a:

    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
    

Resolver problemas

Se você tiver problemas ao seguir este tutorial, recomendamos que consulte estes documentos:

Você também pode analisar a interface de administração do Envoy para diagnosticar problemas com a configuração do Envoy.

  1. Para abrir a interface de administração, configure o encaminhamento de portas do Cloud Shell para a porta admin de um dos pods do Envoy:

    kubectl port-forward \
        $(kubectl get pods -o name | grep envoy | head -n1) 8080:8090
    
  2. Espere até que esta saída seja exibida no console:

    Forwarding from 127.0.0.1:8080 -> 8090
    
  3. Clique no botão Visualização da Web no Cloud Shell e selecione Visualizar na porta 8080. Isso abre uma nova janela do navegador que mostra a interface de administração.

    Interface administrativa de Envoy com visualização selecionada

  4. Quando terminar, volte para o Cloud Shell e pressione Control+C para encerrar o encaminhamento de portas.

Outras formas de rotear o tráfego do gRPC

É possível modificar essa solução de várias maneiras para adequá-la ao seu ambiente.

Balanceadores de carga de camada de aplicativo alternativos

Algumas das funcionalidades de camada de aplicativo fornecidas pelo Envoy também são disponibilizadas por outras soluções de balanceamento de carga:

  • É possível configurar o balanceamento de carga HTTP(S) usando um objeto de entrada do Kubernetes em vez do balanceamento de carga de rede e do Envoy. O uso do balanceamento de carga HTTP(S) oferece vários benefícios em comparação com o balanceamento de carga de rede, como certificados SSL/TLS gerenciados e integração com outros produtos do Google Cloud, como Cloud CDN e IAP.

    Recomendamos que você use o balanceamento de carga HTTP(S) quando não precisar dos itens a seguir:

    • Verificações de integridade do gRPC
    • Controle avançado sobre o algoritmo de balanceamento de carga
    • Exibição de mais de 50 serviços

    Para saber mais sobre como implantar o balanceamento de carga HTTP(S) com um serviço do gRPC de amostra, consulte a documentação sobre entrada do Google Kubernetes Engine e o tutorial no GitHub sobre balanceamento de carga de entrada do gRPC no GKE (em inglês).

  • Se você usar o Istio, poderá aplicar os recursos dele para encaminhar e balancear a carga do tráfego do gRPC. O Ingress Gateway (em inglês) do Istio é implantado como um balanceamento de carga de rede com um back-end do Envoy, semelhante à arquitetura deste tutorial. A principal diferença é que o Envoy Proxy é configurado por meio dos objetos de roteamento de tráfego (em inglês) do Istio. Para tornar os serviços de exemplo neste tutorial roteáveis na malha de serviço do Istio, remova a linha clusterIP: None dos manifestos de serviço do Kubernetes (echo-service.yaml e reverse-service.yaml). Isso significa usar a funcionalidade de descoberta de serviço e balanceamento de carga do Istio em vez da funcionalidade semelhante no Envoy. Se você já usa o Istio, recomendamos que utilize o Ingress Gateway para direcionar o tráfego para seus serviços do gRPC.

  • É possível usar o NGINX em vez do Envoy como uma implantação ou usando o controlador de entrada do NGINX para o Kubernetes (em inglês). Usamos o Envoy neste tutorial porque ele fornece funcionalidades de gRPC mais avançadas, como suporte ao protocolo de verificação de integridade do gRPC (em inglês).

  • É possível usar o Ambassador (em inglês) e o Contour (em inglês), que fornecem controladores de entrada do Kubernetes e são baseados no Envoy.

  • É possível usar o Voyager (em inglês), que é um controlador de entrada do Kubernetes baseado em HAProxy.

Conectividade de rede interna VPC

Se você quiser exibir os serviços fora do seu cluster do GKE, mas dentro da rede VPC, poderá usar o balanceamento de carga interno TCP/UDP (em inglês) em vez do balanceamento de carga de rede. Para fazer isso, adicione a anotação cloud.google.com/load-balancer-type: "Internal" ao manifesto envoy-service.yaml.

Implantação do Envoy x DaemonSet

Neste tutorial, o Envoy é configurado como uma implantação do Kubernetes (em inglês). Essa configuração significa que a configuração replica no manifesto de implantação determina o número de pods do Envoy. Se o balanceador de carga encaminhar solicitações de entrada para um nó de trabalho que não executa um pod do Envoy, o proxy de rede do Kubernetes encaminhará a solicitação para um nó que tem um pod do Envoy em execução.

O DaemonSet (em inglês) é uma alternativa à implantação do Envoy. Com um DaemonSet, um pod do Envoy é executado em cada nó de trabalho no cluster do GKE. Essa alternativa significa maior uso de recursos em clusters grandes e mais pods do Envoy, mas também que as solicitações de entrada sempre chegam a um nó de trabalho que executa um pod do Envoy. O resultado é menos tráfego de rede no seu cluster e latência menor, já que as solicitações não são encaminhadas entre os nós de trabalho para alcançar um pod do Envoy.

Limpeza

Depois de concluir este tutorial, é possível limpar os recursos criados no Google Cloud para que eles não consumam sua cota e você não receba cobranças por eles no futuro. Veja como excluir e desativar esses recursos nas seções a seguir.

Excluir o projeto

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar a página "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Excluir os recursos

Se você quiser manter o projeto do Google Cloud usado neste tutorial, exclua os recursos individuais:

  1. No Cloud Shell, exclua o clone do repositório Git local:

    cd ; rm -rf ~/grpc-gke-nlb-tutorial
    
  2. Exclua as imagens no 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. Exclua o cluster do Google Kubernetes Engine:

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

A seguir