G-gen の佐々木です。当記事では同一プロジェクトにある Cloud Run 間の通信をプライベートな通信経路で行う方法を解説します。
Cloud Run から Cloud Run へのアクセス方法
パブリックアクセス
マイクロサービスなどのユースケースにおいて、Cloud Run から別の Cloud Run にアクセスするケースがあります。この場合、アクセスされる側の Cloud Run にて、設定項目である「上り(内向き)の制御」を「すべて」に設定することで、他の Cloud Run からアクセスすることができます。
しかし、このような設定の場合、たとえアクセスされる側の Cloud Run がインターネットに公開したくないサービスであっても、インターネットから到達できてしまう状態となっています。IAM による認証を必須にすることでアクセスをブロックすることもできますが、なるべくはアクセス元を制限し、ネットワークレイヤで制限をかけたほうが、よりセキュアです。
プライベートアクセス
Cloud Run では、「上り(内向き)の制御」を「内部」に設定することで、同一プロジェクトの VPC を経由した通信のみが Cloud Run にアクセスできるように設定することができます。
「内部」に設定された Cloud Run に対して別の Cloud Run からアクセスする場合、通信は VPC を経由しなければならないため、アクセス元の Cloud Run は Direct VPC Egressもしくはサーバーレス VPC アクセス(両者の比較についてはこちらの記事を参照)を使用して VPC にアクセスできるようにします。
このとき、Cloud Run から接続される VPC 内のサブネットで限定公開の Google アクセスを有効にしておく必要があります。
Cloud Run の呼び出しに IAM 認証を必須にすることで、VPC を経由し、かつ IAM で許可された場合のみ Cloud Run にアクセスできるようになります。
なお、上記の方法は、Cloud Run が両方とも同一のプロジェクトに存在する場合のみ利用可能です。別々のプロジェクトにある Cloud Run 同士の通信を「内部」で行いたい場合、VPC Service Controls や Private Service Connect を使用する必要があります(参考)。
構成図
当記事では Direct VPC Egress を使用することで、フロントエンドとして作成した Cloud Run サービスからバックエンドの Cloud Run サービスに対して、VPC を経由したプライベートな通信経路で接続を行います。
バックエンドの Cloud Run では、上り(内向き)の通信を「内部」のみ許可するように設定することで、インターネットからのアクセスを防ぎます。また、認証を必須にすることで、VPC からの通信であっても IAM で許可されたアクセス元のみがサービスを利用できるようにします。
事前準備
シェル変数の設定
当記事では gcloud コマンドを使用して各種リソースを作成していきます。
コマンド内で何度か使用する値は、以下のようにシェル変数として設定しておきます。
PROJECT
変数の値にはリソースを作成するプロジェクトを、LOCATION
変数の値にはリージョンを設定してください。残りの変数は各種リソースの名前を指定する際に使用します。
PROJECT=my-project LOCATION=asia-northeast1 NETWORK=my-vpc # VPCの名前 SUBNET=my-subnet # サブネットの名前 REPO=my-repo # Artifact Registory リポジトリの名前 RUN_SA=run-frontend # Cloud Run(フロントエンド)に紐付けるサービスアカウントの名前
Artifact Registry リポジトリの作成
Cloud Run 用のコンテナイメージを格納するための Artifact Registory リポジトリを作成します。
# Artifact Registry リポジトリを作成する $ gcloud artifacts repositories create ${REPO} \ --repository-format=docker \ --project=${PROJECT} \ --location=${LOCATION}
バックエンドサービスの作成
まずはアクセス対象となるバックエンドの Cloud Run サービスを作成していきます。
バックエンドのサービスは、フロントエンドの Cloud Run のみが呼び出すことができる認証付きの API 機能を想定します。サービスに対してインターネットからアクセスできないようにし、また IAM による認証を必須とします。
使用するコード(Go)
当記事では Go を使用してサービスを実装していきます。
フロントエンドからのリクエストに対して、ステータスコード 200 と「Hello from backend!」というメッセージを JSON で返却するシンプルな内容となっています。
// backend/main.go package main import ( "encoding/json" "log" "net/http" ) type Response struct { Status int Message string } func main() { log.Print("Service is running on port 8080") http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { body := Response{ http.StatusOK, "Hello from backend!", } res, err := json.Marshal(body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(res) }) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
コンテナイメージのビルド
Cloud Build を使用してコンテナイメージをビルドし、Artifact Registory リポジトリにプッシュします。
当記事では Buildpacks を使用することで、Dockerfile を用意せずにコンテナイメージを作成していきます。
# Cloud Build でコンテナイメージをビルドする $ gcloud builds submit --pack image=${LOCATION}-docker.pkg.dev/${PROJECT}/${REPO}/run-backend
Cloud Run デプロイ用 YAML ファイルの作成
当記事では YAML ファイルを使用して Cloud Run サービスを作成していきます。
# backend.yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: run-backend # サービスの名前 annotations: run.googleapis.com/ingress: internal # 上り(内向き)トラフィックを内部に制限 spec: template: spec: containers: - image: asia-northeast1-docker.pkg.dev/${プロジェクトID}/${リポジトリ名}/run-backend # コンテナイメージの URL
上記の YAML ファイルで重要な箇所、および変更が必要な箇所を以下のようになります。
項目 | 値 | 説明 |
---|---|---|
metadata.annotations. run.googleapis.com/ingress |
internal |
Cloud Run がインターネットからのトラフィックを受信できるようにする。 |
spec.template.spec.containers[0].image | バックエンド用のコンテナイメージ | 以下のコマンドで確認できる。$ echo asia-northeast1-docker.pkg.dev/${PROJECT}/${REPO}/run-backend |
Cloud Run サービスの作成
gcloud コマンドで YAML ファイルを使用して Cloud Run を作成します。
YAML ファイルを使用する場合、サービスを新規に作成する場合であっても gcloud run service replace
コマンドを使用します。
# YAML ファイルを使用して Cloud Run サービスをデプロイする $ gcloud run services replace backend.yaml \ --project=${PROJECT} \ --region=${LOCATION}
VPC とサブネットの作成
後ほど作成するフロントエンドの Cloud Run から接続するための VPC とサブネットを作成します。バックエンドのサービスへの通信は、限定公開の Google アクセスを有効にしたサブネットを経由することで、インターネットではなく内部からのアクセスとなります。
当記事では Direct VPC Egress を使用して VPC に接続します。Direct VPC Egress はフロントエンドの Cloud Run 作成時に同時に作成します。
# VPC の作成 $ gcloud compute networks create ${NETWORK} \ --project=${PROJECT} \ --subnet-mode=custom
サブネット作成時に--enable-private-ip-google-access
フラグを指定することで、限定公開の Google アクセスを有効にします。また、Cloud Run の制限事項として、通信の宛先となるサブネットの IP アドレス範囲が 192.168.1.0/24
の場合、通信することができない点には注意が必要です(参考)。
# サブネットの作成 $ gcloud compute networks subnets create ${SUBNET} \ --project=${PROJECT} \ --network=${NETWORK} \ --range=192.168.101.0/24 \ --region=${LOCATION} \ --enable-private-ip-google-access
フロントエンドサービスの作成
バックエンドの Cloud Run サービスにアクセスするフロントエンドサービスを作成していきます。
フロントエンドのサービスは、インターネットから不特定のユーザーにアクセスされる Web サービスを想定します。そのためサービスに対しては認証なしでアクセスできるようにします。
サービスアカウントの作成
バックエンドの Cloud Run にアクセスするための権限を付与するためのサービスアカウントを作成します。
# Cloud Run(フロントエンド)用のサービスアカウントを作成する $ gcloud iam service-accounts create ${RUN_SA} --project=${PROJECT}
作成したサービスアカウントに、バックエンドの Cloud Run サービスを呼び出すための Cloud Run 起動元(roles/run.invoker) 権限を付与します。
# Cloud Run(バックエンド)を呼び出す権限を付与する $ gcloud run services add-iam-policy-binding run-backend \ --role="roles/run.invoker" \ --member="serviceAccount:${RUN_SA}@${PROJECT}.iam.gserviceaccount.com" \ --project=${PROJECT} \ --region=${LOCATION}
使用するコード(Go)
フロントエンドのサービスは、/api
にアクセスすることでバックエンドのサービスにリクエストを送信し、その後バックエンドから返ってきたメッセージを表示するように実装します。
また、/
にアクセスした場合は、フロントエンドのサービスからそのまま「Hello from frontend!」というメッセージを返します。
// frontend/main.go package main import ( "context" "fmt" "io" "log" "net/http" "os" "google.golang.org/api/idtoken" ) type Response struct { Status int `json:"status"` Message string `json:"message"` } func main() { log.Print("Service is running on port 8080") http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello from frontend!") }) http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { targetUrl := os.Getenv("BACKEND_URL") audience := targetUrl + "/" resp, err := makeGetRequest(w, targetUrl, audience) if err != nil { fmt.Fprintf(w, "Error: %v", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { fmt.Fprintf(w, "Error: %s", resp.Status) return } }) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } } // バックエンドの Cloud Run サービスに対してリクエストを送信する func makeGetRequest(w io.Writer, targetURL string, audience string) (*http.Response, error) { ctx := context.Background() client, err := idtoken.NewClient(ctx, audience) if err != nil { return nil, fmt.Errorf("idtoken.NewClient: %w", err) } resp, err := client.Get(targetURL) if err != nil { return nil, fmt.Errorf("client.Get: %w", err) } defer resp.Body.Close() if _, err := io.Copy(w, resp.Body); err != nil { return nil, fmt.Errorf("io.Copy: %w", err) } return resp, nil }
バックエンドの Cloud Run サービスは IAM 認証を必須としているため、Google APIs のクライアントライブラリから、認証用のパッケージである idtoken を使用しています。
Cloud Run で実行する場合、Cloud Run に紐付けられたサービスアカウントの認証情報が自動で使用されます。
コンテナイメージのビルド
バックエンドサービスと同様の手順でコンテナイメージをビルドし、Artifact Registory リポジトリにプッシュします。
# Cloud Build でコンテナイメージをビルドする $ gcloud builds submit --pack image=${LOCATION}-docker.pkg.dev/${PROJECT}/${REPO}/run-frontend
Cloud Run デプロイ用 YAML ファイルの作成
フロントエンド用の Cloud Run を作成するための YAML ファイルは以下のように記述します。
# frontend.yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: run-frontend # サービスの名前 annotations: run.googleapis.com/ingress: all # インターネットからの上り(内向き)トラフィックを許可 spec: template: metadata: annotations: run.googleapis.com/vpc-access-egress: all-traffic # 全ての下り(外向き)トラフィックを Direct VPC Egress 経由で送信 run.googleapis.com/network-interfaces: '[{"network":"${作成したVPCの名前}","subnetwork":"${作成したサブネットの名前}"}]' spec: serviceAccountName: ${作成したサービスアカウントのメールアドレス} containers: - image: asia-northeast1-docker.pkg.dev/${プロジェクトID}/${リポジトリ名}/run-frontend # コンテナイメージの URL env: - name: BACKEND_URL value: ${Cloud Run サービス(バックエンド)の URL}
上記の YAML ファイルで重要な箇所、および変更が必要な箇所を以下のようになります。
項目 | 値 | 説明 |
---|---|---|
metadata.annotations. run.googleapis.com/ingress |
all |
Cloud Run がインターネットからのトラフィックを受信できるようにする。 |
spec.template.metadata.annotations. run.googleapis.com/vpc-access-egress |
all-traffic |
Cloud Run から送信される全てのトラフィックを VPC 経由で送信する。 |
spec.template.metadata.annotations. run.googleapis.com/network-interfaces |
Direct VPC Egress で接続する VPC とサブネットの名前 | 以下のコマンドの出力をそのまま記載する。$ echo \'[{\"network\":\"${NETWORK}\"\,\"subnetwork\":\"${SUBNET}\"}]\' |
spec.template.spec.serviceAccountName | 作成したサービスアカウントの名前 | 以下のコマンドで確認できる。$ echo ${RUN_SA}@${PROJECT}.iam.gserviceaccount.com |
spec.template.spec.containers[0].image | フロントエンド用のコンテナイメージ | 以下のコマンドで確認できる。$ echo ${LOCATION}-docker.pkg.dev/${PROJECT}/${REPO}/run-frontend |
spec.template.spec.containers[0].env[0].value | バックエンドの Cloud Run サービスの URL | 以下のコマンドで確認できる。$ gcloud run services describe run-backend --project=${PROJECT} --region=${LOCATION} --format='value(status.url)' |
Cloud Run サービスの作成
gcloud コマンドで YAML ファイルから Cloud Run サービスを作成します。
# YAML ファイルを使用して Cloud Run サービスをデプロイする $ gcloud run services replace frontend.yaml \ --project=${PROJECT} \ --region=${LOCATION}
YAML ファイルからのデプロイでは metadata.annotations.run.googleapis.com/ingress
に all
を設定しても「未認証の呼び出しを許可」の設定はされないので、別途 gcloud コマンドを使用して allUsers
に対して Cloud Run 起動元
のロールを付与します。
# 未認証で Cloud Run サービスを呼び出せるようにする $ gcloud run services add-iam-policy-binding run-frontend \ --role="roles/run.invoker" \ --member="allUsers" \ --project=${PROJECT} \ --region=${LOCATION}
これでインターネット上から認証なしでフロントエンドのサービスにアクセスすることができます。
疎通の確認
以下のコマンドでフロントエンドのサービスの URL を確認し、ブラウザや curl コマンドなどでアクセスします。
# Cloud Run サービス(フロントエンド)の URL を確認する $ gcloud run services describe run-frontend \ --project=${PROJECT} \ --region=${LOCATION} \ --format='value(status.url)'
サービスの /
にアクセスした場合、フロントエンドのサービスから返ってきた「Hello from frontend!」というメッセージが表示されます。
# フロントエンドのサービスからメッセージが返る
$ curl https://run-frontend-ai4xxxxxxx-an.a.run.app/
Hello from frontend!
サービスの /api
パスにアクセスすると、IAM で許可された内部からのアクセスのみ可能なバックエンドサービスのレスポンスが表示されます。
# フロントエンドのサービスからバックエンドにアクセスする $ curl https://run-frontend-ai4xxxxxxx-an.a.run.app/api {"Status":200,"Message":"Hello from backend!"}
佐々木 駿太 (記事一覧)
G-gen最北端、北海道在住のクラウドソリューション部エンジニア
2022年6月にG-genにジョイン。Google Cloud Partner Top Engineer 2024に選出。好きなGoogle CloudプロダクトはCloud Run。
趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。
Follow @sasashun0805