Cloud Runのエラーは、メッセージさえ正しく読めれば、原因はほぼ一意に絞れます。逆に言えば、「なんとなく」で設定をいじると泥沼にはまります。本記事は、本番で頻出するエラーを**公式ドキュメントの正確なメッセージ**ごとに整理し、原因と直し方を最短で示します。「cloud run container failed to start でググって来た」人が、そのページで解決して帰れることを目指します。
設計段階で踏まないための予防は Cloud Run 本番運用ガイド、冷起動とコストの最適化は 並行性・課金ガイド を参照してください。
まず診断:ログとローカル再現の2本立て
個別のエラーに入る前に、どんな問題でも最初にやる2つを決めておきます。
# 1. リビジョンのログを見る(エラーの一次情報はここ)
gcloud run services logs read api --region asia-northeast1 --limit 100
# 2. ローカルで本番と同じ条件を再現する(PORTを注入して起動するか?)
docker run --rm -e PORT=8080 -p 8080:8080 \
asia-northeast1-docker.pkg.dev/PROJECT_ID/app/api:TAG
# → http://localhost:8080 に到達できなければ、本番でも起動しない
「ローカルで -e PORT=8080 を渡して起動するか」——これだけで起動系エラーの大半は手元で再現・解決できます。Cloud Loggingにはアプリのstdout/stderrがそのまま出るので、構造化ログを吐いていれば原因追跡が速くなります(可観測性の設計)。
エラー1:Container failed to start(最頻出)
Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable.
デプロイ直後に出る最頻出エラー。Cloud Runが「コンテナを起動したが、$PORT で待ち受けてくれない」と判断した状態です。
原因と直し方:
0.0.0.0で$PORTを待ち受けていない(最多)。localhost/127.0.0.1固定や、ポートをハードコードしている。
# ✗ 悪い例:127.0.0.1 / ポート固定 → Cloud Runから到達できず起動失敗
uvicorn.run(app, host="127.0.0.1", port=3000)
# ✓ 正しい例:0.0.0.0 で、PORT環境変数を読む
import os
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
- 64bit Linux向けにビルドされていない。Apple Silicon (arm64) のローカルでビルドしたイメージをそのまま使うと起こりがち。マルチアーキでビルドするか、
--platform linux/amd64を指定する。
docker build --platform linux/amd64 -t IMAGE_URL .
- 起動処理そのものが失敗している(依存サービスへの接続を起動時に待ってタイムアウト、設定不足で例外)。起動時に重い同期初期化や外部接続をブロッキングで行わない。ログに例外が出ていないか確認する。起動が本当に遅いなら、startupプローブの
failure_threshold × period_secondsを広げる。
エラー2:メモリ超過 / exit 137(OOM)
... the container instance was found to be using too much memory and was terminated.(コンテナの終了コードは 137)
割り当てメモリを超えてOOM Killされた状態。Cloud Runの書き込み可能ファイルシステムはインメモリで、書いた分がメモリを食う点に注意。
原因と直し方:
- メモリ割り当て不足 → ピークに合わせて増やす(ただし闇雲に増やすとコスト増。メトリクスで判断)。
gcloud run services update api --region asia-northeast1 --memory 1Gi
-
インメモリFSへの書き込み(大きな一時ファイル、ローカルへのログ出力)→ 一時データは小さく保つか、Cloud Storageへストリーミングする。ログはファイルではなくstdoutへ。
-
メモリリーク(接続・バッファの蓄積)→ リークを直す。大きなオブジェクトをリクエスト間で保持していないか確認。
私のマルウェアスキャナは、最大10GiBの素材を扱うためバッファせずストリーミング検査してメモリ枯渇を回避していました。「全部メモリに載せてから処理する」設計は、サーバーレスでは特に危険です。大きなデータは流して処理する。
エラー3:503 Service Unavailable
The request was aborted because there was no available instance.
リクエストを捌けるインスタンスが無かった状態。スパイク・冷起動・スケール上限・プローブ失敗が絡みます。
原因と直し方:
- 最大インスタンスが低すぎてスパイクに負けた →
--max-instancesを上げる。 - 冷起動が遅く、起動が間に合わない →
--min-instances 1以上で温める、冷起動対策(起動CPUブースト・スリムイメージ・gen1)。 - 並行性が低すぎて必要インスタンスが膨らんだ → 並行性を見直す(I/Oバウンドなら上げる)。
- livenessプローブが厳しすぎてインスタンスが再起動を繰り返す → プローブのタイムアウト/閾値を緩める、プローブに重い依存チェックを入れない。
gcloud run services update api --region asia-northeast1 \
--min-instances 1 --max-instances 20 --cpu-boost
エラー4:504 Gateway Timeout
The request has been terminated because it has reached the maximum request timeout.
レスポンスがリクエストタイムアウト(既定300秒・最大60分)内に終わらなかった状態。
原因と直し方:
- 処理が単純に長い → タイムアウトを延ばす(
--timeout)。ただし60分が上限。
gcloud run services update api --region asia-northeast1 --timeout 600
- そもそもHTTPで抱え込むべきでない長時間処理 → Cloud Run Jobs / Workflows へ切り離すのが正解(Jobs/Workflowsガイド)。タイムアウトを伸ばし続けるのは設計の先送り。
- 死んだ接続の再利用(プールが切れたDB/HTTP接続を使い回している)→ 接続の健全性チェックとリトライを入れる。
エラー5:イメージpull / 権限エラー(デプロイ失敗)
... the Google Cloud Run Service Agent must have permission to read the image ...
デプロイ時にイメージを読めない=権限不足。コードの問題ではありません。
原因と直し方:
- Cloud Run サービスエージェント(
service-PROJECT_NUMBER@serverless-robot-prod.iam.gserviceaccount.com)に、イメージのあるArtifact Registryの読み取り権限を与える。
gcloud artifacts repositories add-iam-policy-binding app \
--location asia-northeast1 \
--member "serviceAccount:service-PROJECT_NUMBER@serverless-robot-prod.iam.gserviceaccount.com" \
--role "roles/artifactregistry.reader"
- イメージが別プロジェクトにある → そのプロジェクト側で権限を付与。
- VPC Service Controls が読み取りを遮断していないか確認。
関連して、デプロイ用SA(CI)の権限不足でデプロイ自体が失敗するケースは CI/CDガイド を参照(run.developer+AR読み取り+ランタイムSAへの serviceAccountUser)。
エラー6:コンテナのインポート失敗
The service has encountered an error during container import. Resource readiness deadline exceeded.
イメージの取り込み段階で失敗。比較的レアですが原因が分かりにくい。
原因と直し方:
- 非UTF-8なファイル名がイメージ内にある → UTF-8でビルドし直す。
- Windowsイメージの非対応レイヤー(foreign layers) →
--allow-nondistributable-artifactsを有効にして再ビルド。 - 基本は64bit Linuxの素直なイメージにする(マルチステージ・distroless等)。
エラー7:冷起動が遅い(エラーではないが体感を壊す)
明示的なエラーは出ないのに「最初の1リクだけ妙に遅い」——スケールトゥゼロからの冷起動です。
診断と対処(軽い順):
- イメージを小さく(マルチステージ・distroless/alpine)。pullと展開が速くなる。
- 起動CPUブースト(
--cpu-boost)で初期化を速く。 - 遅延初期化:起動時に全部読み込まず、必要時に初期化。接続はハンドラ外で一度だけ生成して再利用。
- 実行環境 gen1(冷起動が速い)を軽量APIで選ぶ。
- それでも体感が許容できない経路だけ
--min-instances 1で温める(常時課金とのトレードオフ)。
詳細な原価試算と打ち手は 並行性・オートスケール・コスト最適化ガイド にまとめています。
診断早見表
| 症状 / メッセージ | 一次原因 | まず試すこと |
|---|---|---|
failed to start and then listen on the port | 0.0.0.0/$PORT待受不可・arm64・起動エラー | ローカルで -e PORT=8080 再現、--platform linux/amd64 |
exit 137 / too much memory | OOM(不足・リーク・インメモリFS) | --memory 増、ストリーミング化、ログをstdoutへ |
503 no available instance | 冷起動・スケール上限・プローブ | --min-instances --max-instances --cpu-boost、プローブ緩和 |
504 maximum request timeout | 処理超過・死接続 | --timeout、長時間処理はJobsへ、接続検証 |
Service Agent must have permission to read the image | AR読み取り権限不足 | サービスエージェントに artifactregistry.reader |
Resource readiness deadline exceeded | 非UTF-8/foreign layers | UTF-8・64bit Linuxで再ビルド |
本番投入チェックリスト(予防)
- ローカルで
docker run -e PORT=8080が起動する - イメージは 64bit Linux(
--platform linux/amd64かマルチアーキ) - 起動時に重い同期処理・外部接続のブロッキング待ちをしない
- 大きなデータはメモリに載せず流す(インメモリFSを使い切らない)
- min/max instances とプローブを設定し、503を予防
- 長時間処理は Jobs/Workflows へ(504をタイムアウト延長で誤魔化さない)
- 構造化ログ(stdout)で原因追跡を速くする
- サービスエージェント/デプロイSAの権限を事前に確認
まとめ:メッセージを読めば原因は絞れる
Cloud Runのトラブルシュートは、公式のエラーメッセージを正確に読むことが9割です。「起動しない」なら $PORT・0.0.0.0・アーキテクチャ、「137」ならメモリ、「503」ならスケールと冷起動、「504」なら長時間処理の切り離し、「image permission」なら権限——と、メッセージから原因へ一意に降りられるように設計されています。慌てて設定をいじる前に、ログを読み、ローカルで再現する。それが最短経路です。
予防は設計から:全体は Cloud Run 本番運用ガイド、コストと冷起動は 並行性・課金ガイド、長時間処理は Jobs/Workflowsガイド へ。本番障害の調査・恒久対策の設計が必要なら、実運用の知見でお手伝いします。