このチュートリアルでは、Stackdriver ツールで問題を検出してローカル開発ワークフローで調査し、破損した Knative serving サービスのトラブルシューティングを行います。
このトラブル シューティング ガイドの詳細な「ケーススタディ」では、デプロイ時にランタイム エラーが発生するサンプル プロジェクトを使用して、問題を発見および修正します。
目標
- Knative serving にサービスを作成、ビルド、デプロイする
- Cloud Logging を使用してエラーを特定する
- 根本原因を分析するため Container Registry からコンテナ イメージを取得する
- 「本番環境」のサービスを修正し、今後の問題を軽減するためにサービスを改善する
費用
このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。
料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。
始める前に
- このチュートリアルは、Knative serving がクラスタにインストールされ、構成されていることを前提としています。
- コマンドライン環境が設定され、ツールが最新であることを確認してください。
- サービスを試用するために、curl をインストールします。
- Docker をローカルにインストールします。
コードを組み立てる
新しい Knative serving サービスを段階的にビルドします。このサービスでは、トラブル シューティングの演習のためにランタイム エラーを発生させます。
新しいプロジェクトを作成します。
Node.js
サービス パッケージ、初期依存関係、および一般的なオペレーションを定義して、Node.js プロジェクトを作成します。hello-service
ディレクトリを作成します。mkdir hello-service cd hello-service
package.json
ファイルを生成します。npm init --yes npm install --save express@4
エディタで新しい
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
hello-service
ディレクトリを新規作成します。mkdir hello-service cd hello-service
requirements.txt ファイルを作成し、依存関係をコピーします。
Go
hello-service
ディレクトリを作成します。mkdir hello-service cd hello-service
新しい go モジュールを初期化して Go プロジェクトを作成します。
go mod init <var>my-domain</var>.com/hello-service
名前は必要に応じて更新できます。コードがウェブ到達可能なコード リポジトリに公開されている場合は、名前を更新する必要があります。
Java
maven プロジェクトを作成します。
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=hello-service \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
依存関係を
pom.xml
依存関係リストにコピーします(<dependencies>
要素間)。ビルド設定を
pom.xml
(<dependencies>
要素の下)にコピーします。
受信リクエストを処理する HTTP サービスを作成します。
Node.js
Python
Go
Java
Dockerfile
を作成して、サービスのデプロイに使用するコンテナ イメージを定義します。Node.js
Python
Go
Java
このサンプルでは、Jib を使用して一般的な Java ツールにより Docker イメージをビルドします。Jib は、Dockerfile や Docker をインストールせずにコンテナのビルドを最適化します。Jib を使用して Java コンテナを構築する方法の詳細を確認します。
コードを配布する
コードの配布は、Cloud Build でコンテナ イメージをビルドする、Container Registry にコンテナ イメージをアップロードする、Knative serving にコンテナ イメージをデプロイする、という 3 つのステップで構成されます。
コードを配布するには:
コンテナをビルドして、Container Registry に公開します。
Node.js
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
ここで PROJECT_ID は、Google Cloud プロジェクト ID です。現在のプロジェクト ID は、
gcloud config get-value project
で確認できます。ビルドが成功すると、ID、作成時間、イメージ名を含む SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。
Python
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
ここで PROJECT_ID は、Google Cloud プロジェクト ID です。現在のプロジェクト ID は、
gcloud config get-value project
で確認できます。ビルドが成功すると、ID、作成時間、イメージ名を含む SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。
Go
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
ここで PROJECT_ID は、Google Cloud プロジェクト 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 は、Google Cloud プロジェクト ID です。現在のプロジェクト ID は、
gcloud config get-value project
で確認できます。ビルドが成功すると、BUILD SUCCESS メッセージが表示されます。イメージが Container Registry に保存されます。このイメージは必要に応じて再利用できます。
次のコマンドを実行して、アプリをデプロイします。
gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service
PROJECT_ID を Google Cloud プロジェクト ID に置き換えます。
hello-service
は、コンテナ イメージ名と Knative serving サービスの名前の両方です。コンテナ イメージは、gcloud の設定で構成したサービスとクラスタにデプロイされることに注意してください。デプロイが完了するまで待ちます。30 秒ほどかかる場合があります。成功すると、コマンドラインにサービス URL が表示されます。
試してみる
サービスを試してみて、サービスが正常にデプロイされたことを確認します。リクエストは HTTP 500 エラーまたは HTTP 503 エラー(クラス 5xx サーバーエラーのメンバー)で失敗します。このチュートリアルでは、このエラー レスポンスのトラブルシューティングについて説明します。
クラスタにルーティング可能なデフォルト ドメインが構成されている場合は、上記の手順をスキップして、URL をウェブブラウザにコピーします。
自動 TLS 証明書とドメイン マッピングを使用しない場合、サービスにナビゲーション可能な URL は提供されません。
その代わりに、指定された URL とサービスの入力ゲートウェイの IP アドレスを使用して、サービスにリクエストする curl
コマンドを作成します。
-
ロードバランサの外部 IP を取得するには、次のコマンドを実行します。
kubectl get svc istio-ingressgateway -n ASM-INGRESS-NAMESPACE
ASM-INGRESS-NAMESPACE は、Cloud Service Mesh Ingress が配置されている Namespace に置き換えます。Cloud Service Mesh をデフォルトの構成を使用してインストールした場合は、
istio-system
を指定します。出力は次のようになります。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) istio-ingressgateway LoadBalancer XX.XX.XXX.XX pending 80:32380/TCP,443:32390/TCP,32400:32400/TCP
ここで、EXTERNAL-IP の値は、ロードバランサの外部 IP アドレスです。
URL でこの
GATEWAY_IP
アドレスを使用して、curl
コマンドを実行します。curl -G -H "Host: SERVICE-DOMAIN" https://EXTERNAL-IP/
SERVICE-DOMAIN は、サービスにデフォルトで割り当てられているドメインに置き換えます。そのためにデフォルトの URL を取得し、プロトコル
http://
を削除します。HTTP 500 または HTTP 503 のエラー メッセージを確認してください。
問題の調査
前述の試してみるで発生した HTTP 5xx エラーが、本番環境ランタイム エラーとして発生したことを表示で確認してください。このチュートリアルでは、正式な処理プロセスを説明します。本番環境でのエラー解決プロセスは大きく異なりますが、このチュートリアルでは、有用なツールとテクニックを利用するための特定の手順を示します。
この問題を調査するには、以下のフェーズを実施します。
- 報告されたエラーの詳細を収集し、さらに調査を進め、軽減策を策定します。
- 修正を進めるか、既知の正常なバージョンにロールバックして、ユーザーへの影響を軽減させます。
- 正確な詳細が収集されたことと、1 回限りのエラーではないことを確認するために、エラーを再現します。
- バグの根本原因を分析して、このエラーの原因となったコード、構成、プロセスを特定します。
調査を開始すると、URL、タイムスタンプ、「Internal Server Error」というメッセージが表示されます。
詳細の収集
問題の詳細情報を収集して何が起きたのかを理解し、次のステップを決定します。
利用可能なツールを使用して詳細を収集します。
ログを表示して詳細を確認します。
Cloud Logging を使用して、エラー メッセージなど、問題につながる一連の操作を確認します。
正常なバージョンへのロールバック
機能していることがわかっているリビジョンがある場合は、サービスをロールバックして、そのリビジョンを使用できます。たとえば、このチュートリアルでデプロイした新しい hello-service
サービスには 1 つのリビジョンしかないため、ロールバックはできません。
リビジョンを見つけてサービスをロールバックするには:
エラーを再現する
以前に取得した詳細を使用して、テスト条件で一貫して問題が発生していることを確認します。
同じ HTTP リクエストをもう一度試すことで送信し、同じエラーと詳細が報告されるかどうかを確認します。エラーの詳細が表示されるまでに時間がかかることがあります。
このチュートリアルのサンプル サービスは読み取り専用であり、複雑な副次的影響をトリガーしないため、本番環境でのエラーの再現は安全です。ただし、実際のサービスの多くには上記が当てはまりません。テスト環境でエラーを再現するか、ローカル調査に限定する必要があります。
エラーを再現することで、後続作業のコンテキストが明確になります。たとえば、デベロッパーがエラーを再現できない場合、サービスに対してインストゥルメンテーションの追加が必要になる場合があります。
根本原因の分析を行う
根本原因の分析は、効果的なトラブルシューティングにおいて、症状ではなく問題を解決するための重要なステップです。
このチュートリアルでは、Knative serving で問題を再現し、Knative serving でサービスがホストされているときに問題がアクティブであることを確認しました。次に、問題をローカルで再現させてみて、問題がコードに限定されているか、本番環境のホスティングでのみ発生しているかを判断します。
Container Registry で Docker CLI をローカルで使用していない場合は、gcloud で認証します。
gcloud auth configure-docker
別の方法については、Container Registry の認証方法をご覧ください。
最近使用したコンテナ イメージ名が利用できない場合、サービスの説明に最近デプロイしたコンテナ イメージの情報があります。
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
のようなコンテナ イメージ名を明らかにします。コンテナ イメージを Container Registry から環境に pull します。この手順では、コンテナ イメージをダウンロードするため、数分かかることがあります。
docker pull gcr.io/PROJECT_ID/hello-service
この名前を再利用するコンテナ イメージに対するその後の更新は、同じコマンドで取得できます。この手順をスキップすると、次の
docker run
コマンドはコンテナ イメージを pull します(ローカルマシンにない場合)。ローカルで実行して、Knative serving に固有の問題ではないことを確認します。
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 Observability のエラー メッセージと一致するエラー メッセージをチェックします。
問題がローカルで再現できない場合、Knative serving 環境固有の問題である可能性があります。Knative serving のトラブルシューティング ガイドで、調査する具体的な領域を確認します。
この場合、エラーはローカルで再現されます。
エラーが永続的なものとして二重に確認され、またホスティング プラットフォームではなくサービスコードが原因と判明したため、コードを詳しく調査します。
このチュートリアルでは、コンテナ内のコードとローカル システムのコードが同じであることを前提にしています。
Node.js
ログに表示されるスタック トレースで示されている行番号の近くにあるindex.js
ファイルで、エラー メッセージのソースを見つけます。
Python
ログに表示されるスタック トレースで示されている行番号の近くにあるmain.py
ファイルで、エラー メッセージのソースを見つけます。
Go
ログに表示されるスタック トレースで示されている行番号の近くにある main.go
ファイルで、エラー メッセージのソースを見つけます。
Java
ログに表示されるスタック トレースで示されている行番号の近くにある App.java
ファイルで、エラー メッセージのソースを見つけます。
このコードを調べると、NAME
環境変数が設定されていない場合、次のアクションが実施されます。
- Google Cloud Observability にエラーが記録される
- HTTP エラー レスポンスが送信される。
問題の原因は変数がないことですが、根本的な原因はより具体的です。環境変数に対する強い依存関係を追加するコード変更が行われた後で、デプロイ スクリプトとランタイム要件のドキュメントに対して関連する変更が行われていないことが原因です。
根本原因の修正
これでコードを収集し潜在的な原因を特定できたので、修正手順を実施します。
サービスが、そこで利用できる
NAME
環境でローカルで動作するかどうかを確認します。環境変数を追加してコンテナをローカルで実行します。
PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \ -e NAME="Local World!" \ gcr.io/PROJECT_ID/hello-service
ブラウザで http://localhost:9000 に移動します。
ページに「Hello Local World!」が表示されるのを確認します。
実行している Knative serving サービス環境を変更して、この変数を含めます。
--update-env-vars
パラメータを指定して services update コマンドを実行し、環境変数を追加します。gcloud run services update hello-service \ --update-env-vars NAME=Override
Knative serving が以前のリビジョンに基づき、追加された新しい環境変数を含む新しいリビジョンを作成するまで数秒待ちます。
サービスが修正されたことを確認します。
- ブラウザで Knative serving サービスの URL に移動します。
- ページに「Hello Override!」が表示されるのを確認します。
- Cloud Logging に予期しないメッセージまたはエラーが表示されないことを確認します。
今後のトラブルシューティングの改善
この本番環境の問題のサンプルでは、エラーは運用構成に関連していました。この問題による将来の影響を最小限に抑えるようなコード変更があります。
- エラーログを改善して、より具体的な情報を伝えます。
- エラーを返す代わりに、サービスを安全なデフォルトに戻します。デフォルトの使用が通常の機能への変更を意味する場合、モニタリングのため警告メッセージを使用します。
依存度が高い NAME
環境変数を削除する手順を説明します。
既存の
NAME
処理コードを削除します。Node.js
Python
Go
Java
フォールバック値を設定する新しいコードを追加します。
Node.js
Python
Go
Java
影響を受けた構成ケースからコンテナを再ビルドして実行し、ローカルでテストします。
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
サービスが結果を返さない場合、最初のステップでのコード削除で、レスポンスの書き込みに使用されるような余分な行まで削除していないか確認します。
コードをデプロイするセクションに戻ってこれをデプロイします。
サービスをデプロイするたびに、リビジョンが作成されます。準備ができると、トラフィックの送信が自動的に開始します。
前に設定した環境変数をクリアするには、次の手順を行います。
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 サンプル サービスには影響ありません。
クリーンアップ
費用が発生しないように、このチュートリアル用に作成したリソースを削除します。
チュートリアル リソースを削除する
このチュートリアルでデプロイした Knative serving サービスを削除します。
gcloud run services delete SERVICE-NAME
SERVICE-NAME は、選択したサービス名です。
Knative serving サービスは、Google Cloud コンソールから削除することもできます。
チュートリアルを設定したときに追加した gcloud のデフォルト構成を削除します。
gcloud config unset run/platform gcloud config unset run/cluster gcloud config unset run/cluster_location
プロジェクト構成を削除します。
gcloud config unset project
このチュートリアルで作成した他の Google Cloud リソースを削除します。
gcr.io/<var>PROJECT_ID</var>/hello-service
という名前のコンテナ イメージを Container Registry から削除します。- このチュートリアル用にクラスタを作成した場合は、クラスタを削除します
次のステップ
- Cloud Logging を使用して本番環境での動作を分析する方法を学習する。
- 詳しくは、Knative serving のトラブルシューティングをご覧ください。
- Google Cloud に関するリファレンス アーキテクチャ、図、ベスト プラクティスを確認する。Cloud アーキテクチャ センターをご覧ください。