チュートリアル: Cloud Run サービスのローカル トラブルシューティング

このチュートリアルでは、サービス デベロッパーが Stackdriver ツールを使用して Cloud Run サービスの問題を検出し、ローカル開発ワークフローで調査を行う方法を説明します。

このトラブル シューティング ガイドの詳細な「ケーススタディ」では、デプロイ時にランタイム エラーが発生するサンプル プロジェクトを使用して、問題を発見および修正します。

目標

  • Cloud Run でサービスを書き込み、作成、ビルド、デプロイする
  • Error Reporting と Cloud Logging を使用してエラーを特定する
  • 根本原因を分析するため Container Registry からコンテナ イメージを取得する
  • 「本番環境」のサービスを修正し、今後の問題を軽減するためにサービスを改善する

費用

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

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを出すことができます。

新しい Cloud Platform ユーザーは無料トライアルをご利用いただけます。

始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

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

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

  4. Cloud Run API を有効にします。
  5. Cloud SDK をインストールして初期化します。
  6. コンポーネントを更新します。
    gcloud components update
  7. 手順に沿って、Docker をローカルにインストールします。

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

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

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

    gcloud config set project PROJECT_ID

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

  2. 選択したリージョン向けに gcloud を構成します。

    gcloud config set run/region REGION

    REGION は、任意のサポートされている Cloud Run のリージョンに置き換えます。

Cloud Run のロケーション

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

レイテンシ、可用性、耐久性の要件を満たしていることが、Cloud Run サービスを実行するリージョンを選択する際の主な判断材料になります。一般的には、ユーザーに最も近いリージョンを選択できますが、Cloud Run サービスで使用されている他の Google Cloud サービスのロケーションも考慮する必要があります。使用する Google Cloud プロダクトが複数のロケーションにまたがっていると、サービスの料金だけでなくレイテンシにも影響します。

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

ティア 1 料金を適用

  • asia-east1(台湾)
  • asia-northeast1(東京)
  • asia-northeast2(大阪)
  • europe-north1(フィンランド) リーフアイコン 最低 CO2
  • europe-west1(ベルギー)
  • europe-west4(オランダ)
  • us-central1(アイオワ) リーフアイコン 最低 CO2
  • us-east1(サウスカロライナ)
  • us-east4(北バージニア)
  • us-west1(オレゴン) リーフアイコン 最低 CO2

ティア 2 料金を適用

  • asia-east2(香港)
  • asia-northeast3(ソウル、韓国)
  • asia-southeast1(シンガポール)
  • asia-southeast2 (ジャカルタ)
  • asia-south1(ムンバイ、インド)
  • asia-south2(デリー、インド)
  • australia-southeast1(シドニー)
  • australia-southeast2(メルボルン)
  • europe-central2(ワルシャワ、ポーランド)
  • europe-west2(ロンドン、イギリス)
  • europe-west3(フランクフルト、ドイツ)
  • europe-west6(チューリッヒ、スイス) リーフアイコン 最低 CO2
  • northamerica-northeast1(モントリオール) リーフアイコン 最低 CO2
  • southamerica-east1(ブラジル、サンパウロ) リーフアイコン 最低 CO2
  • us-west2(ロサンゼルス)
  • us-west3(ラスベガス)
  • us-west4(ソルトレイクシティ)

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

コードを組み立てる

新しい Cloud Run のサービスを段階的にビルドします。このサービスでは、トラブル シューティングの演習のためにランタイム エラーを発生させます。

  1. 新しいプロジェクトを作成します。

    Node.js

    サービス パッケージ、初期依存関係、および一般的なオペレーションを定義して、Node.js プロジェクトを作成します。

    1. hello-service ディレクトリを新規作成します。

      mkdir hello-service
      cd hello-service
      
    2. package.json ファイルを生成して、Node.js プロジェクトを新規作成します。

      npm init --yes
      npm install --save express@4
      
    3. エディタで新しい package.json ファイルを開き、node index.js を実行するように start スクリプトを構成します。完了すると、ファイルは次のような状態になります。

      {
        "name": "hello-service",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
            "start": "node index.js",
            "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [],
        "author": "",
        "license": "ISC",
        "dependencies": {
            "express": "^4.17.1"
        }
      }

    このサービスをこのチュートリアルからさらに進化させる場合は、説明、作成者を入力してライセンスを評価します。詳しくは、package.json のドキュメントをご覧ください。

    Python

    1. hello-service ディレクトリを新規作成します。

      mkdir hello-service
      cd hello-service
      
    2. requirements.txt ファイルを作成し、依存関係をコピーします。

      Flask==2.0.1
      pytest==5.3.0; python_version > "3.0"
      pytest==4.6.6; python_version < "3.0"
      gunicorn==20.1.0
      

    Go

    1. hello-service ディレクトリを新規作成します。

      mkdir hello-service
      cd hello-service
      
    2. 新しい go モジュールを初期化して Go プロジェクトを作成します。

      go mod init example.com/hello-service
      

    名前は必要に応じて更新できます。コードがウェブ到達可能なコード リポジトリに公開されている場合は、名前を更新する必要があります。

    Java

    1. 新しい maven プロジェクトを作成します。

      mvn archetype:generate \
        -DgroupId=com.example.cloudrun \
        -DartifactId=hello-service \
        -DarchetypeArtifactId=maven-archetype-quickstart \
        -DinteractiveMode=false
      
    2. 依存関係を pom.xml 依存関係リストにコピーします(<dependencies> 要素間)。

      <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.9.3</version>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.3.0-alpha5</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.32</version>
      </dependency>
      
    3. ビルド設定を pom.xml<dependencies> 要素の下)にコピーします。

      <build>
        <plugins>
          <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>jib-maven-plugin</artifactId>
            <version>3.1.2</version>
            <configuration>
              <to>
                <image>gcr.io/PROJECT_ID/hello-service</image>
              </to>
            </configuration>
          </plugin>
        </plugins>
      </build>
      

  2. 受信リクエストを処理する HTTP サービスを作成します。

    Node.js

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('hello: received request.');
    
      const {NAME} = process.env;
      if (!NAME) {
        // Plain error logs do not appear in Stackdriver Error Reporting.
        console.error('Environment validation failed.');
        console.error(new Error('Missing required server parameter'));
        return res.status(500).send('Internal Server Error');
      }
      res.send(`Hello ${NAME}!`);
    });
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log(`hello: listening on port ${port}`);
    });

    Python

    import json
    import os
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/", methods=["GET"])
    def index():
        print("hello: received request.")
    
        NAME = os.getenv("NAME")
    
        if not NAME:
            print("Environment validation failed.")
            raise Exception("Missing required service parameter.")
    
        return f"Hello {NAME}"
    
    if __name__ == "__main__":
        PORT = int(os.getenv("PORT")) if os.getenv("PORT") else 8080
    
        # This is used when running locally. Gunicorn is used to run the
        # application on Cloud Run. See entrypoint in Dockerfile.
        app.run(host="127.0.0.1", port=PORT, debug=True)

    Go

    
    // Sample hello demonstrates a difficult to troubleshoot service.
    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	log.Print("hello: service started")
    
    	http.HandleFunc("/", helloHandler)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    
    	log.Printf("Listening on port %s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    
    func helloHandler(w http.ResponseWriter, r *http.Request) {
    	log.Print("hello: received request")
    
    	name := os.Getenv("NAME")
    	if name == "" {
    		log.Printf("Missing required server parameter")
    		// The panic stack trace appears in Cloud Error Reporting.
    		panic("Missing required server parameter")
    	}
    
    	fmt.Fprintf(w, "Hello %s!\n", name)
    }
    

    Java

    import static spark.Spark.get;
    import static spark.Spark.port;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class App {
    
      private static final Logger logger = LoggerFactory.getLogger(App.class);
    
      public static void main(String[] args) {
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
        port(port);
    
        get(
            "/",
            (req, res) -> {
              logger.info("Hello: received request.");
              String name = System.getenv("NAME");
              if (name == null) {
                // Standard error logs do not appear in Stackdriver Error Reporting.
                System.err.println("Environment validation failed.");
                String msg = "Missing required server parameter";
                logger.error(msg, new Exception(msg));
                res.status(500);
                return "Internal Server Error";
              }
              res.status(200);
              return String.format("Hello %s!", name);
            });
      }
    }

  3. Dockerfile を作成して、サービスのデプロイに使用するコンテナ イメージを定義します。

    Node.js

    
    # Use the official lightweight Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:12-slim
    
    # 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 copying both package.json AND package-lock.json (when available).
    # Copying this first prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    # If you add a package-lock.json, speed your build by switching to 'npm ci'.
    # RUN npm ci --only=production
    RUN npm install --only=production
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Python

    
    # Use the official Python image.
    # https://hub.docker.com/_/python
    FROM python:3.9
    
    # Allow statements and log messages to immediately appear in the Cloud Run logs
    ENV PYTHONUNBUFFERED True
    
    # Copy application dependency manifests to the container image.
    # Copying this separately prevents re-running pip install on every code change.
    COPY requirements.txt ./
    
    # Install production dependencies.
    RUN pip install -r requirements.txt
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . ./
    
    # Run the web service on container startup.
    # Use gunicorn webserver with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    # Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
    

    Go

    
    # Use the offical golang image to create a binary.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang:1.16-buster as builder
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Retrieve application dependencies.
    # This allows the container build to reuse cached dependencies.
    # Expecting to copy go.mod and if present go.sum.
    COPY go.* ./
    RUN go mod download
    
    # Copy local code to the container image.
    COPY . ./
    
    # Build the binary.
    RUN go build -v -o server
    
    # Use the official Debian slim image for a lean production container.
    # https://hub.docker.com/_/debian
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM debian:buster-slim
    RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
        ca-certificates && \
        rm -rf /var/lib/apt/lists/*
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/server /server
    
    # Run the web service on container startup.
    CMD ["/server"]
    

    Java

    このサンプルでは、Jib を使用して一般的な Java ツールにより Docker イメージをビルドします。Jib は、Dockerfile や Docker をインストールせずにコンテナのビルドを最適化します。Jib を使用して Java コンテナを構築する方法の詳細を確認します。

    <plugin>
      <groupId>com.google.cloud.tools</groupId>
      <artifactId>jib-maven-plugin</artifactId>
      <version>3.1.2</version>
      <configuration>
        <to>
          <image>gcr.io/PROJECT_ID/hello-service</image>
        </to>
      </configuration>
    </plugin>
    

コードを配布する

コードの配布は、Cloud Build でコンテナ イメージをビルドする、Container Registry にコンテナ イメージをアップロードする、Cloud Run にコンテナ イメージをデプロイするという 3 つのステップで構成されます。

コードを配布するには、次の手順を行います。

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

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT_ID は、GCP のプロジェクト ID です。現在のプロジェクト ID は、gcloud config get-value project で確認できます。

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

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT_ID は、GCP のプロジェクト ID です。現在のプロジェクト ID は、gcloud config get-value project で確認できます。

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

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT_ID は、GCP のプロジェクト ID です。現在のプロジェクト ID は、gcloud config get-value project で確認できます。

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

    Java

    1. Docker を承認して Container Registry に push するには、gcloud 認証ヘルパーを使用します。
      gcloud auth configure-docker
    2. Jib Maven プラグインを使用して、コンテナをビルドして Container Registry に push します。
      mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service

    PROJECT_ID は、GCP のプロジェクト ID です。現在のプロジェクト ID は、gcloud config get-value project で確認できます。

    ビルドが成功すると、BUILD SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。

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

    gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service

    PROJECT_ID は実際の GCP プロジェクト ID に置き換えます。hello-service は、コンテナ イメージ名と Cloud Run サービスの名前の両方です。コンテナ イメージは、gcloud の設定で構成したサービスとリージョンにデプロイされることに注意してください。

    未認証の許可を求めるプロンプトに対して y(はい)と答えます。IAM ベースの認証の詳細については、アクセスの管理をご覧ください。

    デプロイが完了するまで待ちます。30 秒ほどかかる場合があります。成功すると、コマンドラインにサービス URL が表示されます。

試してみる

サービスを試してみて、サービスが正常にデプロイされたことを確認します。リクエストは HTTP 500 エラーまたは HTTP 503 エラー(クラス 5xx サーバーエラーのメンバー)で失敗します。このチュートリアルでは、このエラー レスポンスのトラブルシューティングについて説明します。

サービスにはナビゲーション可能な URL が自動的に割り当てられます。

  1. ウェブブラウザで次の URL に移動します。

    1. ウェブブラウザを開きます。

    2. 前に実行した deploy コマンドで出力されたサービス URL を見つけます。

      deploy コマンドが URL を提供しなかった場合、何か問題が発生しています。エラーメッセージを確認し、その内容に応じて対処します。対処可能なガイダンスがない場合は、トラブルシューティング ガイドを確認し、デプロイ コマンドを再試行します。

    3. この URL をブラウザのアドレスバーにコピーして、ENTER を押します。

  2. HTTP 500 エラーまたは HTTP 503 エラーを確認します。

    HTTP 403 エラーが表示された場合は、デプロイのプロンプトで allow unauthenticated invocations を拒否した可能性があります。認証されていないサービスへのアクセスを許可して、問題を解決します。

    gcloud run services add-iam-policy-binding hello-service \
      --member="allUsers" \
      --role="roles/run.invoker"
    

詳しくは、公開(未認証)アクセスを許可するをご覧ください。

問題の調査

前述の試してみるで発生した HTTP 5xx エラーが、本番環境ランタイム エラーとして発生したことを表示で確認してください。このチュートリアルでは、正式な処理プロセスを説明します。本番環境でのエラー解決プロセスは大きく異なりますが、このチュートリアルでは、有用なツールとテクニックを利用するための特定の手順を示します。

この問題を調査するには、以下のフェーズを実施します。

  • 報告されたエラーの詳細を収集し、さらに調査を進め、軽減策を策定します。
  • 修正を進めるか、既知の正常なバージョンにロールバックして、ユーザーへの影響を軽減させます。
  • 正確な詳細が収集されたことと、1 回限りのエラーではないことを確認するために、エラーを再現します。
  • バグの根本原因を分析して、このエラーの原因となったコード、構成、プロセスを特定します。

調査を開始すると、URL、タイムスタンプ、「Internal Server Error」というメッセージが表示されます。

詳細の収集

問題の詳細情報を収集して何が起きたのかを理解し、次のステップを決定します。

利用可能な Stackdriver ツールを使用して詳細を収集します。

  1. Error Reporting コンソールを使用します。これにより、認識されたスタック トレースを含むエラーの詳細と繰り返しトラッキングがダッシュボードに表示されます。

    Error Reporting コンソールに移動

    「解決ステータス」、「発生回数」、「エラー」、「表示」の列からなるエラーリストのスクリーン ショット
    記録されたエラーのリストエラーは、リビジョン、サービス、プラットフォームのメッセージ内容でグループ化されます。
  2. スタック トレースの詳細を表示するには、エラーをクリックします。このとき、関数呼び出しはエラーの直前であることに留意します。

    単一の解析済みスタック トレースのスクリーン ショットで、このエラーの一般的なプロファイルを示すもの
    エラー詳細ページの「スタック トレース サンプル」にエラーのインスタンスが 1 つ表示されます。それぞれのインスタンスを確認できます。
  3. Cloud Logging を使用して、問題につながる一連の操作を確認します。確認対象には、確認されたエラースタック トレースが存在しないために Error Reporting コンソールに表示されないエラー メッセージが含まれます。

    Cloud Logging コンソールに移動

    最初のプルダウン ボックスで、[Cloud Run のリビジョン] > [hello-service] の順に選択します。これにより、ログエントリはサービスによって生成されたものに絞り込まれます。

Cloud Run でのログの表示に関する詳細をご覧ください。

正常なバージョンへのロールバック

これが確立されたサービスであり、動作することがわかっている場合は、Cloud Run のサービスの以前のリビジョンがあります。このチュートリアルでは、以前のバージョンを使用しない新しいサービスを使用するため、ロールバックはできません。

ただし、ロールバックできる以前のバージョンのサービスを利用している場合は、リビジョンの詳細の表示に従って、サービスの新しいワーキング デプロイを作成するために必要なコンテナ名と構成の詳細を抽出します。

エラーを再現する

以前に取得した詳細を使用して、テスト条件で一貫して問題が発生していることを確認します。

同じ HTTP リクエストをもう一度試すことで送信し、同じエラーと詳細が報告されるかどうかを確認します。エラーの詳細が表示されるまでに時間がかかることがあります。

このチュートリアルのサンプル サービスは読み取り専用であり、複雑な副次的影響をトリガーしないため、本番環境でのエラーの再現は安全です。ただし、実際のサービスの多くには上記が当てはまりません。テスト環境でエラーを再現するか、ローカル調査に限定する必要があります。

エラーを再現することで、後続作業のコンテキストが明確になります。たとえば、デベロッパーがエラーを再現できない場合、サービスに対してインストゥルメンテーションの追加が必要になる場合があります。

根本原因の分析を行う

根本原因の分析は、効果的なトラブルシューティングにおいて、症状ではなく問題を解決するための重要なステップです。

このチュートリアルでは、Cloud Run で問題を再現し、Cloud Run でサービスがホストされているときに問題がアクティブであることを確認しました。次に、問題をローカルで再現させてみて、問題がコードに限定されているか、本番環境のホスティングでのみ発生しているかを判断します。

  1. Container Registry で Docker CLI をローカルで使用していない場合は、gcloud で認証します。

    gcloud auth configure-docker

    別の方法については、Container Registry の認証方法をご覧ください。

  2. 最近使用したコンテナ イメージ名が利用できない場合、サービスの説明に最近デプロイしたコンテナ イメージの情報があります。

    gcloud run services describe hello-service

    spec オブジェクト内部のコンテナ イメージ名を探します。よりターゲットを絞ったコマンドを使用すれば、直接取得することもできます。

    gcloud run services describe hello-service \
       --format="value(spec.template.spec.containers.image)"

    このコマンドは、gcr.io/PROJECT_ID/hello-service のようなコンテナ イメージ名を明らかにします。

  3. コンテナ イメージを Container Registry から環境に pull します。この手順では、コンテナ イメージをダウンロードするため、数分かかることがあります。

    docker pull gcr.io/PROJECT_ID/hello-service

    この名前を再利用するコンテナ イメージに対するその後の更新は、同じコマンドで取得できます。この手順をスキップすると、次の docker run コマンドはコンテナ イメージを pull します(ローカルマシンにない場合)。

  4. ローカルで実行して、Cloud Run に固有の問題ではないことを確認します。

    PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       gcr.io/PROJECT_ID/hello-service

    上記のコマンドを要素に分解してみます。

    • PORT 環境変数は、コンテナ内でリッスンするポートを指定するためサービスが使用します。
    • run コマンドはコンテナを起動します。デフォルトでは、Dockerfile または親コンテナ イメージで定義されたエントリポイントのコマンドになります。
    • --rm フラグは、終了時にコンテナ インスタンスを削除します。
    • -e フラグは、環境変数に値を割り当てます。-e PORT=$PORT は、PORT 変数をローカル システムから同じ変数名のコンテナに移動します。
    • -p フラグは、ローカルホストのポート 9,000 でコンテナをサービスとして公開します。localhost: 9000 へのリクエストは、ポート 8080 でコンテナにルーティングされます。つまり、使用中のポート番号に関するサービスからの出力は、サービスへのアクセス方法と一致しません。
    • 最後の引数 gcr.io/PROJECT_ID/hello-service はコンテナ イメージ tag です。これは、コンテナ イメージの SHA256 ハッシュ識別子を表す、人が読める形式のラベルです。ローカルで入手できない場合、Docker はリモート レジストリからイメージを取得しようとします。

    ブラウザで http://localhost:9000 を開きます。ターミナルの出力で、Google Cloud のオペレーション スイートのエラー メッセージと一致するエラー メッセージをチェックします。

    問題がローカルで再現できない場合、Cloud Run 環境固有の問題である可能性があります。Cloud Run のトラブルシューティング ガイドで、調査する特定の領域を確認します。

    この場合、エラーはローカルで再現されます。

エラーが永続的なものとして二重に確認され、またホスティング プラットフォームではなくサービスコードが原因と判明したため、コードを詳しく調査します。

このチュートリアルでは、コンテナ内のコードとローカル システムのコードが同じであることを前提にしています。

エラーレポートにあるスタック トレースを再度確認し、コードと見比べて問題のある特定の行を見つけます。

Node.js

ログに表示されるスタック トレースで示されている行番号の近くにある index.js ファイルで、エラー メッセージのソースを見つけます。
const {NAME} = process.env;
if (!NAME) {
  // Plain error logs do not appear in Stackdriver Error Reporting.
  console.error('Environment validation failed.');
  console.error(new Error('Missing required server parameter'));
  return res.status(500).send('Internal Server Error');
}

Python

ログに表示されるスタック トレースで示されている行番号の近くにある main.py ファイルで、エラー メッセージのソースを見つけます。
NAME = os.getenv("NAME")

if not NAME:
    print("Environment validation failed.")
    raise Exception("Missing required service parameter.")

Go

ログに表示されるスタック トレースで示されている行番号の近くにある main.go ファイルで、エラー メッセージのソースを見つけます。

name := os.Getenv("NAME")
if name == "" {
	log.Printf("Missing required server parameter")
	// The panic stack trace appears in Cloud Error Reporting.
	panic("Missing required server parameter")
}

Java

ログに表示されるスタック トレースで示されている行番号の近くにある App.java ファイルで、エラー メッセージのソースを見つけます。

String name = System.getenv("NAME");
if (name == null) {
  // Standard error logs do not appear in Stackdriver Error Reporting.
  System.err.println("Environment validation failed.");
  String msg = "Missing required server parameter";
  logger.error(msg, new Exception(msg));
  res.status(500);
  return "Internal Server Error";
}

このコードを調べると、NAME 環境変数が設定されていない場合、次のアクションが実施されます。

  • エラーが Google Cloud のオペレーション スイートに記録される。
  • HTTP エラー レスポンスが送信される。

問題の原因は変数がないことですが、根本的な原因はより具体的です。環境変数への強い依存関係を追加するコード変更で、デプロイ スクリプトおよびランタイム要件のドキュメントに対して関連する変更をしなかったからです。

根本原因の修正

これでコードを収集し潜在的な原因を特定できたので、修正の手順を行います。

  • サービスが、そこで利用できる NAME 環境でローカルで動作するかどうかを確認します。

    1. 環境変数を追加してコンテナをローカルで実行します。

      PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       -e NAME="Local World!" \
       gcr.io/PROJECT_ID/hello-service
    2. ブラウザで http://localhost:9000 に移動します。

    3. ページに「Hello Local World!」が表示されるのを確認します。

  • 実行中の Cloud Run サービス環境を変更して、この変数を含めます。

    1. サービス更新コマンドを実行して、環境変数を追加します。

      gcloud run services update hello-service \
        --set-env-vars NAME=Override
      
    2. Cloud Run が以前のリビジョンに基づき、追加された新しい環境変数を含む新しいリビジョンを作成するまで数秒待ちます。

  • サービスが修正されたことを確認します。

    1. ブラウザで Cloud Run サービスの URL に移動します。
    2. ページに「Hello Override!」が表示されるのを確認します。
    3. Cloud Logging または Error Reporting に予期しないメッセージやエラーが表示されないことを確認します。

今後のトラブルシューティングの速度向上

この本番環境の問題のサンプルでは、エラーは運用構成に関連していました。この問題による将来の影響を最小限に抑えるようなコード変更があります。

  • エラーログを改善して、より具体的な情報を伝えます。
  • エラーを返す代わりに、サービスを安全なデフォルトに戻します。デフォルトの使用が通常の機能への変更を意味する場合、モニタリングのため警告メッセージを使用します。

依存度が高い NAME 環境変数を削除する手順を説明します。

  1. 既存の NAME 処理コードを削除します。

    Node.js

    const {NAME} = process.env;
    if (!NAME) {
      // Plain error logs do not appear in Stackdriver Error Reporting.
      console.error('Environment validation failed.');
      console.error(new Error('Missing required server parameter'));
      return res.status(500).send('Internal Server Error');
    }

    Python

    NAME = os.getenv("NAME")
    
    if not NAME:
        print("Environment validation failed.")
        raise Exception("Missing required service parameter.")

    Go

    name := os.Getenv("NAME")
    if name == "" {
    	log.Printf("Missing required server parameter")
    	// The panic stack trace appears in Cloud Error Reporting.
    	panic("Missing required server parameter")
    }

    Java

    String name = System.getenv("NAME");
    if (name == null) {
      // Standard error logs do not appear in Stackdriver Error Reporting.
      System.err.println("Environment validation failed.");
      String msg = "Missing required server parameter";
      logger.error(msg, new Exception(msg));
      res.status(500);
      return "Internal Server Error";
    }

  2. フォールバック値を設定する新しいコードを追加します。

    Node.js

    const NAME = process.env.NAME || 'World';
    if (!process.env.NAME) {
      console.log(
        JSON.stringify({
          severity: 'WARNING',
          message: `NAME not set, default to '${NAME}'`,
        })
      );
    }

    Python

    NAME = os.getenv("NAME")
    
    if not NAME:
        NAME = "World"
        error_message = {
            "severity": "WARNING",
            "message": f"NAME not set, default to {NAME}",
        }
        print(json.dumps(error_message))

    Go

    name := os.Getenv("NAME")
    if name == "" {
    	name = "World"
    	log.Printf("warning: NAME not set, default to %s", name)
    }

    Java

    String name = System.getenv().getOrDefault("NAME", "World");
    if (System.getenv("NAME") == null) {
      logger.warn(String.format("NAME not set, default to %s", name));
    }

  3. 影響を受けた構成ケースからコンテナを再ビルドして実行し、ローカルでテストします。

    Node.js

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Python

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Go

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Java

    mvn compile jib:build

    NAME 環境変数が引き続き機能することを確認します。

    PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \
     -e NAME="Robust World" \
     gcr.io/PROJECT_ID/hello-service

    NAME 変数なしでサービスが機能することを確認します。

    PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \
     gcr.io/PROJECT_ID/hello-service

    サービスが結果を返さない場合、最初のステップでのコード削除で、レスポンスの書き込みに使用されるような余分な行まで削除していないか確認します。

  4. コードをデプロイするセクションに戻ってこれをデプロイします。

    サービスをデプロイするたびに、リビジョンが作成されます。準備ができると、トラフィックの送信が自動的に開始します。

    前に設定した環境変数をクリアするには、次の手順を行います。

    gcloud run services update hello-service --clear-env-vars

サービスの自動テスト カバレッジに、デフォルト値の新しい機能を追加します。

ログ内でその他の問題を検索

このサービスのログビューアには、その他の問題がある場合があります。たとえば、サポートされていないシステムコールは、「Container Sandbox Limitation」(コンテナ サンドボックスの制限)としてログに表示されます。

たとえば、Node.js サービスはこのようなログメッセージを残すことがあります。

Container Sandbox Limitation: Unsupported syscall statx(0xffffff9c,0x3e1ba8e86d88,0x0,0xfff,0x3e1ba8e86970,0x3e1ba8e86a90). Please, refer to https://gvisor.dev/c/linux/amd64/statx for more information.

この場合、サポート対象外でも hello-service サンプル サービスには影響ありません。

クリーンアップ

このチュートリアル用に新規プロジェクトを作成した場合は、そのプロジェクトを削除します。既存のプロジェクトを使用し、このチュートリアルで変更を加えずに残す場合は、チュートリアル用に作成したリソースを削除します。

プロジェクトを削除する

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

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

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

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

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

チュートリアル リソースを削除する

  1. このチュートリアルでデプロイした Cloud Run サービスを削除します。

    gcloud run services delete SERVICE-NAME

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

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

  2. チュートリアルの設定時に追加した gcloud のデフォルト リージョン構成を削除します。

     gcloud config unset run/region
    
  3. プロジェクト構成を削除します。

     gcloud config unset project
    
  4. このチュートリアルで作成した他の Google Cloud リソースを削除します。

次のステップ