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

冒頭まとめ Kubernetes の 409 Conflict は、1つの意味を持つエラーではありません。実装を読むと、409 を返す構築関数が3つあります。 1つ目は、同じ名前のものが既に存在する場合です。区分は AlreadyExists、文言は対象の名前に「already exists」を付けた形になります。 2つ目は、更新しようとした対象が、読み取ってから書き込むまでの間に他者に変更されていた場合です。区分は Conflict、文言は「Operation cannot be fulfilled on …」で始まり、中に「the object has been modified; please apply your changes to the latest version and try again」が入ります。 3つ目は、Server-Side Apply でフィールドの所有権が衝突した場合です。区分は同じ Conflict ですが、details.causes にフィールドごとの衝突と、その所有者の名前が入ります。文言は「Apply failed with N conflict(s)」の形です。 この3つは、対処が正反対です。1つ目は既存を使うか名前を変える。2つ目は読み直してからやり直す。3つ目は所有権を奪うか、手放すか、共有するかを選ぶ。同じ要求をそのまま送り直して直るものは、1つもありません。 したがって、409 を見たら最初にやるのは reason の確認、次に details の確認です。kubectl は区分を括弧付きで表示するので、Error from server (AlreadyExists) か Error from server (Conflict) かがそのまま手がかりになります。 エラーの概要 既に存在する場合の応答です。 { "kind": "Status", "status": "Failure", "message": "configmaps \"app-config\" already exists", "reason": "AlreadyExists", "details": { "kind": "configmaps", "name": "app-config" }, "code": 409 } 楽観ロックの競合は、区分も文言も変わります。 ...

2026年8月3日 · ErrorLog

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

冒頭まとめ Kubernetes の 422 Unprocessable Entity は、区分が Invalid のエラーです。意味は明快で、内容は読めたが、検証を通らなかったという状態を指します。 このエラーの扱いやすさは、応答の details.causes にあります。実装を読むと、検証のエラー一覧がそのまま causes に変換され、各要素に どのフィールドか(field) と なぜ駄目か(reason) が入ります。reason に入る値は決まっていて、必須項目の欠落なら FieldValueRequired、値が不正なら FieldValueInvalid、対応していない値なら FieldValueNotSupported、禁止された操作なら FieldValueForbidden といった具合です。つまり、推測は不要です。どこがなぜ駄目かは応答に書かれています。 もう1つ、実務で最も誤解されている点があります。知らないフィールドを書いても 422 にはなりません。公式文書には、検証の水準を厳格にした場合、未知または重複したフィールドを検出すると 400 Bad Request で拒否する、と明記されています。さらに但し書きとして、既知のフィールドに型の違う値を入れた場合も 400 になる、とも書かれています。 したがって境界はこうなります。読めなかったのが 400、読めたが内容が通らなかったのが 422。綴りを間違えた、型を間違えた、というよくある失敗は 400 側に落ちます。422 が返っているなら、書式の問題ではなく意味の問題です。 エラーの概要 応答の構造は次の形です。details.causes が本体で、message はその要約にすぎません。 { "kind": "Status", "status": "Failure", "message": "Deployment.apps \"web\" is invalid: spec.selector: Invalid value: ...: field is immutable", "reason": "Invalid", "details": { "group": "apps", "kind": "Deployment", "name": "web", "causes": [ { "reason": "FieldValueInvalid", "field": "spec.selector", "message": "Invalid value: ...: field is immutable" } ] }, "code": 422 } kubectl からの見え方には特徴があります。実装を読むと、区分が Invalid の場合だけ専用の整形が行われ、他のエラーのような Error from server (...) の形にはなりません。 ...

2026年8月3日 · ErrorLog

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

冒頭まとめ Kubernetes の 429 Too Many Requests には、出どころの違う3つの系統があります。 1つ目は、API サーバーの過負荷保護です。優先度と公平性の仕組み(API Priority and Fairness)が、混雑時に要求を落とします。2つ目は、Pod の退避が PodDisruptionBudget に阻まれた場合です。これは過負荷とは無関係で、「今は許可できない」という意味の拒否です。3つ目は、API サーバー以外、たとえばイメージの取得元が返す制限です。 さらに厄介なのが、429 に見えて 429 ではないものです。ログに「client-side throttling, not priority and fairness」と出ている場合、要求はサーバーにまだ送られていません。クライアント側が自分で待っているだけです。この文言は、ソフトウェア側の実装で「優先度と公平性の仕組みではない」と明示的に書かれています。ここを取り違えると、サーバー側をいくら調べても何も出てきません。 したがって、429 に当たったら最初にやるのは原因の推測ではなく、どこが返したのかの確定です。応答の区分、details の内容、Retry-After の値、この3つで系統が決まります。 エラーの概要 過負荷保護による 429 は、素っ気ない応答です。優先度と公平性の仕組みが要求を落とすとき、実装は Retry-After ヘッダーを付けたうえで、本文に短い文言だけを返します。 HTTP/1.1 429 Too Many Requests Retry-After: 3 Too many requests, please try again later. 一方、退避が拒否された場合の応答は、構造化された情報を持ちます。 { "kind": "Status", "status": "Failure", "message": "Cannot evict pod as it would violate the pod's disruption budget.", "reason": "TooManyRequests", "details": { "causes": [ { "reason": "DisruptionBudget", "message": "The disruption budget web-pdb needs 7 healthy pods and has 6 currently" } ] }, "code": 429 } 同じ 429 でも、details.causes の有無で系統が分かれます。DisruptionBudget が入っていれば退避の拒否であり、混雑とは関係ありません。 ...

2026年8月3日 · ErrorLog

Kubernetes の CreateContainerConfigError:原因と解決策

冒頭まとめ CreateContainerConfigError は、Kubernetes がコンテナを起動する前段階、設定を組み立てる段階で失敗したことを示します。イメージの取得は成功しており、コンテナの作成にも到達していません。 重要なのは、この文字列自体には原因が書かれていないことです。実装を見ると、これは分類のための名前で、kubectl get pods の状態欄にはこの名前だけが出ます。実際の原因は、kubectl describe pod のイベントの側に入ります。しかも、そのイベントの理由欄は CreateContainerConfigError ではなく Failed です。実装で理由の定数がそう定義されています。 原因の大半は、環境変数として参照している ConfigMap や Secret が解決できないことです。文言は2種類に分かれます。参照先そのものが無い場合は secret "app-secrets" not found の形、参照先はあるがキーが無い場合は couldn't find key API_KEY in Secret default/app-secrets の形になります。実装でも、この2つは別々の分岐で作られています。 もう1つ、実務で効く性質があります。kubelet は失敗しても作成を繰り返します。したがって、足りない ConfigMap や Secret を後から作れば、Pod を作り直さなくても起動します。実際の報告を見ても、イベントには同じ失敗が数分間で8回といった形で記録されています。 エラーの概要 まず状態欄です。分類名だけが出ます。 NAME READY STATUS RESTARTS AGE app-6f8d9c7b5-x4k2h 0/1 CreateContainerConfigError 0 64s 原因はイベントにあります。理由欄が Failed である点に注意してください。 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Pulled 3m (x8 over 9m) kubelet Successfully pulled image "app:v1.2" Warning Failed 3m (x8 over 9m) kubelet Error: secret "app-secrets" not found コンテナの状態を直接読むこともできます。分類名と文言が対で入っています。 ...

2026年8月3日 · ErrorLog

Kubernetes の Evicted:原因と解決策

冒頭まとめ Evicted は、Kubernetes の kubelet がノードの資源を守るために Pod を落とした状態です。実装では理由の文字列が Evicted と定義され、Pod は失敗として終了します。 最初に押さえるべきは、これは Pod が使いすぎたという意味ではないことです。公式文書によれば、kubelet は退避シグナルを閾値と比較して退避を決めます。判定の対象はノード側の空き資源です。既定のハード閾値は次のとおりです。 memory.available < 100Mi(Linux)/< 500Mi(Windows) nodefs.available < 10% imagefs.available < 15% nodefs.inodesFree < 5%(Linux) imagefs.inodesFree < 5%(Linux) つまり、ノードがこの線を割った瞬間に、誰かが落とされます。落とされる側の使用量の多さは、順番を決める材料にすぎません。 ここが最大の誤解の元です。文言には「Container X was using 122Ki, request is 0」のような使用量と要求量が並びますが、これは選ばれた理由であって、退避が起きた原因ではありません。この表現が分かりにくいという指摘は、公式の課題として複数回登録されています。 もう1つ、OOMKilled との違いも重要です。公式文書に明記があり、コンテナが OOM で落とされた場合は再起動方針に従って再起動されますが、Pod の退避では再起動されません。 エラーの概要 一覧では状態として現れます。 NAME READY STATUS RESTARTS AGE app-7d8f767544-pk4ch 0/1 Evicted 0 12m app-7d8f767544-q2n8x 0/1 Evicted 0 12m 詳細を見ると、フェーズは失敗、理由が Evicted、そして本文に経緯が入ります。 Status: Failed Reason: Evicted Message: The node was low on resource: ephemeral-storage. Threshold quantity: 94576558032, available: 92034400Ki. Container app was using 122Ki, request is 0, has larger consumption of ephemeral-storage. 実装を読むと、この文言は部品の組み合わせで作られています。どの資源が不足したか、閾値と実際の空き、そして選ばれたコンテナの使用量と要求量です。ほかに、ノードの状態を示す形式、一時領域の上限を超えた場合、一時的なボリュームの使用量が上限を超えた場合の文言も定義されています。 ...

2026年8月3日 · ErrorLog

Kubernetes の NodeNotReady:原因と解決策

冒頭まとめ kubectl get nodes に NotReady と表示されたとき、まず確認すべきは条件の値です。Kubernetes の Ready 条件は3つの値を取り、表示は同じでも意味が違います。 公式の定義はこうです。True はノードが健全で Pod を受け入れられる状態、False は健全ではなく受け入れていない状態、そして Unknown はノード制御役が既定50秒の猶予の間にノードから連絡を受け取れなかった状態です。 この違いが調査の方向を決めます。False はノード自身が「準備できていない」と申告しているので、ノードの中を調べます。Unknown は申告そのものが届いていないので、ノードと制御側の間を調べます。ノードが正常に動いていても Unknown にはなり得ます。 時間の流れも押さえておくと役に立ちます。ノード制御役は5秒ごとに状態を確認し、連絡が途絶えて50秒で Unknown にします。そこから既定で5分待って、Pod の退去を始めます。この5分は、node.kubernetes.io/not-ready と node.kubernetes.io/unreachable に対して自動的に付与される猶予(tolerationSeconds=300)によるものです。 つまり、NotReady になってもすぐ Pod は動かない。逆に、5分を過ぎると一斉に動き始めます。 エラーの概要 一覧では状態として現れます。 NAME STATUS ROLES AGE VERSION node-01 Ready <none> 30d v1.32.1 node-02 NotReady <none> 30d v1.32.1 詳細を見ると、条件と最終連絡時刻が入ります。ここが判断材料です。 Conditions: Type Status LastHeartbeatTime Reason Message ---- ------ ----------------- ------ ------- MemoryPressure Unknown Mon, 03 Aug ... 12:01 NodeStatusUnknown Kubelet stopped posting node status. DiskPressure Unknown Mon, 03 Aug ... 12:01 NodeStatusUnknown Kubelet stopped posting node status. Ready Unknown Mon, 03 Aug ... 12:01 NodeStatusUnknown Kubelet stopped posting node status. すべての条件が同時に Unknown になっていれば、連絡が途絶えた形です。最終連絡時刻を見れば、いつ止まったかが分かります。 ...

2026年8月3日 · ErrorLog

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" このとき記録に残る転送先の番号を確認してください。ここに出ている番号が、自分が意図したコンテナの番号と違っていれば、原因は設定の食い違いです。 ...

2026年7月29日 · ErrorLog

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秒で落ちているので、起動処理のどこかで失敗していると分かります。 ...

2026年7月29日 · ErrorLog

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 という表示だけを見ていても、この文言には辿り着きません。 ...

2026年7月29日 · ErrorLog

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(上限が実態に対して低い): ...

2026年7月29日 · ErrorLog