冒頭まとめ

GCP の 503 Service Unavailable は、まず「どの URL が返したか」で2系統に分けると迷いません。第一に、Google Cloud の各 API(googleapis.com への呼び出し)が返す503です。これは Google 公式のエラーコード定義(google/rpc/code.proto)で UNAVAILABLE に割り当てられたもので、定義の原文に「多くの場合は一時的な状態であり、バックオフつきの再試行で回復しうる。ただし非冪等な操作の再試行が常に安全とは限らない」と、性質と対処と注意点まで書かれています。第二に、自分がデプロイしたサービス(Cloud Run など)の URL が返す503です。こちらは Google 側の障害ではなく、コンテナの待ち受け設定やメモリなど、自分のワークロード側の調査になります。

境界も公式定義で引けます。クォータや利用枠の超過は RESOURCE_EXHAUSTED で429、処理の時間切れは DEADLINE_EXCEEDED で504、Google 内部の深刻なエラーは INTERNAL で500に割り当てられており、これらが503として返ることはありません。「クォータ超過で503」という説明は Google のエラーモデルに合いません。

エラーの概要

Google Cloud の APIエラーは、公式のエラーモデルに沿った JSON で返ります。503の場合、切り分けの決め手になるのは status フィールドです。

{
  "error": {
    "code": 503,
    "message": "The service is currently unavailable.",
    "status": "UNAVAILABLE"
  }
}

status が UNAVAILABLE なら、この記事の原因1(API 側の一時的な利用不能)です。message の文言はサービスにより異なりますが、status の値はエラーモデルで定義された名前がそのまま入ります。一方、Cloud Run にデプロイした自分のサービスの URL への503は、Google の公式トラブルシューティング文書で「HTTP 応答が不正だったか、インスタンスへの接続でエラーが起きた」場合と説明されており、応答本文は自分のアプリや基盤の状態次第です(原因2)。

まず最初に:どの URL の503かで2つに分岐する

失敗したリクエストの宛先を確認します。googleapis.com 系の API 呼び出し(Cloud Storage、BigQuery、各サービスの管理 API など)で、応答の status が UNAVAILABLE なら原因1です。自分のサービスの URL(run.app のドメインや独自ドメイン)への503なら原因2です。あわせて、応答に error.status がある場合は値を必ず読みます。RESOURCE_EXHAUSTED(429)や DEADLINE_EXCEEDED(504)が本来のコードとともに返っているなら、調査は503ではなくそれぞれの系統に切り替えます。

よくある原因と解決手順

原因1:Google Cloud の API が一時的に利用不能(UNAVAILABLE)

Google 側の一時的な問題や混雑で、正しいリクエストにも503が返ることがあります。公式定義が示すとおり、この503への一次対処は「バックオフつきの再試行」です。公式のクライアントライブラリの多くは UNAVAILABLE を再試行対象として扱う設定を持つため、まず自分のコードが即席の再試行を重ねていないか、ライブラリの再試行に任せられないかを確認します。自前で書く場合は、間隔を指数的に広げ、回数に上限を付けます。

Before(間隔なしの即時リトライ。混雑を悪化させ、復旧も遅らせる):

import requests

def call_api(url, headers):
    while True:  # 503 のたびに即時で無限に再送する
        r = requests.get(url, headers=headers)
        if r.status_code == 200:
            return r

After(指数バックオフと回数上限。再試行は 5xx に限定する):

import random
import time

import requests

def call_api(url, headers, max_retries=4):
    for attempt in range(max_retries + 1):
        r = requests.get(url, headers=headers)
        if r.status_code < 500:
            return r  # 4xx は再試行しても結果が変わらないため原因調査へ
        if attempt < max_retries:
            wait = (2 ** attempt) + random.random()  # 1,2,4,8 秒+ゆらぎ
            time.sleep(wait)
    return r

定義の注意書きどおり、非冪等な操作(作成・課金を伴う処理など)の再試行は常に安全とは限りません。再試行の前に、直前の要求が実は成功していないか(対象が作られていないか)を確認する手順を挟みます。503が広範囲・長時間に及ぶ場合は、Google Cloud の稼働状況ページ(https://status.cloud.google.com)でインシデントを確認します。掲載が遅れることもあるため、掲載がないことは障害でないことの証明にはなりません。

原因2:自分のサービス(Cloud Run)の基盤が503を返している

Cloud Run にデプロイしたサービスの URL が503を返す場合、公式トラブルシューティング文書に列挙された典型を順に確認します。

第一に、コンテナの待ち受けです。デプロイ時に「Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable.」という公式のエラーメッセージが出ている場合、コンテナが Cloud Run の指定するポートで待ち受けていません。公式文書の確認点は、環境変数 PORT のポートで待ち受けること、127.0.0.1 ではなく 0.0.0.0(全インターフェース)で待ち受けること、イメージが 64bit Linux 向けであることの3点です。

Before(ポートとアドレスが固定で、Cloud Run の契約に合っていない):

from flask import Flask
app = Flask(__name__)

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)  # PORT を無視し、外から届かないアドレス

After(PORT を読み、0.0.0.0 で待ち受ける。公式文書の指示どおり):

import os
from flask import Flask
app = Flask(__name__)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

第二に、メモリ超過です。公式文書のとおり、「the container instance was found to be using too much memory and was terminated」という記録がログにある場合、インスタンスメモリ上限を超えて強制終了されており、その際のリクエストは 500 または 503 で失敗します。対処はメモリ割り当ての増量かリークの修正で、Cloud Run ではローカルファイルへの書き込み(ログの書き出しを含む)もメモリを消費する点に注意します。

第三に、時間設定の食い違いです。公式文書は、Cloud Run に設定したリクエストタイムアウトより前に503で切れる場合、アプリ側フレームワーク自身のタイムアウト(Node.js の server.timeout など)を疑うよう案内しています。基盤の設定だけでなく、アプリの中の時間制限も揃えます。

切り分けの基本は、同じコンテナをローカルで動かすことです。公式のトラブルシューティングチュートリアルも、PORT を渡した docker run での再現を最初の手順としています。ローカルで動かないものは Cloud Run でも動きません。

補足:このコードではない類似エラー

Google のエラーモデル上、503と混同されやすい問題には別のコードが割り当てられています。クォータや利用枠の超過は RESOURCE_EXHAUSTED(429)で、待つか、割り当ての引き上げを申請します(仕組みの考え方は AWS の 429 の記事と同型です)。処理の時間切れは DEADLINE_EXCEEDED(504)で、要求の縮小・分割が対処の軸です(GCP の 504 の記事)。Google 内部の深刻なエラーは INTERNAL(500)です。API が有効化されていない・権限がない場合は 403 系で、503にはなりません。また、手元の HTTP クライアントタイムアウト発火はコード自体が返らず例外になるため、503の調査ではなく手元の設定の調査です。自分で Nginx などを立てて Cloud Run の前段に置いている場合、その503は Nginx 側の仕組みで発生します(Nginx の 503 の記事)。

切り分けの順序

  1. 宛先を確認する。googleapis.com 系 API なら原因1、自分のサービスの URL なら原因2。
  2. 応答の error.status を読む。RESOURCE_EXHAUSTED・DEADLINE_EXCEEDED・INTERNAL なら、それぞれ 429・504・500 の系統として調査を切り替える。
  3. 原因1は、稼働状況ページを確認し、バックオフつき再試行を実装する。非冪等な操作は再試行前に成否を確認する。
  4. 原因2は、Cloud Run のログで公式の典型メッセージ(起動失敗・メモリ超過)を探し、PORT・0.0.0.0・64bit Linux・メモリ・アプリ側タイムアウトを順に確認する。
  5. 原因2で原因が絞れない場合は、同じイメージをローカルの docker run で再現し、Cloud Run 固有かコンテナ自体の問題かを切り分ける。

確認コマンド集

# 1. API の503か、status フィールドを確認する
curl -si -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://storage.googleapis.com/storage/v1/b?project=<project-id>" | grep -E "^HTTP|status"

# 2. Cloud Run のログから公式の典型メッセージを探す
gcloud logging read 'resource.type="cloud_run_revision" AND resource.labels.service_name="<service>"' \
  --limit 50 --format "value(textPayload)" | grep -iE "failed to start|memory|terminated"

# 3. サービスの設定(メモリ・タイムアウト・ポート)を確認する
gcloud run services describe <service> --region <region> \
  --format "value(spec.template.spec.containers[0].resources.limits.memory, spec.template.spec.timeoutSeconds, spec.template.spec.containers[0].ports[0].containerPort)"

# 4. 同じコンテナをローカルで再現する(公式チュートリアルの手順)
PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT <image>
curl -i http://localhost:9000/

Editor’s Note

原因1の実例として、Google 自身が公開した大規模なインシデント報告があります(2025年6月12日のインシデント報告)。公式報告の記述によれば、Google Cloud と Google Workspace などの製品で外部 API リクエストの503エラーが増加しました。原因は、API 管理を担う Service Control に5月末に追加された新機能に適切なエラー処理も機能フラグの保護もなかったところへ、空のフィールドを含むポリシーデータが投入され、グローバルに数秒で複製されて各地のバイナリが連鎖的にクラッシュしたことです。大半の地域は約2時間で回復した一方、us-central1 は再起動の集中が基盤に過負荷をかけたことで回復が長引いた、という経緯も報告に記されています。報告はその要因として、Service Control 自体にランダム化した指数バックオフが実装されていなかったことを名指しし、再発防止策にも全システムでのバックオフ実装の監査を挙げています。執筆時点から約1年前の事例で、正しいリクエストにも503が世界規模で返る時間帯が現実にあること、そしてその最中に手元でできる正しいことが「バックオフを効かせた再試行」だけであることを示しています。間隔を空けない再試行の殺到が回復を遅らせる側に回ることを、Google 自身が自社システムの反省点として公式に記録した文書でもあります。

GCP の503は、Google のエラーモデルでは「今は無理だが、たいてい一時的」という意味が定義に書き込まれたコードです。宛先と status フィールドで系統を確定し、API 側なら正しく待ち、自分のサービス側なら待ち受けとメモリから順に調べるのが確実な近道です。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。