GCP 409

GCP の 409 エラー:原因と解決策

冒頭まとめ GCP の 409 Conflict は、2つの異なる区分に対応します。エラー区分の定義ファイルを見ると、1つは作ろうとしたものが既に存在する場合、もう1つは同時実行の衝突で処理が中断された場合です。 この2つは、対処が正反対です。 前者は、状態が既に望みどおりになっている可能性があります。作成の操作を繰り返す自動化では、2回目以降は必ずこのエラーになります。異常として止めるのではなく、既にあるものを使う分岐を持つのが正しい作りです。 後者は、他の処理と衝突しました。定義には、この区分をどう扱うべきかが明示されています。実装する側への指針として、失敗した呼び出しだけを再試行してよいのが 503 の区分、上位の処理からやり直すべきなのがこの区分、系の状態が明示的に直されるまで再試行すべきでないのが 400 の区分、と3つが並べて説明されています。例として挙げられているのは、値を確認してから書き換える処理が失敗した場合で、読み取りから書き込みまでの一連の流れをやり直すべきだ、とされています。 つまり、同じ要求をそのまま送り直すのは、この区分に対しては誤った対処です。読み取りからやり直す必要があります。 したがって、409 を見たときに最初に読むべきは status の値です。ここで、待つのか、既存を使うのか、処理全体をやり直すのかが決まります。 エラーの概要 応答の形は他のエラーと共通です。既に存在する場合は次のようになります。 { "error": { "code": 409, "message": "The resource 'projects/my-project/zones/asia-northeast1-a/instances/my-vm' already exists", "status": "ALREADY_EXISTS" } } message に、既に存在する対象の完全な名前が入ります。自分が作ろうとした名前と同じであることを確認できます。 同時実行の衝突の場合は、区分名が変わります。 { "error": { "code": 409, "message": "Aborted due to cross-transaction contention.", "status": "ABORTED" } } こちらは対象の名前ではなく、衝突の理由が書かれます。同時に走っている処理があった、という趣旨の文言です。 コマンド行の道具からは、簡潔な形で表示されます。作成の操作を繰り返した場合、既に存在する旨がそのまま出ます。 まず最初に:status で3方向に振り分ける 第一に、status の値を読みます。ALREADY_EXISTS なら既に存在します。ABORTED なら同時実行の衝突です。 第二に、ALREADY_EXISTS であれば、既存のものが自分の望む状態かを確認します。同じ設定であれば、そのまま使えます。違えば、更新の操作に切り替えます。 第三に、ABORTED であれば、読み取りからやり直します。同じ要求の送り直しではありません。 第四に、FAILED_PRECONDITION が返っている場合は 409 ではなく 400 です。状態が整うまで待つ必要があります(GCP の 400 の記事)。混同しやすい3つですが、区分名で確実に分かれます。 ...

{
  "error": {
    "code": 409,
2026年7月29日 · ErrorLog
GCP 422

GCP の 422 エラー:原因と解決策

冒頭まとめ 先に結論を述べます。GCP の窓口は、検証に落ちた要求に対して 422 を返しません。Google が公開しているエラー区分の定義ファイルには17の区分があり、それぞれに対応する HTTP の状態コードが併記されていますが、422 は1か所も出てきません。 代わりに使われるのは 400 です。しかも、400 に対応する区分は1つではなく3つあります。引数が不正な場合、対象の現在の状態がその操作を許さない場合、そして値が許容範囲の外にある場合です。他のサービスが 400 と 422 で表現し分けている区別を、GCP は状態コードではなく区分名で表現している、と考えると分かりやすくなります。 したがって、GCP で入力の誤りを追うときに見るべきは、状態コードではなく応答に含まれる区分名です。ここを読まないと、400 が返ってきたという事実だけでは原因を1つに絞れません。 そして、実際に GCP の宛先から 422 を受け取った場合、それを作ったのは GCP の窓口ではありません。前段のプロキシか、GCP 上で動いている自作あるいは第三者のアプリケーションです。この場合、GCP の設定を調べても答えは出ません。 エラーの概要 区分と状態コードの対応は、定義ファイルにそのまま書かれています。検証に関わる3つを抜き出すと、次のようになります。 引数が不正な場合の区分は、対応する状態コードが 400 です。定義の説明では、系の状態に関係なく問題のある引数、たとえば形式の壊れた名前などを指す、とされています。 対象の状態が操作を許さない場合の区分も、対応は 400 です。空でないディレクトリを削除しようとした場合が例として挙げられており、系の状態が変われば成功しうる、という性質を持ちます。 値が許容範囲の外にある場合の区分も、対応は 400 です。読み取りの開始位置が終端を越えている場合などが該当します。 つまり、応答だけを見ると次の形になり、code の値は3つとも同じです。 { "error": { "code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT" } } 区別できるのは status の値だけです。ここが INVALID_ARGUMENT なのか FAILED_PRECONDITION なのか OUT_OF_RANGE なのかで、次にやることが変わります。 まず最初に:statusの値を読む 第一に、状態コードが 400 であることを確認します。GCP で入力の誤りを疑う場面では、まずここに落ちてきます。 第二に、status の値を読みます。INVALID_ARGUMENT なら送った値そのものが不正で、何度送っても同じです。FAILED_PRECONDITION なら値は正しく、対象の現在の状態が問題です。状態を整えれば同じ要求が通ります。OUT_OF_RANGE なら、値の形式は正しいが範囲の外です。 ...

{
  "error": {
    "code": 400,
2026年7月29日 · ErrorLog
GCP 502

GCP の 502 エラー:原因と解決策

冒頭まとめ GCP で 502 Bad Gateway を受け取ったとき、最初に押さえるべき事実が1つあります。Google が公開しているエラー区分の定義ファイルには、502 が存在しません。各区分には対応する HTTP の状態コードが併記されており、200・400・401・403・404・409・429・499・500・501・503・504 が並びますが、502 はどこにも出てきません。 これは、各サービスの窓口が自分のエラーとして 502 を返す仕組みになっていない、ということです。つまり GCP で 502 を見たら、応答を作ったのは窓口そのものではなく、その手前にいる仕組みです。多くの場合は Cloud Load Balancing、あるいは経路上のプロキシです。504 が窓口自身からも返りうるのとは、この点で性質が違います。 ロードバランサが返す 502 については、原因を絞り込む手段が用意されています。ログの statusDetails という項目です。公式のトラブルシューティング文書には、この値が response_sent_by_backend であればロードバランサは背後の応答をそのまま渡しただけで、それ以外の値であればロードバランサ自身が作った応答だ、と明記されています。値ごとの意味も一覧になっています。 もう1つ、種類による違いがあります。同じ文書によれば、グローバルおよびリージョンの外部アプリケーション ロードバランサは 503 や 504 といった意味のある状態コードを生成しますが、従来型のアプリケーション ロードバランサは常に 502 を使います。従来型の環境では、待ち時間の超過も接続の失敗も、まとめて 502 として現れます。数字だけでは区別できません。 エラーの概要 利用者側に届くのは簡素な応答で、そこに手がかりはほとんどありません。判断の材料はログ側にあります。 { "httpRequest": { "status": 502 }, "jsonPayload": { "@type": "type.googleapis.com/google.cloud.loadbalancing.type.LoadBalancerLogEntry", "statusDetails": "failed_to_connect_to_backend" }, "resource": { "type": "http_load_balancer" } } 公式文書に説明がある値のうち、502 に結び付きやすいものは次の3つです。 failed_to_connect_to_backend は、ロードバランサが背後との接続を確立できなかったことを示します。文書では、背後で動いている処理が、バックエンド サービスに定義された番号で待ち受けていない可能性がある、と説明されています。 failed_to_pick_backend は、送り先を選べなかったことを示します。すべての背後が正常でない状態が考えられ、正常性の確認に必要な通信が許可されているかを確かめるよう案内されています。なお同じ文書には、グローバルの構成を変更した直後に、設定が行き渡るまでの短い間だけこの値とともに 502 が出ることがある、とも書かれています。 backend_connection_closed_before_data_sent_to_client は、応答が利用者へ渡される前に、背後が予期せず接続を閉じたことを示します。文書では、間に別の装置が挟まっていて、そちらの待ち時間のほうが短い場合に起きうる、と説明されています。 まず最初に:statusDetails を読む 第一に、ログから 502 の件を取り出し、statusDetails の値を確認します。ここが response_sent_by_backend であれば、502 を作ったのは背後のアプリケーションです。調べる先はそちらになり、ロードバランサの設定を触っても変わりません。 ...

{
  "httpRequest": { "status": 502 },
  "jsonPayload": {
2026年7月29日 · ErrorLog
GitLab 502

GitLab の 502 エラー:原因と解決策

冒頭まとめ GitLab の 502 には、他のソフトウェアにはない手がかりがあります。専用の画面が用意されていて、応答に時間がかかりすぎている、という趣旨の文言が表示されます。この文言が出ているかどうかが、切り分けの起点になります。 専用の画面が表示されているなら、応答を作ったのは GitLab に同梱されている前段のソフトウェアです。つまり、前段は動いています。動いていない相手は、その後ろにいる応用処理の側です。逆に、素っ気ない画面や別の形式の画面が出ているなら、応答を作ったのは GitLab の外側にいる中継役です。この場合、GitLab の設定をいくら見直しても変わりません。 GitLab の構成は多段です。前段が受け取り、補助の役が中継し、応用処理が実際の処理を行い、その先に格納の役が控えています。502 が起きるのは、このどこかで応答が返らなくなったときです。段が多いぶん、どこで止まったかを特定する作業が要ります。 公式の窓口記事には、具体的な設定に起因する例が挙げられています。応用処理を単独で動かす設定にしていると、頻繁な再起動が起き、その際に 502 が表示される、というものです。この設定は資源の限られた環境向けのもので、外すと複数の処理単位で動くようになり、順に入れ替える方式が使えるため、停止する時間が短くなる、と説明されています。 エラーの概要 利用者側に表示されるのは、GitLab が用意した専用の画面です。時間がかかりすぎている、という趣旨の文言が入ります。 前段の記録には、その先へ繋げなかったことが残ります。 upstream prematurely closed connection while reading response header from upstream connect() failed (111: Connection refused) while connecting to upstream, upstream: "http://unix:/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket:/" 転送先として、ファイルを経由した接続先が記録されているのが特徴です。この経路で繋がらないということは、応用処理が待ち受けていないか、応答を返す前に落ちているということです。 各段の稼働状況は、まとめて確認できます。 run: gitaly: (pid 1580) 83s; run: log: (pid 1575) 83s run: nginx: (pid 1588) 83s; run: log: (pid 1584) 83s run: puma: (pid 12394) 0s; run: log: (pid 1574) 83s run: sidekiq: (pid 12015) 2s; run: log: (pid 1578) 83s ここで見るべきは、右側の経過時間です。他の段が同じくらいの値なのに、応用処理の段だけが数秒しか経っていない場合、その段が繰り返し起動し直していることを示します。この形は、後述する再起動の問題に直結します。 ...

upstream prematurely closed connection while reading response header from upstream
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://unix:/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket:/"
2026年7月29日 · ErrorLog
Kubernetes 502

Kubernetes の 502 エラー:原因と解決策

冒頭まとめ Kubernetes で 502 Bad Gateway を見る場面は、利用者向けの通信を取り次ぐ Ingress の制御役に集中します。kubectl の操作で 502 が出ることは、通常ありません。API サーバーが時間切れで打ち切る場合は 504 になり、応答できる相手が居ない場合は 503 になるためです。したがって 502 を見たら、まずコンテナへの通信経路の話だと考えて構いません。 502 の意味は、取り次いだ側が転送先から正しい応答を得られなかった、ということです。ここで重要なのは、転送先そのものは選べているという点です。選べていなければ、そもそも転送する相手が居ないので別のエラーになります。多くの制御役は、対象の転送先が1つも無い場合に 503 を返します。 したがって切り分けの第一歩は、502 と 503 のどちらが出ているかを見ることです。503 なら、転送先の一覧が空です。準備完了の判定が通っていないか、選択の条件が合っていないかのどちらかです。502 なら、一覧には相手が居るのに、その相手との通信が成立していません。 502 の原因は、制御役の土台になっているソフトウェアの作りから、3系統に整理できます。接続そのものを拒否された場合、応答の見出し部分が大きすぎて扱えなかった場合、そして応答を渡し終える前に切られた場合です。ログの文言でこの3つは区別できます。 エラーの概要 制御役のログには、要求ごとの記録と、失敗の理由が残ります。接続を拒否された場合の記録は次の形です。 connect() failed (111: Connection refused) while connecting to upstream, client: 10.1.0.5, server: example.com, request: "GET /api HTTP/1.1", upstream: "http://10.2.3.4:8080/api" 応答の見出し部分が大きすぎる場合は、別の文言になります。 upstream sent too big header while reading response header from upstream, client: 10.1.0.5, server: example.com, request: "GET /api HTTP/1.1" このとき記録に残る転送先の番号を確認してください。ここに出ている番号が、自分が意図したコンテナの番号と違っていれば、原因は設定の食い違いです。 ...

connect() failed (111: Connection refused) while connecting to upstream,
client: 10.1.0.5, server: example.com, request: "GET /api HTTP/1.1",
upstream: "http://10.2.3.4:8080/api"
2026年7月29日 · ErrorLog
Kubernetes CrashLoopBackOff

Kubernetes の CrashLoopBackOff エラー:原因と解決策

冒頭まとめ CrashLoopBackOff は、コンテナが落ちた原因の名前ではありません。落ちたコンテナを再び起動するまで、待たせている状態の名前です。この文字列自体は、kubelet のソースに定義された固定の値で、それ以上の意味を持ちません。原因は別の場所、直前に終了したときの情報に残っています。 待ち時間の決まり方は、ソースに明確に書かれています。初回は10秒で、以降は倍々に増え、上限は300秒です。起点になるのは、直前に終了したコンテナの終了時刻です。 もう1つ、知っておくと症状の見え方が説明できる値があります。待ち時間の記録は、600秒つまり10分のあいだ更新されなければ期限切れになります。言い換えると、10分以上持ちこたえたコンテナは、次に落ちたとき10秒から数え直します。逆に、9分ごとに落ち続けるコンテナは記録が消えないため、待ち時間が上限に張り付いたままになります。「同じように落ちているのに、片方は復旧が速く、片方は遅い」という現象は、たいていこの境目です。 したがって、見るべきものは3つに絞れます。直前の終了理由、終了コード、そして直前の出力です。この3つが揃えば、CrashLoopBackOff という表示は読み終わったも同然です。 エラーの概要 一覧では、状態の欄にこの文字列が出ます。再起動の回数が増え続けるのが特徴です。 NAME READY STATUS RESTARTS AGE my-app-7d9f8b6c-xk2p9 0/1 CrashLoopBackOff 6 (2m11s ago) 14m 詳細を見ると、待ち時間を含む記録が出ます。文言はソースに定義されているとおりの形です。 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning BackOff 2m11s (x14 over 12m) kubelet Back-off restarting failed container my-app in pod my-app-7d9f8b6c-xk2p9 Last State: Terminated Reason: Error Exit Code: 1 Started: Wed, 29 Jul 2026 10:20:31 +0900 Finished: Wed, 29 Jul 2026 10:20:33 +0900 読むべきはこの下半分です。終了の理由と終了コード、そして開始から終了までの秒数です。上の例では2秒で落ちているので、起動処理のどこかで失敗していると分かります。 ...

NAME                    READY   STATUS             RESTARTS      AGE
my-app-7d9f8b6c-xk2p9   0/1     CrashLoopBackOff   6 (2m11s ago) 14m
2026年7月29日 · ErrorLog
Kubernetes ImagePullBackOff

Kubernetes の ImagePullBackOff エラー:原因と解決策

冒頭まとめ ImagePullBackOff は、イメージの取得に失敗した理由の名前ではありません。失敗したあと、次に取得を試みるまで待たせている状態の名前です。理由は別の名前で表されます。 kubelet のソースには、取得に関する状態名が5つ定義されています。待たせている状態、一般的な取得の失敗、イメージを調べられない場合、手元に無いのに取得しない方針になっている場合、そして名前を解釈できない場合です。この5つのどれが出ているかで、原因の範囲がほぼ決まります。最初に見るべきはここです。 待ち時間の決まり方は、コンテナの再起動と同じ形です。初回は10秒で倍々に増え、上限は300秒です。値は同じですが、記録は別に管理されています。 そして、この状態には決定的な性質が1つあります。諦めません。何度失敗しても、対象は待機中の扱いのまま残り続け、割り当てられた資源を確保し続けます。失敗を重ねたら異常として扱う、という仕組みが標準では用意されていません。人が気付いて手を入れるまで、そのままです。 最後に、取得の方針の既定値も押さえておきます。ソースの規則は単純で、タグが latest なら毎回取得し、それ以外なら手元にあるものを使います。タグを書かない場合はタグを latest とみなす処理が入るため、結果として毎回取得になります。一方、ダイジェストで指定した場合はタグが空のままなので、手元にあるものを使う方針になります。 エラーの概要 一覧では待機中の理由として表示されます。再起動の回数は増えません。コンテナが一度も起動していないためです。 NAME READY STATUS RESTARTS AGE my-app-6c4d8f9b5-2xq7w 0/1 ImagePullBackOff 0 4m12s 詳細を見ると、最初の失敗と、その後の待機が別々に記録されています。 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Pulling 4m (x4 over 5m) kubelet Pulling image "example/my-app:1.0" Warning Failed 3m (x4 over 5m) kubelet Failed to pull image "example/my-app:1.0": ... Warning Failed 3m (x4 over 5m) kubelet Error: ErrImagePull Normal BackOff 30s (x12 over 4m) kubelet Back-off pulling image "example/my-app:1.0" Warning Failed 30s (x12 over 4m) kubelet Error: ImagePullBackOff 読むべきは、上から2行目の末尾です。ここに、取得を担う仕組みが返した文言がそのまま入ります。名前が見つからない、資格が無い、回数の上限に達した、といった内容が、その置き場の言葉で書かれています。ImagePullBackOff という表示だけを見ていても、この文言には辿り着きません。 ...

NAME                    READY   STATUS             RESTARTS   AGE
my-app-6c4d8f9b5-2xq7w  0/1     ImagePullBackOff   0          4m12s
2026年7月29日 · ErrorLog
Kubernetes OOMKilled

Kubernetes の OOMKilled エラー:原因と解決策

冒頭まとめ OOMKilled は、コンテナがメモリの都合で外から止められたことを示します。ただし、止めた理由は2通りあり、対処が正反対になります。 1つ目は、そのコンテナ自身に設定された上限を超えた場合です。この場合、超えたコンテナだけが止められます。機器に空きがあっても関係ありません。 2つ目は、機器全体のメモリが尽きた場合です。この場合、自分の上限を超えていないコンテナでも止められることがあります。誰が選ばれるかは、あらかじめ設定された優先度で決まります。 そして、この優先度を決めているのは上限ではありません。要求量です。kubelet のソースには計算式が定義されており、要求量を保証する種類なら最も殺されにくい値、上限も要求量も書かない種類なら最も殺されやすい値、その中間の種類なら「1000 から、要求量を機器のメモリ量で割った比率に1000を掛けた値を引いた数」になります。注釈には、要求量を超えて使うコンテナは実質的に最も殺されやすい値になり、優先的な対象になる、と書かれています。 ここが実務上いちばん重要な点です。「上限を上げる」という対処は、1つ目にしか効きません。2つ目に対しては、要求量を正しく設定することが効きます。上限だけを大きくして要求量を書かないままにすると、2つ目の状況ではむしろ殺されやすいままです。 なお、kubelet が能動的に対象を退去させる仕組みは別物で、状態名も違います。こちらの既定のしきい値は、空きが100メガバイトを下回った時点です。 エラーの概要 一覧では、繰り返せば待機中の表示になります。判断の材料は詳細のほうにあります。 Last State: Terminated Reason: OOMKilled Exit Code: 137 Started: Wed, 29 Jul 2026 11:02:14 +0900 Finished: Wed, 29 Jul 2026 11:19:48 +0900 終了コードの 137 は、128に9を足した値です。9番は強制終了の信号なので、ソフトウェアが自分で終了したのではなく、外から止められたことを示します。理由の欄が OOMKilled になっていれば、止めた理由はメモリです。 開始から終了までの時間も重要です。数分から数時間かけて止まっている場合、使用量が徐々に増えている可能性があります。起動直後に止まる場合は、初期化の時点で上限を超えています。 種類の判定は、要求量と上限の書き方で決まります。 kubectl get pod <ポッド名> -o jsonpath='{.status.qosClass}' 要求量と上限が等しければ最も保護される種類、どちらも書いていなければ最も保護されない種類、その中間はそれ以外です。 まず最初に:自分の上限か、機器の枯渇か 第一に、止められたコンテナの使用量が、自分の上限に達していたかを確認します。達していれば1つ目です。 第二に、同じ時刻に、同じ機器の上で他のコンテナも止まっていないかを確認します。複数が同時に止まっていれば、2つ目の可能性が高くなります。 第三に、機器全体のメモリの状況を確認します。空きが逼迫していた形跡があれば2つ目です。 この区別を付けずに上限だけを上げると、1つ目なら直り、2つ目なら悪化します。上限を上げたコンテナがより多くを使うようになり、機器全体の逼迫が早まるためです。 よくある原因と解決手順 原因1:自分の上限を超えている 最も分かりやすい形です。設定した上限に対して、実際の使用量が足りていません。 まず、上限と実測を並べます。 kubectl get pod <ポッド名> \ -o jsonpath='{range .spec.containers[*]}{.name}{" req="}{.resources.requests.memory}{" lim="}{.resources.limits.memory}{"\n"}{end}' kubectl top pod <ポッド名> --containers Before(上限が実態に対して低い): ...

Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
2026年7月29日 · ErrorLog
Kubernetes Pending

Kubernetes の Pending:原因と解決策

冒頭まとめ Pending は、コンテナの状態を表す名前ではありません。対象全体がどの段階にあるかを表す名前です。公式の定義には、対象は受け付けられたがコンテナの1つ以上がまだ起動していない状態であり、これには機器に割り当てられる前の時間と、イメージを取得している時間の両方が含まれる、と書かれています。 この一文が示すとおり、Pending は性質の違う2つの状況を1語でまとめています。置き場所がまだ決まっていない状況と、置き場所は決まったが起動の準備が終わっていない状況です。前者は配置を担う仕組みの問題で、後者は機器の上の仕組みの問題です。原因も対処もまったく違います。 分岐は1点で決まります。配置先が決まっているかどうかです。決まっていなければ配置の問題、決まっていれば起動準備の問題です。この確認を最初に行えば、調べる範囲が半分になります。 配置の問題であれば、記録に理由が残ります。文言は「0/N nodes are available」で始まり、そのあとに理由が続きます。この形式はソースに定義されていて、理由ごとに何台の機器が該当したかという数が付きます。 読み方に注意点が1つあります。この数は理由ごとの機器の台数で、1台の機器が複数の理由に数えられることがあります。したがって、並んでいる数字を足しても総数に一致するとは限りません。合計が合わないのは異常ではありません。 エラーの概要 一覧では、準備できているコンテナの数が0のまま止まります。 NAME READY STATUS RESTARTS AGE my-app-5f8c7d9b4-nq3vt 0/1 Pending 0 6m41s 配置が済んでいない場合、記録に理由が残ります。 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 6m (x5 over 6m) default-scheduler 0/5 nodes are available: 3 Insufficient memory, 2 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. この例では、5台のうち3台がメモリの空き不足、2台が許容していない印によって除外されています。理由が2つ並んでいるので、対処も2通り考えられます。空きを増やすか、印を許容する設定を加えるかです。 配置が済んでいる場合は、この記録が出ません。代わりに、コンテナごとの待機の理由が入ります。この場合、Pending という表示のままイメージの取得や保管領域の準備が進んでいます。 まず最初に:配置先が決まっているかを見る 第一に、配置先が入っているかを確認します。 kubectl get pod <ポッド名> -o jsonpath='{.spec.nodeName}{"\n"}' 何も出なければ、まだ置き場所が決まっていません。配置の問題として調べます。機器の名前が出れば、置き場所は決まっています。起動準備の問題として調べます。 ...

NAME                    READY   STATUS    RESTARTS   AGE
my-app-5f8c7d9b4-nq3vt  0/1     Pending   0          6m41s
2026年7月29日 · ErrorLog
Terraform 422

Terraform の 422 エラー:原因と解決策

冒頭まとめ Terraform で 422 Unprocessable Entity を受け取ったとき、まず押さえるべきことがあります。このエラーは Terraform が作ったものではありません。Terraform 本体には、要求の内容を検証して 422 を返す仕組みがありません。目にする 422 は、必ずどこかから中継されたものです。 出どころは2つに分かれます。プロバイダが相手先の窓口を叩いた結果として返ってきたものと、HCP Terraform や Terraform Enterprise の窓口を直接叩いた結果として返ってきたものです。前者の場合、応答の中身は相手先の流儀に従います。GitHub の窓口なら GitHub の書式、別のサービスならそのサービスの書式です。したがって、読み方は相手先の規則で決まります。Terraform の文書をいくら読んでも、応答の中身の意味は書かれていません。 もう1つ、共通する性質があります。Terraform の各層は、422 を再試行しません。レジストリ向けの通信で使われている仕組みは、再試行の対象を429と、501を除く500番台に限っています。AWS 向けの実装が再試行の対象として定義しているのも 500・502・503・504 です。どこにも 422 は含まれていません。これは怠慢ではなく設計です。要求を直さない限り結果が変わらないエラーなので、送り直す意味がありません。 エラーの概要 プロバイダ経由の場合、相手先の応答がそのまま、あるいは整形されて表示されます。GitHub を相手にした場合の例です。 Error: PATCH https://api.github.com/repos/example/sample: 422 Validation Failed [{Resource:Repository Field:default_branch Code:invalid Message:Cannot update default branch for an empty repository.}] 同じ相手先でも、詳細が空のことがあります。この場合、手がかりは短い文言だけです。 { "message": "Validation Failed", "errors": [], "documentation_url": "https://docs.github.com/rest/reference/repos#update-a-repository" } 別のサービスを相手にした場合は、書式が変わります。 Bad response statusCode [422]. Status [422 Unprocessable Entity]. Body: [baseType=error, code=InvalidBodyContent, message=must have at least one node pool] HCP Terraform の窓口を直接叩いた場合は、また別の形になります。整形の規約に沿った構造で、status と title と detail が並びます。 ...

Error: PATCH https://api.github.com/repos/example/sample: 422 Validation Failed
[{Resource:Repository Field:default_branch Code:invalid
  Message:Cannot update default branch for an empty repository.}]
2026年7月29日 · ErrorLog