Cloud Run チュートリアルで Cloud Pub/Sub を使用する

このチュートリアルでは、Cloud Run サービスを作成してデプロイし、Cloud Pub/Sub push サブスクリプションから呼び出す方法を説明します。

このチュートリアルは、Cloud Run または Cloud Run on GKE で使用できます。

目標

  • サービスをビルドして Cloud Run または Cloud Run on GKE にデプロイする
  • Cloud Pub/Sub トピックにメッセージを発行してサービスを呼び出す

料金

このチュートリアルでは、以下を含む Cloud Platform の有料コンポーネントを使用します。

Cloud Platform を初めて使用する方は、無料トライアルをご利用いただけます。

始める前に

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. GCP Console のプロジェクト セレクタのページで、GCP プロジェクトを選択または作成します。

    プロジェクト セレクタのページに移動

  3. Google Cloud Platform プロジェクトに対して課金が有効になっていることを確認します。 プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Cloud Run API を有効にします。
  5. Cloud SDK をインストールし、初期化します
  6. gcloud ベータ版コンポーネントをインストールします。
    gcloud components install beta
  7. Cloud Run on GKE 用に gcloud kubectl コンポーネントをインストールします。
    gcloud components install kubectl
  8. コンポーネントを更新します。
    gcloud components update
  9. Cloud Run on GKE を使用している場合は、Cloud Run on GKE の設定の手順に従って新しいクラスタを作成します。

gcloud のデフォルトを設定する

Cloud Run サービスを gcloud のデフォルトに構成するには:

  1. デフォルト プロジェクトを設定します。

    gcloud config set project [PROJECT-ID]

    [PROJECT-ID] は、このチュートリアルで作成したプロジェクトの名前で置き換えます。

  2. Cloud Run を使用している場合は、選択したリージョンを gcloud に構成します。

     gcloud config set run/region us-central1
    

    us-central1 は、Cloud Run に選択したサポート対象のリージョンで置き換えます。

  3. Cloud Run on GKE を使用している場合は、クラスタを gcloud に構成します。

    gcloud config set run/cluster [CLUSTER-NAME]
    gcloud config set run/cluster_location us-central1-a

    [CLUSTER-NAME] はクラスタの名前で置き換えます。必要であれば、us-central1-a の代わりに、クラスタに選択したロケーションを使用してください。

Cloud Run のロケーション

Cloud Run はリージョナルです。つまり、Cloud Run サービスを実行するインフラストラクチャは特定のリージョンに配置され、そのリージョン内のすべてのゾーンで冗長的に利用できるように Google によって管理されます。

レイテンシ、可用性、耐久性の要件を満たすことが、Cloud Run サービスを実行するリージョンを選択する際の主な要素になります。通常、ユーザーに最も近いリージョンを選択できますが、Cloud Run サービスで使用される別の GCP プロダクトのロケーションも検討する必要があります。GCP プロダクトを複数のロケーションで使用すると、サービスのレイテンシだけでなく、コストにも影響を及ぼす可能性があります。

Cloud Run は、次のリージョンで利用できます。

  • us-central1(アイオワ)

Cloud Run サービスをすでに作成している場合は、GCP Console の Cloud Run ダッシュボードにリージョンが表示されます。

Cloud Pub/Sub トピックを作成する

このサンプル サービスは Cloud Pub/Sub トピックに発行されたメッセージによってトリガーされるため、Cloud Pub/Sub でトピックを作成する必要があります。

新しい Cloud Pub/Sub トピックを作成するには、次のコマンドを使用します。

gcloud pubsub topics create myRunTopic

myRunTopic を使用するか、GCP プロジェクト内で固有のトピック名に置き換えます。

サンプルコードを取得する

使用するサンプルコードを取得するには:

  1. サンプル サービスコードをダウンロードします。

    Node.js

    Go

  2. アーカイブを解凍します。たとえば、Linux または macOS の標準コマンドライン ユーティリティを使用します。

    Tarball

    tar -xzvf [FILE].tar.gz

    Zip ファイル

    unzip [FILE].zip

    [FILE] は、ダウンロードしたアーカイブのファイル名です(拡張子は除く)。

コードを確認する

このチュートリアルのコードは、次のものから構成されています。

  • 受信 HTTP リクエストを処理するサーバー。

    Node.js

    Node.js サービスをテストしやすくするため、サーバー構成はサーバーの起動とは別になっています。

    Node.js ウェブサーバーは app.js で設定されています。

    const express = require('express')
    const bodyParser = require('body-parser');
    const app = express()
    
    app.use(bodyParser.json())
    
    ウェブサーバーは index.js で開始します。
    const app = require('./app.js');
    const PORT = process.env.PORT || 8080;
    
    app.listen(PORT, () => console.log(`nodejs-pubsub-tutorial listening on port ${PORT}`))

    Go

    // Sample run-pubsub is a Cloud Run service which handles Pub/Sub messages.
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	http.HandleFunc("/", HelloPubSub)
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    	// Start HTTP server.
    	log.Printf("Listening on port %s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    

  • Cloud Pub/Sub メッセージを処理し、あいさつ文をログに記録する Hello World ファンクション。

    Node.js

    app.post('/', (req, res) => {
    
      if (!req.body) {
        const msg = 'no Pub/Sub message received'
        console.error(`error: ${msg}`)
        res.status(400).send(`Bad Request: ${msg}`)
        return
      }
      if (!req.body.message) {
        const msg = 'invalid Pub/Sub message format'
        console.error(`error: ${msg}`)
        res.status(400).send(`Bad Request: ${msg}`)
        return
      }
    
      const pubSubMessage = req.body.message
      const name = pubSubMessage.data
        ? Buffer.from(pubSubMessage.data, 'base64').toString().trim()
        : 'World'
    
      console.log(`Hello ${name}!`)
      res.status(204).send()
    })
    

    Go

    // PubSubMessage is the payload of a Pub/Sub event. Please refer to the docs for
    // additional information regarding Pub/Sub events.
    type PubSubMessage struct {
    	Message struct {
    		Data []byte `json:"data,omitempty"`
    		ID   string `json:"id"`
    	} `json:"message"`
    	Subscription string `json:"subscription"`
    }
    
    // HelloPubSub consumes a Pub/Sub message.
    func HelloPubSub(w http.ResponseWriter, r *http.Request) {
    	// Parse the Pub/Sub message.
    	var m PubSubMessage
    
    	if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
    		log.Printf("json.NewDecoder: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	name := string(m.Message.Data)
    	if name == "" {
    		name = "World"
    	}
    	log.Printf("Hello %s!", name)
    }
    

    正確な HTTP レスポンス コードを返すようにサービスをコーディングする必要があります。HTTP 200204 などの成功コードは、Cloud Pub/Sub メッセージの処理の完了を意味します。HTTP 400500 などのエラーコードは、push を使用したメッセージの受信で説明されているように、メッセージが再試行されることを示します。

  • サービスの動作環境を定義する DockerfileDockerfile の内容は言語によって異なります。

    Node.js

    # Use the official Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:10
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    RUN npm install --only=production
    
    # Copy local code to the container image.
    COPY . .
    
    # Service must listen to $PORT environment variable.
    # This default value facilitates local development.
    ENV PORT 8080
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Go

    # Use the offical Golang image to create a build artifact.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY . .
    
    # Build the outyet command inside the container.
    # (You may fetch or manage dependencies here,
    # either manually or with a tool like "godep".)
    RUN CGO_ENABLED=0 GOOS=linux go build -v -o hellopubsub
    
    # Use a Docker multi-stage build to create a lean production image.
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM alpine
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/hellopubsub .
    
    # Service must listen to $PORT environment variable.
    # This default value facilitates local development.
    ENV PORT 8080
    
    # Run the web service on container startup.
    CMD ["/hellopubsub"]
    

Cloud Pub/Sub リクエストの送信元を認証する方法については、以下の Cloud Pub/Sub との統合をご覧ください。

コードを配布する

コードの配布は 3 つのステップで行います。まず、Cloud Build でコンテナ イメージをビルドします。次に、そのイメージを Container Registry にアップロードします。最後に、アップロードしたイメージを Cloud Run または Cloud Run on GKE にデプロイします。

コードを配布するには:

  1. コンテナをビルドして、Container Registry に公開します。

    gcloud builds submit --tag gcr.io/[PROJECT-ID]/pubsub

    [PROJECT-ID] は GCP プロジェクト ID、pubsub はサービスに付ける名前です。

    ビルドが成功すると、ID、作成時間、画像の名前を含む SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。

  2. 次のコマンドを実行して、アプリをデプロイします。

    gcloud beta run deploy pubsub-tutorial --image gcr.io/[PROJECT-ID]/pubsub

    [PROJECT-ID] は GCP プロジェクト ID で置き換えます。pubsub はコンテナ名、pubsub-tutorial はサービス名です。コンテナ イメージは、サービス以外に、gcloud の設定で構成済みのリージョン(Cloud Run)またはクラスタ(Cloud Run on GKE)にデプロイされます。

    Cloud Run にデプロイする場合は、未承認を許可するプロンプトに n(いいえ)と答えます。サービスを非公開にすることで、Cloud Run と Cloud Pub/Sub の自動統合でリクエストの認証を行うことができます。構成方法の詳細については、Cloud Pub/Sub との統合をご覧ください。IAM ベースの認証の詳細については、アクセスの管理をご覧ください。

    デプロイが完了するまで待ちます。30 秒ほどかかる場合があります。デプロイが成功すると、コマンドラインに次のようなサービス URL が表示されます。この URL は、Cloud Pub/Sub サブスクリプションの構成で使用します。

  3. サービスにコードの更新をデプロイする場合は、上記のステップを繰り返します。サービスをデプロイするたびに、リビジョンが作成されます。準備ができると、トラフィックの送信が自動的に開始します。

Cloud Pub/Sub との統合

Cloud Run サービスをデプロイしたので、メッセージを push するように Cloud Pub/Sub を構成します。

Cloud Pub/Sub とサービスを統合するには:

  1. プロジェクトで Cloud Pub/Sub 認証トークンを作成できるようにします。

    gcloud projects add-iam-policy-binding [PROJECT-ID] \
         --member=serviceAccount:service-[PROJECT-NUMBER]@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    次のように置き換えます。

    • [PROJECT-ID] は、実際の GCP プロジェクト ID で置き換えます。
    • [PROJECT-NUMBER] は GCP プロジェクト番号で置き換えます。
  2. Cloud Pub/Sub サブスクリプション ID を表すサービス アカウントを作成または選択します。

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
         --display-name "Cloud Run Pub/Sub Invoker"

    cloud-run-pubsub-invoker を使用するか、Google Cloud Platform プロジェクト内で一意の名前で置き換えます。

  3. Cloud Run の場合は、このサービス アカウントに pubsub-tutorial サービスを呼び出す権限を付与します。

    gcloud beta run services add-iam-policy-binding pubsub-tutorial \
         --member=serviceAccount:cloud-run-pubsub-invoker@[PROJECT-ID].iam.gserviceaccount.com \
         --role=roles/run.invoker

    IAM の変更が反映されるまでに数分かかることがあります。その間、サービスログに HTTP 403 エラーが記録されることがあります。

  4. サービス アカウントで Cloud Pub/Sub サブスクリプションを作成します。

    gcloud beta pubsub subscriptions create myRunSubscription --topic myRunTopic \
       --push-endpoint=[SERVICE-URL]/ \
       --push-auth-service-account=cloud-run-pubsub-invoker@[PROJECT-ID].iam.gserviceaccount.com

    次のように置き換えます。

    • myRunTopic は、前に作成したトピックで置き換えます。
    • [SERVICE-URL] は、サービスのデプロイ時に提供された HTTPS URL で置き換えます。
    • [PROJECT-ID] は、実際の GCP プロジェクト ID で置き換えます。

    --push-account-service-account フラグを使用して、認証と承認で Cloud Pub/Sub push 機能を有効にします。

  5. Cloud Run にデプロイするときにデフォルトの run.app ドメインを使用した場合、サービス ドメインが自動的に登録され、同じプロジェクトの Cloud Pub/Sub サブスクリプションで使用できるようになります。カスタム ドメインを使用している場合は、ドメインの所有権を登録する必要があります。カスタム ドメインを使用する Cloud Run サービスに安全に push するようにサブスクリプションを構成するには、次のようにサブスクリプションを作成します。

    gcloud beta pubsub subscriptions create myRunSubscription --topic myRunTopic \
       --push-endpoint=https://[CUSTOM-DOMAIN] \
       --push-auth-service-account=cloud-run-pubsub-invoker@[PROJECT-ID].iam.gserviceaccount.com \
       --push-auth-token-audience=[SERVICE-URL]/

    次のように置き換えます。

    • myRunTopic は、前に作成したトピックで置き換えます。
    • [CUSTOM-DOMAIN] は、前にマッピングしたカスタム ドメインで置き換えます。
    • [PROJECT-ID] は、実際の GCP プロジェクト ID で置き換えます。
    • [SERVICE-URL] は、サービスのデプロイ時に提供された HTTPS URL で置き換えます。
  6. Cloud Run にデプロイすると、サービスに到達する前にすべての Cloud Pub/Sub リクエストが認証されます。高度なユースケースの場合、あるいは Cloud Run on GKE にデプロイした場合は、サービスの中で Cloud Pub/Sub リクエストの一部として送信された JSON ウェブトークンを確認します。詳しくは、認証と許可をご覧ください。

これで、サービスが Cloud Pub/Sub と完全に統合されました。

試してみる

エンドツーエンドのソリューションをテストするには:

  1. トピックに Cloud Pub/Sub メッセージを送信します。

    gcloud pubsub topics publish myRunTopic --message "Runner"

    このチュートリアルで説明したコマンドラインを使用する代わりに、プログラムでメッセージを発行することもできます。詳しくは、メッセージの公開をご覧ください。

  2. サービスログに移動します。

    1. Google Cloud Platform Console に移動します。
    2. pubsub-tutorial サービスをクリックします。
    3. [ログ] タブを選択します。

      ログが表示されるまで少し時間がかかることがあります。すぐに表示されない場合は、しばらくしてからもう一度確認してください。

  3. 「Hello Runner!」というメッセージを探します。

クリーンアップ

プロジェクトの削除

課金を停止する最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

プロジェクトを削除するには:

  1. GCP Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

Cloud Run サービスを削除する

Cloud Run サービスを削除しても、Cloud Storage に保存されているリソースは削除されません。削除する場合は、個別に削除する必要があります。

このチュートリアルでデプロイしたサービスを削除するには:

gcloud beta run services delete [SERVICE-NAME]

[SERVICE-NAME] は、選択したサービス名です。

Google Cloud Platform Console から Cloud Run または Cloud Run on GKE サービスを削除することもできます。

次のステップ