Google Cloud 上に gRPC サーバを起動する(Compute & Kubernetes)チュートリアルをやってみた

はじめに

以下のチュートリアルをやってみた。
ただし、まったく同じ手順ではなく、Container Registry ではなく、Artifact Registry を利用したり、インスタンスタイプを指定するなどしている。
実際に操作している動画を YouTube にアップロードした。

事前準備


ツール類のインストール

以下のツールをインストールする。バージョンは、異なっても良い。
  • go 1.19.3
  • git 2.35.1.windows.2
  • protoc 3.21.10

Google Cloud 環境の設定等

チュートリアルを実施するための Google Cloud プロジェクトは、作成済みとする。
gcloud コマンド類は、以下のサイトの手順でインストール済みとする。
プロジェクトを切り替える場合は、以下のようにする。
gcloud config set project プロジェクトID

API の有効化

以下の API を有効化しておく。
  • Compute Engine API
  • Service Control API
  • Artifact Registry API
  • Cloud Build API
  • Kubernetes Engine API
手順は、Google Cloud コンソールで、対象のプロジェクトを切り替えて、「API とサービス」→「ライブラリ」に移動、「API とサービスを検索」で該当 API を検索してクリックする。
API の詳細画面で「有効にする」ボタンを押下する。

ファイアウォールについて

前提知識として、Compute Engine の VM インスタンスに設定するファイアウォール ルールについて説明する。
ファイアウォール ルールは、「VPC ネットワーク」→「ファイアウォール」に「VPC ファイアウォール ルール」として定義する。
ファイアウォール ルールには、「ターゲットタグ」設定があり、ここで設定した値を、VM インスタンスの「ネットワーク タグ」に指定すると、ルールがそのインスタンスに適用される。
ファイアウォール ルールは、初期状態で以下が存在する。ただし、タグは設定されておらず、すべてのインスタンスに自動的に適用される。
  • default-allow-icmp
  • default-allow-internal
  • default-allow-rdp
  • default-allow-ssh
ここで、ハマりポイントがあった。
チュートリアルでは、「http-server」というタグを利用しているが、初期状態では対応するファイアウォール ルールの「default-allow-http」が、存在していない。この状態でインスタンスを作成しても、エラーにはならないが、外部からの HTTP アクセスが、ファイアウォールで遮断される。
上記ファイアウォール ルールが作成されるタイミングは、Google Cloud コンソールから、VM インスタンスを作成する時に、ファイアウォールで「HTTP トラフィックを許可する」をチェックした場合となる。(手動で作成してもいいのかもしれないが、未確認)
gcloud コマンドで実行する場合、「--tags=http-server」オプションを指定しただけでは、ファイアウォール ルールが、自動で作成されることはない。
よって、適当なインスタンスを Google Cloud コンソールから「HTTP トラフィックを許可する」をチェックして作成して、default-allow-http ファイアウォール ルールが作成された事を確認して、インスタンスを削除する。
HTTPS の場合も同様なので、必要に応じてファイアウォール ルールを作成する。このチュートリアルでは利用しないので、作成しなくてもよい。

チュートリアル実施


チュートリアルのソースをクローンする

git clone --depth 1 https://github.com/GoogleCloudPlatform/golang-samples.git
cd golang-samples\endpoints\getting-started-grpc

ローカル環境で実行する

start go run server/main.go
go run client/main.go

Google Endpoints のサービスを登録する

protocolbuf の定義ファイル(out.pb)を生成する
protoc --include_imports --include_source_info --descriptor_set_out out.pb helloworld/helloworld.proto
api_config.yaml ファイルの YOUR_PROJECT_ID を Google Cloud のプロジェクト ID に書き換え、以下を実行する。
gcloud endpoints services deploy out.pb api_config.yaml
登録が完了すると、Google Cloud コンソールの「エンドポイント」に、登録したサービスが表示される。

Artifacts Registry にリポジトリを作成する

この手順は、オリジナルには無い。
gcloud artifacts repositories create --repository-format=docker --location=asia-northeast1 helloworld-repo

Artifacts Registry のリポジトリに gRPC サーバの Docker イメージを登録する

go.mod および go.sum をコピーする。 ローカルで動作させる場合は、親ディレクトリにある、これらファイルを参照してくれるが、 Docker イメージ作成時にコピー対象から外れているため。
copy ..\go.* .
Dockerfile を以下のように変更する。
FROM golang:alpine

RUN apk update \
 && apk add git

COPY . /go/src/app

WORKDIR /go/src/app
RUN go install ./server

ENTRYPOINT ["/go/bin/server"]
リポジトリに Docker イメージの登録を実行する。YOUR_PROJECT_ID をプロジェクトIDに変更すること。
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/helloworld-repo/go-grpc-hello:1.0 .

Compute Engine にデプロイする

Compute Engine にデプロイする方法は、Docker が利用可能な VM インスタンスを起動して、 その VM インスタンス上で、gRPC を処理するサーバと、エンドポイントと gRPC を処理するサーバとの仲介をする Extensible Service Proxy(ESP)をコンテナとして起動する。

Compute Engine の VM インスタンスを作成する

コマンドを実行すると、VM インスタンスが起動する。途中でゾーンを選択する必要があるが、ここでは、asia-northeast1-b とした。
インスタンスの OS は、「Container-Optimized OS」となる。 この OS は、パッケージ管理ツールが無く、直接アプリなどをインストールしない前提で利用する。 その代わり、Docker を利用可能で、アプリケーションなどは、Docker コンテナとして動作させることとなる。
gcloud compute instances create grpc-host --image-family cos-stable --image-project cos-cloud --tags=http-server --machine-type=e2-micro

VM インスタンスにアプリケーションをデプロイする

VM インスタンスに SSH 接続する。コマンドを実行すると、PuTTY が起動し、VM インスタンスに接続された状態となる。以後の作業は、起動した PuTTY ウィンドウで実施する。
gcloud compute ssh grpc-host

プロジェクト名および、エンドポイントサービス名を取得する

GOOGLE_CLOUD_PROJECT=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
SERVICE_NAME=hellogrpc.endpoints.${GOOGLE_CLOUD_PROJECT}.cloud.goog

Docker に Artifacts Registry 参照権限を付与する

docker-credential-gcr configure-docker --registries=asia-northeast1-docker.pkg.dev

コンテナネットワークを作成する

docker network create --driver bridge esp_net

gRPC サーバコンテナを起動する

docker run --detach --net=esp_net --name=grpc-hello asia-northeast1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/helloworld-repo/go-grpc-hello:1.0

エンドポイント用のプロキシコンテナを起動する

docker run \
    --detach \
    --name=esp \
    --publish=80:9000 \
    --net=esp_net \
    gcr.io/endpoints-release/endpoints-runtime:1 \
    --service=${SERVICE_NAME} \
    --rollout_strategy=managed \
    --http2_port=9000 \
    --backend=grpc://grpc-hello:50051

VM インスタンスの IP を確認する

このコマンドは、ローカルマシン上で実行すること。
NAME が grpc-host となっている行の、EXTERNAL_IP が、外部からアクセスするための IP となる。
gcloud compute instances list --filter=grpc-host

API キーを作成する

Google Cloud コンソールの「API とサービス」→「認証情報」画面で、「+認証情報を作成」ボタンを押下し、「API キー」を選択する。
表示された API キーをコピーしておく。

gRPC クライアントでアクセスする

ローカル PC 上のチュートリアルプロジェクトで実行する。api-key オプションには、上記で作成した API キーを指定する。
go run client/main.go --api-key=AIza... --addr=<上記で調べたEXTERNAL_IP>:80 FOOBAR

クリーンアップ

VM インスタンスを削除する。
gcloud compute instances delete grpc-host

Kubernetes Engine にデプロイする

続いて、Kubernetes Engine にも gRPC サーバをデプロイしてみる。

kubectl コマンドをインストールする

kubectl コマンドは、gcloud コマンドでインストールする。ローカルマシンで以下のコマンドを実行する。
gcloud components install kubectl
当方の環境では、Docker Desktop の kubectl が既にあり、PATH の設定の関係で、そちらが優先され、 認証用プラグインなどで警告が出るため、Docker Desktop 側の kubectl.exe をリネームして対応した。 Kubernetes Engine へのアクセスは、gcloud でインストールする kubectl が推奨されているため、こちらを利用する。

Kubernetes クラスタを作成する

Autopilot クラスタの作成手順で、クラスタを作成する。オリジナルの手順では、ゾーンクラスタを作成している。
名前は、デフォルト(autopilot-cluster-1)、リージョンは、「asia-northeast1」 、ネットワークアクセスは、「一般公開クラスタ」を選択した。その他はデフォルトのままとした。作成完了まで多少時間がかかるようだ。

クラスタへアクセスするための認証情報を取得する

kubectl で作成したクラスタへアクセスするための認証情報を取得する。autopilot-cluster-1 の部分は、作成したクラスタ名にすること。
コマンド実行が完了すると、設定が作成され、デフォルトの接続先となる。
gcloud container clusters get-credentials --region=asia-northeast1 autopilot-cluster-1

権限を設定する

Kubernetes Engine の Pod に権限を設定するために、Workload Identity を利用する。 Workload Identity とは、Kubernetes Engine クラスタ内の「Kubernetes サービス アカウント」を、「IAM サービス アカウント」として機能させるための仕組みとなる。
Kubernetes 上で動作する Pod には、エンドポイント API 等へのアクセス権限が必要となる。 Kubernetes Engine の Autopilot クラスタ における Pod への権限設定は、「Kubernetes サービス アカウント」を作成し、 次に、「IAM サービス アカウント」を作成して、各 API アクセス権限を割り当て、「Kubernetes サービス アカウント」と対応付けした上で、 各 Pod の定義に「Kubernetes サービス アカウント」を指定することで行う。
このチュートリアルでは、Extensible Service Proxy(ESP)を動作させる Pod に権限が必要となる。

Kubernetes サービス アカウントを作成する

default ネームスペースに、Kubernetes サービス アカウントを作成する。
kubectl create serviceaccount grpc-hello-endpoint-ksa
作成されたことを確認する。
kubectl get serviceaccount

IAM サービス アカウントを作成する

gcloud iam service-accounts create grpc-hello-endpoint-gsa
作成されたことを確認する。
Google Cloud コンソールで確認する場合は、「IAM と管理」→「サービス アカウント」で、 「grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com」のような項目として表示される。
コマンドで確認する場合は、以下のようにする。
gcloud iam service-accounts list

IAM サービス アカウントにロールを付与する

ESP に必要な権限は、エンドポイントを管理するためのサービスコントローラロールおよび、 Cloud Trace エージェントロール(各種情報収集用)となる。
サービスコントローラロールを付与する。YOUR_PROJECT_ID(2 箇所)をプロジェクトID に変更すること。
gcloud endpoints services add-iam-policy-binding hellogrpc.endpoints.YOUR_PROJECT_ID.cloud.goog --member serviceAccount:grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com --role roles/servicemanagement.serviceController
Cloud Trace エージェントロールを付与する。YOUR_PROJECT_ID(2 箇所)をプロジェクトID に変更すること。
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID --member serviceAccount:grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com --role roles/cloudtrace.agent

Kubernetes サービス アカウントと IAM サービス アカウントを紐付ける

紐付けは、双方向に行う必要があるようだ。
IAM サービス アカウント側を設定。YOUR_PROJECT_ID(2 箇所)をプロジェクトID に変更すること。
gcloud iam service-accounts add-iam-policy-binding grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com --role roles/iam.workloadIdentityUser --member "serviceAccount:YOUR_PROJECT_ID.svc.id.goog[default/grpc-hello-endpoint-ksa]"
Kubernetes サービス アカウント側にアノテーションを設定。YOUR_PROJECT_ID(1 箇所)をプロジェクトID に変更すること。
kubectl annotate serviceaccount grpc-hello-endpoint-ksa iam.gke.io/gcp-service-account=grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com

デプロイ用設定ファイルを修正する

deployment.yaml ファイル中の <YOUR_PROJECT_ID> をプロジェクトID に変更する。
Pod のイメージは、Artifact Registry のリポジトリから取得するように、 「gcr.io/<YOUR_PROJECT_ID>/go-grpc-hello:1.0」の部分は、「asia-northeast1-docker.pkg.dev/<YOUR_PROJECT_ID>/helloworld-repo/go-grpc-hello:1.0」のようにする。
「serviceAccountName: grpc-hello-endpoint-ksa」行を追加する。
以上の修正をした結果は、以下のような差分となる。
diff --git a/endpoints/getting-started-grpc/deployment.yaml b/endpoints/getting-started-grpc/deployment.yaml
index f1e2321..b2ae498 100644
--- a/endpoints/getting-started-grpc/deployment.yaml
+++ b/endpoints/getting-started-grpc/deployment.yaml
@@ -40,18 +40,33 @@ spec:
       labels:
         app: grpc-hello
     spec:
+      serviceAccountName: grpc-hello-endpoint-ksa
       containers:
       - name: esp
         image: gcr.io/endpoints-release/endpoints-runtime:1
         args: [
           "-P", "9000",
           "-a", "grpc://127.0.0.1:50051",
-          "-s", "hellogrpc.endpoints.<YOUR_PROJECT_ID>.cloud.goog", # replace <YOUR_PROJECT_ID>
+          "-s", "hellogrpc.endpoints.YOUR_PROJECT_ID.cloud.goog", # replace <YOUR_PROJECT_ID>
           "--rollout_strategy", "managed",
         ]
         ports:
           - containerPort: 9000
+        resources:
+          requests:
+            cpu: "250m"
+            memory: "500Mi"
+          limits:
+            cpu: "250m"
+            memory: "500Mi"
       - name: echo
-        image: gcr.io/<YOUR_PROJECT_ID>/go-grpc-hello:1.0 # replace <YOUR_PROJECT_ID>
+        image: asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/helloworld-repo/go-grpc-hello:1.0 # replace <YOUR_PROJECT_ID>
         ports:
           - containerPort: 50051
+        resources:
+          requests:
+            cpu: "250m"
+            memory: "500Mi"
+          limits:
+            cpu: "250m"
+            memory: "500Mi"

Kubernetes クラスタにデプロイする

以下のコマンドで、デプロイする。デプロイ直後は、「Cannot schedule pods: Insufficient cpu.」などのエラー表示となるが、 しばらくすると、正常に起動する。 ワークロードのイベントを確認することで、状態が確認できる。
kubectl apply -f deployment.yaml

クライアントで接続する

接続先 IP を参照する。コマンドを実行し、Pod の起動が完了すると、EXTERNAL_IP の値をコピーする。
kubectl get svc grpc-hello --watch
クライアントを実行する。
go run client/main.go --api-key=AIza... --addr=<上記で調べたEXTERNAL_IP>:80 FOOBAR

クリーンアップ

kubectl delete -f deployment.yaml
gcloud container clusters delete --region=asia-northeast1 autopilot-cluster-1
gcloud iam service-accounts delete grpc-hello-endpoint-gsa@YOUR_PROJECT_ID.iam.gserviceaccount.com
gcloud artifacts repositories delete --location=asia-northeast1 helloworld-repo
gcloud storage rm --recursive gs://YOUR_PROJECT_ID_cloudbuild
gcloud endpoints services delete hellogrpc.endpoints.YOUR_PROJECT_ID.cloud.goog

参考資料