Envoy プロキシを使用して GKE 上で gRPC サービスのロードバランシングを行う

Last reviewed 2019-05-30 UTC

このチュートリアルでは、外部パススルー ネットワーク ロードバランサEnvoy プロキシを使用して、Google Kubernetes Engine(GKE)にデプロイした複数の gRPC サービスを 1 つの外部 IP アドレスで公開する方法について説明します。また、Envoy が gRPC 用に提供している高度な機能の一部についても取り上げます。

はじめに

gRPC は、HTTP/2 をベースとした、言語に依存しないオープンソースの RPC フレームワークです。プロトコル バッファを使用し、効率のよいデータ転送と高速なシリアル化を実現します。Google 内部の RPC フレームワークである Stubby からアイデアを得て、gRPC はマイクロサービス間やモバイル クライアントと API 間の低レイテンシ通信を可能にしています。

gRPC は HTTP/2 で動作するため、効率的なバイナリ エンコード、単一接続上でのリクエストとレスポンスの多重化、自動フロー制御といった、HTTP/1.1 より優れた点がいくつかあります。また、gRPC には負荷分散のオプションもいくつか用意されています。このチュートリアルでは、モバイル クライアントや、サービス プロバイダの信頼境界の外部で実行されているクライアントなど、クライアントを信頼できない状況に焦点を当てています。また、gRPC が提供するロード バランシング オプションのうち、プロキシベースのロード バランシングを使用します。

このチュートリアルでは、TYPE=LoadBalancer の Kubernetes Service をデプロイし、Google Cloud 上でトランスポート レイヤ(レイヤ 4)の外部パススルー ネットワーク ロードバランサとして公開します。このサービスはパブリック IP アドレスを 1 つ提供し、構成されているバックエンドに TCP 接続を直接渡します。このチュートリアルでのバックエンドは、Envoy インスタンスの Kubernetes Deployment です。

Envoy は、高度な機能を数多く提供するオープンソースのアプリケーション レイヤ(レイヤ 7)プロキシです。このチュートリアルでは、Envoy を使用して TLS 接続の終端処理を行い、gRPC トラフィックを適切な Kubernetes Service に転送します。Kubernetes Ingress などの他のアプリケーション レイヤのソリューションとは異なり、Envoy を使用すると、次のようないくつものカスタマイズ オプションを直接利用できます。

  • サービス ディスカバリ
  • 負荷分散アルゴリズム
  • リクエストやレスポンスの変換(たとえば JSON や gRPC-Web などへの変換)
  • JWT トークンを検証することによるリクエストの認証
  • gRPC ヘルスチェック

外部パススルー ネットワーク ロードバランサと Envoy を組み合わせると、Google Kubernetes Engine クラスタで実行されている一連の Envoy インスタンスにトラフィックを転送するエンドポイント(外部 IP アドレス)を設定できます。これらのインスタンスはアプリケーション レイヤの情報を使用して、クラスタ内で実行されているさまざまな gRPC サービスに対するリクエストをプロキシ処理します。Envoy インスタンスは、クラスタ DNS を使用して gRPC の受信リクエストを識別し、各サービスの正常に実行されている Pod に負荷分散します。つまりトラフィックは、クライアントからの TCP 接続単位ではなく、RPC リクエスト単位で Pod に負荷分散されます。

アーキテクチャ

このチュートリアルでは、Google Kubernetes Engine(GKE)クラスタに 2 つの gRPC サービス、echo-grpcreverse-grpc をデプロイし、パブリック IP アドレスでインターネットに公開します。次の図は、これら 2 つのサービスを 1 つのエンドポイントを通して公開するためのアーキテクチャを示しています。

1 つのエンドポイントを通して「echo-grpc」と「reverse-grpc」を公開するためのアーキテクチャ

外部パススルー ネットワーク ロードバランサは、インターネットから(例: モバイル クライアントや社外のサービス ユーザーから)受信リクエストを受け入れます。外部パススルー ネットワーク ロードバランサは、次のタスクを実行します。

  • 受信接続をプール内のノードへロード バランスします。トラフィックは、クラスタ内のすべてのノードに公開されている envoy Kubernetes Service に転送されます。Kubernetes ネットワーク プロキシは、Envoy を実行している Pod にこれらの接続を転送します。
  • クラスタ内のノードに対して HTTP ヘルスチェックを実行します。

Envoy は次のタスクを実行します。

  • TLS 接続を終端します。
  • 内部クラスタの DNS サービスを照会して、gRPC サービスを実行している Pod を検出します。
  • トラフィックを gRPC サービスの各ポッドにルーティング、負荷分散します。
  • gRPC ヘルスチェック プロトコルに従って gRPC サービスのヘルスチェックを実行します。
  • 外部パススルー ネットワーク ロードバランサによる Envoy インスタンスのヘルスチェック用にエンドポイントを公開します。

gRPC サービス(echo-grpcreverse-grpc)は、Kubernetes のヘッドレス Service として公開されます。つまり、clusterIP アドレスが割り振られず、Kubernetes ネットワーク プロキシによってトラフィックが Pod に負荷分散されないということです。代わりに、クラスタの DNS サービス内に、Pod の IP アドレスが含まれた DNS A レコードが作成されます。Envoy はこの DNS エントリからポッドの IP アドレスを検出し、Envoy に構成されているポリシーに従ってそれらの IP アドレス間で負荷分散を行います。

次の図は、このチュートリアルに含まれる Kubernetes オブジェクトを示しています。

サービス、YAML ファイル、DNS A レコード、シークレット、Pod、プロキシ エントリなど、このチュートリアルで使用する Kubernetes オブジェクト

料金

このドキュメントでは、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 API と 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 クラスタと同じリージョンにリポジトリを作成して、ノードがコンテナ イメージを pull するときにレイテンシとネットワーク帯域幅を最適化できるようにします。

  2. GKE クラスタのノード VM で使用される Google サービス アカウントにリポジトリに対する Artifact Registry 読み取りロールを付与します。

    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 で実行されるコンテナ イメージツールが、イメージを pull および push するための Artifact Registry リポジトリの場所に対して認証を行うことができます。

gRPC サービスをデプロイする

1 つのロードバランサの背後にある複数の gRPC サービスにトラフィックを転送するため、2 つのサンプル 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 -
    

    この TLS 証明書と秘密鍵は、Envoy が TLS 接続の終端時に使用します。

  4. Skaffold を使用して、サンプルアプリ echo-grpcreverse-grpc のコンテナ イメージを作成し、それらのイメージを Artifact Registry に push して、アプリを 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 のオープンソース ツールであり、アプリケーションをコンテナとして開発、ビルド、push、デプロイするワークフローを自動化します。

  5. 次のように、Skaffold を使用して GKE クラスタに Envoy をデプロイします。

    skaffold run \
        --digest-source=none \
        --module=envoy \
        --skip-tests
    
  6. 各 Deployment で 2 つの Pod の準備ができていることを確認します。

    kubectl get deployments
    

    出力は次のようになります。すべての Deployment で、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 Services として存在することを確認します。

    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 Pod の名前に対応した 2 つの異なる値が表示されます。

  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-grpc サンプルアプリと reverse-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_DNS フィールドと lb_policy: ROUND_ROBIN フィールドは、Envoy が address フィールドで指定されたホスト名の DNS ルックアップを行い、DNS ルックアップのレスポンスの IP アドレス間でロード バランシングを行うように指定します。サンプルアプリを定義する Kubernetes Service オブジェクトはヘッドレス サービスを指定しているため、レスポンスには複数の IP アドレスが含まれます。

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 のバックエンドを持つ外部パススルー ネットワーク ロードバランサとしてデプロイされる Ingress ゲートウェイを備えています。主な違いは、Envoy は Istio のトラフィック ルーティング オブジェクトを介して構成されることです。

    このチュートリアルのサンプル サービスを Anthos Service Mesh や Istio サービス メッシュ内で転送できるようにするには、Kubernetes Service マニフェスト(echo-service.yamlreverse-service.yaml)から clusterIP: None の行を削除する必要があります。これは、Envoy の同様の機能ではなく、Anthos Service Mesh または Istio のサービス ディスカバリとロード バランシングの機能を使用することを表しています。

    Anthos Service Mesh または Istio をすでに使用している場合は、Ingress ゲートウェイを使用して gRPC サービスに転送することをおすすめします。

  • Envoy の代わりに NGINX を Deployment として使用できます。または、NGINX Ingress Controller for Kubernetes を使用することもできます。Envoy では gRPC ヘルスチェック プロトコルのサポートなどの高度な gRPC 機能が提供されるため、このチュートリアルでは Envoy を使用しています。

VPC 内部のネットワーク接続

GKE クラスタの外部にサービスを公開する必要があるものの、VPC ネットワーク内に限定することを必要とする場合は、内部パススルー ネットワーク ロードバランサまたは内部アプリケーション ロードバランサを使用します。

外部パススルー ネットワーク ロードバランサの代わりに内部パススルー ネットワーク ロードバランサを使用するには、envoy-service.yaml マニフェストにアノテーション cloud.google.com/load-balancer-type: "Internal" を追加します。

内部アプリケーション ロードバランサを使用するには、内部アプリケーション ロードバランサの 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
    

次のステップ