ローカル トラブルシューティング

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

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

Google Cloud のオペレーション スイートのサポート制限により、このチュートリアルは Cloud Run for Anthos on VMware では使用できません。

目標

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

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

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

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

  3. Google Cloud プロジェクトで課金が有効になっていることを確認します

  4. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

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

  5. Google Cloud プロジェクトで課金が有効になっていることを確認します

  6. Cloud Run for Anthos API を有効にする
  7. Google Cloud CLI をインストールして初期化します。
  8. kubectl コンポーネントをインストールします。
    gcloud components install kubectl
  9. コンポーネントを更新します。
    gcloud components update
  10. Cloud Run for Anthos を使用している場合は、Cloud Run for Anthos の設定の手順に沿って新しいクラスタを作成します。
  11. Cloud Run for Anthos を使用している場合は、curl をインストールしてサービスをお試しください。
  12. 手順に沿って、Docker をローカルにインストールします。

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

gcloud に Cloud Run for Anthos サービス用のデフォルトを構成するには、次のようにします。

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

    gcloud config set project PROJECT_ID

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

  2. クラスタに gcloud を構成します。

    gcloud config set run/platform gke
    gcloud config set run/cluster CLUSTER-NAME
    gcloud config set run/cluster_location REGION

    以下のように置き換えます。

    • CLUSTER-NAME は、クラスタに対して使用した名前に置き換えます。
    • REGION は、選択したサポート対象のクラスタの場所に置き換えます。

コードを組み立てる

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

  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.1.0
      pytest==7.0.1; python_version > "3.0"
      # pin pytest to 4.6.11 for Python2.
      pytest==7.0.1; 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 \
        -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-alpha11</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
      </dependency>
      
    3. ビルド設定を pom.xml<dependencies> 要素の下)にコピーします。

      <build>
        <plugins>
          <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>jib-maven-plugin</artifactId>
            <version>3.2.1</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 = parseInt(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:18-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.10
    
    # 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.17-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.2.1</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

    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 for Anthos サービスの名前の両方です。コンテナ イメージは、gcloud の設定で構成したサービスとクラスタにデプロイされることに注意してください。

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

試してみる

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

クラスタにルーティング可能なデフォルト ドメインが構成されている場合は、上記の手順をスキップして、URL をウェブブラウザにコピーします。

自動 TLS 証明書ドメイン マッピングを使用しない場合、サービスにナビゲーション可能な URL は提供されません。

その代わりに、指定された URL とサービスの入力ゲートウェイの IP アドレスを使用して、サービスにリクエストする curl コマンドを作成します。

  1. Istio Ingress ゲートウェイの外部 IP を取得するには:
    kubectl get svc istio-ingress -n gke-system
    
    次のような出力が生成されます。
    NAME            TYPE           CLUSTER-IP     EXTERNAL-IP  PORT(S)
    istio-ingress   LoadBalancer   XX.XX.XXX.XX   pending      80:32380/TCP,443:32390/TCP,32400:32400/TCP
    
    ロードバランサの EXTERNAL-IP は、使用する必要がある IP アドレスです。
  2. URL でこの GATEWAY_IP アドレスを使用して、curl コマンドを実行します。

     curl -G -H "Host: SERVICE-DOMAIN" https://EXTERNAL-IP/

    SERVICE-DOMAIN は、サービスにデフォルトで割り当てられているドメインに置き換えます。そのためにデフォルトの URL を取得し、プロトコル http:// を削除します。

  3. HTTP 500 または HTTP 503 のエラー メッセージを確認してください。

問題の調査

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

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

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

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

詳細の収集

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

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

  1. ログを表示して詳細を確認します。

  2. Cloud Logging を使用して、エラー メッセージなど、問題につながる一連の操作を確認します。

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

動作していたことがわかっているリビジョンがある場合は、サービスをロールバックしてそのリビジョンを使用できます。たとえば、このチュートリアルでデプロイした新しい hello-service サービスには 1 つのリビジョンしかないため、ロールバックはできません。

リビジョンを見つけてサービスをロールバックするには、次のようにします。

  1. サービスのすべてのリビジョンを一覧表示します

  2. すべてのトラフィックを正常なリビジョンに移行します

エラーを再現する

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

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

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

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

根本原因の分析を行う

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

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

  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 for Anthos に固有の問題ではないことを確認します。

    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 は、コンテナ イメージの最新バージョンを指すリポジトリパスです。ローカルで入手できない場合、Docker はリモート レジストリからイメージを取得しようとします。

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

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

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

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

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

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 for Anthos サービス環境を変更して、この変数を含めます。

    1. --update-env-vars パラメータを指定して services update コマンドを実行し、環境変数を追加します。

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

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

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

今後のトラブルシューティングの改善

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

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

依存度が高い 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. Google Cloud コンソールで、[リソースの管理] ページに移動します。

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

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

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

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

    gcloud run services delete SERVICE-NAME

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

    Google Cloud コンソールから Cloud Run for Anthos のサービスを削除することもできます。

    Cloud Run for Anthos に移動

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

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

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

次のステップ

  • Cloud Logging を使用して本番環境での動作を分析する方法を学習する。
  • Cloud Run for Anthos のトラブルシューティングの詳細については、[/anthos/run/archive/docs/troubleshooting#sandbox] をご覧ください。
  • Google Cloud に関するリファレンス アーキテクチャ、図、チュートリアル、ベスト プラクティスを確認する。Cloud Architecture Center を確認します。