【AI Shift Advent Calendar 2023】Cloud Run + Cloud Endpointsで構築するgRPC API Gateway

こんにちは、開発チームの由利です。本記事はAI Shift Advent Calendar 2023の3日目の記事になります。

みなさんの環境でもアプリケーション間/サービス間での通信としてgRPCを利用されていることと思います。
便利ではありますが、テストや他チームからの要請で一部APIをSecureに公開したい!と思われたことはありませんか?

今回の記事では、CloudRun + CloudEndpointsでgRPCのAPI Gatewayを構築する手順をご紹介します。


公式ドキュメントより (https://cloud.google.com/endpoints/docs/grpc/set-up-cloud-run-espv2?hl=ja)

※この記事ではGCPのtutorial記事を参考にしています。
こちらも合わせてご確認ください!
https://cloud.google.com/endpoints/docs/grpc/set-up-cloud-run-espv2?hl=ja

Cloud Endpointsとは

一言で言うと「Cloud Runなどで開発したAPIに対して、PublicなエンドポイントをSecureに提供する」ための仕組みです。
テストや他チーム・外部企業との連携やシステム拡大に伴って、インフラ構成を分割したい場合や手軽に開発環境を用意したいようなケースで威力を発揮します。
APIが保護できるだけでなく、APIの実行状況監視(実行回数、レスポンスタイム、エラー回数)もCloud Endpointsの画面で参照できるようになります。

必要環境

下記が予めinstallされ、利用できるようになっていること

  • gcloud CLI
  • python 3.10
  • grpcurl
    ※protocをpython経由で利用するだけなので、適宜読み替えしていただければ大丈夫です。

サンプルgRPCアプリのデプロイ

まずはサンプルgRPCアプリをCloudRunにデプロイします。
今回はGCPが公開しているサンプルアプリを利用します。

pointは --use-http2 で、これを指定することでHTTP/2通信が許可されます。
gRPCのstreamではこの指定が必要になるので注意してください。

$ gcloud run deploy python-grpc-bookstore-server \
--image="gcr.io/endpointsv2/python-grpc-bookstore-server" \
--platform managed \
--region asia-northeast1 \
--use-http2 \
--allow-unauthenticated \
--port 8000

※注
--allow-unauthenticated を指定すると全て公開されてしまうので、通常はinternal accessのみに閉じておく(--no-allow-unauthenticatedを指定する)のが良い
今回は動作の違いを確かめるため、未認証でのアクセスを許可しておく

デプロイ実行すると下記のように表示されます。

...
Service [python-grpc-bookstore-server] revision [python-grpc-bookstore-server-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://python-grpc-bookstore-server-<HASH>-an.a.run.app

実はこれで既にgRPCアプリケーションを実行できるようになっています。
これを試しにgrpcurlで呼び出してみましょう。
Service URLとして出力されたURLのプロトコル識別子(https://) は消し、port番号を指定して呼び出します。

$ grpcurl -proto bookstore.proto python-grpc-bookstore-server-<HASH>-an.a.run.app:443 endpoints.examples.bookstore.Bookstore.ListShelves
{
  "shelves": [
    {
      "id": "1",
      "theme": "Fiction"
    },
    {
      "id": "2",
      "theme": "Fantasy"
    }
  ]
}

$ grpcurl -proto bookstore.proto -d '{ "shelf":1 }' python-grpc-bookstore-server-<HASH>-an.a.run.app:443 endpoints.examples.bookstore.Bookstore.ListBooks
{
  "books": [
    {
      "id": "1",
      "author": "Neal Stephenson",
      "title": "README"
    }
  ]
}

Cloud Endpointsの構築

Cloud Endpoints用Cloud Runの用意

さて、では次にCloud Endpointsを準備します。
Cloud Endpoints用Cloud Runを予め立てておきます。
この時のservice名(下記だと「grpc-bookstore-endpoint」)がendpoint名になります。

$ gcloud run deploy grpc-bookstore-endpoint \
    --image="gcr.io/cloudrun/hello" \
    --allow-unauthenticated \
    --platform managed \
    --use-http2 \
    --project=<gcp_project_id> \
    --region asia-northeast1

実行すると下記のように表示されます。

...
Service [grpc-bookstore-endpoint] revision [grpc-bookstore-endpoint-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://grpc-bookstore-endpoint-<HASH>-an.a.run.app

この Service URL は以降の手順で使うので覚えておいてください。

Cloud Endpointsの設定とデプロイ

次にこちらをダウンロードします。
https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/endpoints/bookstore-grpc-transcoding
ダウンロードしたディレクトリの中で、下記コマンドを実行してください。

$ pip install -r requirements.txt
$ mkdir generated_pb2
$ python3 -m grpc_tools.protoc \
    --include_imports \
    --include_source_info \
    --proto_path=. \
    --descriptor_set_out=api_descriptor.pb \
    --python_out=generated_pb2 \
    --grpc_python_out=generated_pb2 \
    bookstore.proto

そうすると api_descriptor.pb が出力されます。
次に api_config.yaml ファイルを作成し、次のように内容を設定します。

  • name --> CloudEndpoints用に準備したCloudRunのService URLからプロトコル識別子(https://) を消したものを指定
  • address --> サンプルgRPCアプリをデプロイしたCloudRunのService URLからプロトコル識別子(https://) を消したものを指定
type: google.api.Service
config_version: 3
name: grpc-bookstore-endpoint-<HASH>-an.a.run.app
title: Cloud Endpoints + Cloud Run gRPC
apis:
  - name: endpoints.examples.bookstore.Bookstore
usage:
  rules:
  # ListShelves methods can be called without an API Key.
  - selector: endpoints.examples.bookstore.Bookstore.ListShelves
    allow_unregistered_calls: true
backend:
  rules:
    - selector: "*"
      address: grpcs://python-grpc-bookstore-server-<HASH>-an.a.run.app

ここで注目していただくのは下記sectionです。
呼び出し可能なEndpointを指定しています。
ここで指定したもの以外は呼び出すことができません。

usage:
  rules:
  # ListShelves methods can be called without an API Key.
  - selector: endpoints.examples.bookstore.Bookstore.ListShelves
    allow_unregistered_calls: true

準備が完了したら下記コマンドを実行します。

$ gcloud endpoints services deploy api_descriptor.pb api_config.yaml

実行結果に表示される Service Configuration [2023-12-02r0] の[]の中の値はCONFIG_IDと呼ばれる大事なIDなので覚えておいてください。

Service Configuration [2023-12-02r0] uploaded for service [grpc-bookstore-endpoint-<HASH>-an.a.run.app]

必要なサービスが有効になっていることを確認するには、次のコマンドを実行します。

$ gcloud services list

おそらく表示されるはずですが、サービスが表示されない場合は、次のコマンドを使用してサービスを有効にします。

$ gcloud services enable servicemanagement.googleapis.com
$ gcloud services enable servicecontrol.googleapis.com
$ gcloud services enable endpoints.googleapis.com

Endpoints サービスも有効にします。

$ gcloud services enable grpc-bookstore-endpoint-<HASH>-an.a.run.app

ただ、ここで終わりではないので要注意!

ESPv2 イメージのビルドとデプロイ

実は最後にGCPで用意されたスクリプトでESPv2コンテナをビルドし、手動でCloud Runにデプロイ(入れ替え)しなければなりません。

下記スクリプトをダウンロードしてください。
https://github.com/GoogleCloudPlatform/esp-v2/blob/master/docker/serverless/gcloud_build_image

その後下記コマンドを実行します。
オプションに指定する内容は下記の通りです。

  • -s --> CloudEndpoints用に準備したCloudRunのService URLからプロトコル識別子(https://) を消したものを指定
  • -c --> Endpoint構成時のService Configuration [2023-12-02r0] の[]の中の値(CONFIG_ID)を指定
$ chmod +x gcloud_build_image
$ ./gcloud_build_image -s grpc-bookstore-endpoint-<HASH>-an.a.run.app \
    -c 2023-12-02r0 -p <gcp_project_id>

実行結果にビルドされたimage名が表示されるので覚えておいてください

gcr.io/<gcp_project_id>/endpoints-runtime-serverless:2.46.0-grpc-bookstore-endpoint-<HASH>-an.a.run.app-2023-12-02r0

最後に、序盤に予約したEndpoint用CloudRunをこのimageに入れ替えます。

$ gcloud run deploy grpc-bookstore-endpoint \
  --image="gcr.io/<gcp_project_id>/endpoints-runtime-serverless:2.46.0-grpc-bookstore-endpoint-<HASH>-an.a.run.app-2023-12-02r0" \
  --allow-unauthenticated \
  --platform managed \
  --use-http2 \
  --project=<gcp_project_id> \
  --region asia-northeast1

これで準備が整いました!

動作確認

最後にgrpcurlで動作確認してみましょう。
サンプルアプリをデプロイしたCloudRunに対して直接アクセスした時の挙動と比較してみてください。

$ grpcurl -proto bookstore.proto grpc-bookstore-endpoint-<HASH>-an.a.run.app:443 endpoints.examples.bookstore.Bookstore.ListShelves
{
  "shelves": [
    {
      "id": "1",
      "theme": "Fiction"
    },
    {
      "id": "2",
      "theme": "Fantasy"
    }
  ]
}

$ grpcurl -proto bookstore.proto -d '{ "shelf":1 }' grpc-bookstore-endpoint-<HASH>-an.a.run.app:443 endpoints.examples.bookstore.Bookstore.ListBooks
ERROR:
  Code: Unauthenticated
  Message: {"code":401,"message":"UNAUTHENTICATED: Method doesn't allow unregistered callers (callers without established identity). Please use API Key or other form of API consumer identity to call this API."}

比較してみていただくとわかりますが、公開rulesに設定したendpoints.examples.bookstore.Bookstore.ListShelves はアクセスできるのに対し、
rulesに記述のない endpoints.examples.bookstore.Bookstore.ListBooks は呼び出すことができませんでした。

他にもログ出力や外部から認証をつけられるなど、API Gatewayとして一般的な機能が用意されており、とても便利です。

とはいえ、あまりに面倒では??

はい。おっしゃる通り、後半の手順がかなり大変です。。
これを簡略化する方法として「GCPフルマネージドのAPI Gateway」を採用する。(正式名称はAPI Gatewayなのですが、わかりにくいので、ここではGCP API Gatewayと呼びます)という手があります。
機能が制限される、利用可能なregionの制限がある。などの制約がありますが、制約が許容できればかなり手順が簡略化されるので、お試しください。
https://cloud.google.com/api-gateway/docs/get-started-cloud-run-grpc?hl=ja

GCP API Gateway構築手順

※説明は省略しますが、手順のみ記載しておきます
本文中の「api_config.yamlの作成」以降の手順を下記に入れ替えてお試しください!

api_config.yamlを下記に変更する

type: google.api.Service
config_version: 3
name: "*.apigateway.<gcp_project_id>.cloud.goog"
title: API Gateway + Cloud Run gRPC
apis:
  - name: endpoints.examples.bookstore.Bookstore
usage:
  rules:
  # ListShelves methods can be called without an API Key.
  - selector: endpoints.examples.bookstore.Bookstore.ListShelves
    allow_unregistered_calls: true
backend:
  rules:
    - selector: "*"
      address: grpcs://python-grpc-bookstore-server-<HASH>-an.a.run.app

API設定を作成する

$ gcloud api-gateway api-configs create grpc-bookstore-api-config \
--api=grpc-bookstore-api --project=<gcp_project_id> \
--grpc-files=api_descriptor.pb,api_config.yaml

GCP API Gatewayをデプロイする
describe結果にアクセス先host名が表示される

$ gcloud api-gateway gateways create grpc-bookstore-gateway \
  --api=grpc-bookstore-api --api-config=grpc-bookstore-api-config \
  --location=asia-northeast1 --project=<gcp_project_id>
$ gcloud api-gateway gateways describe grpc-bookstore-gateway \
  --location=asia-northeast1 --project=<gcp_project_id>

GCP API Gatewayにアクセスを試す

$ grpcurl -proto bookstore.proto grpc-bookstore-gateway-<HASH>.an.gateway.dev:443 endpoints.examples.bookstore.Bookstore.ListShelves
$ grpcurl -proto bookstore.proto grpc-bookstore-gateway-<HASH>.an.gateway.dev:443 endpoints.examples.bookstore.Bookstore.ListBooks

おわりに

ここまで読んでいただきありがとうございました。
AI Shiftの開発チームでは、AIチームと連携してAI/LLMを活用したプロダクト開発を通し、日々ユーザのみなさまにより素晴らしい価値・体験を届けるべく開発に取り組んでいます。

AI Shiftではエンジニアの採用に力を入れています!この分野に少しでも興味を持っていただけましたら、カジュアル面談でお話しませんか?(オンライン・19時以降の面談も可能です!)
面談フォームはこちら

明日のAdvent Calendar 4日目の記事は、開発チームの石井による Goのコード生成について の記事の予定です。こちらもよろしくお願いいたします。

PICK UP

TAG