エラーの内容
Readiness probe failed: HTTP probe failed with statuscode: 500
新しいサービスバージョンをデプロイした後にこのエラーが発生しました。Podは正常に起動しましたが、0/1 Running のまま止まり、トラフィックが届きませんでした。Serviceは古いPodにルーティングし続け、readiness probeがヘルスエンドポイントをチェックするたびに500が返ってきて、Kubernetesは静かに新しいPodをローテーションから外していたのです。
なぜこうなるのか
KubernetesはreadinessProbeを使ってトラフィックの受け入れ可否を判断します。probeエンドポイントが2xxまたは3xx以外を返すと、そのPodはnot readyとマークされ、ServiceのEndpointリストから除外されます。プロセス自体は生きていますが、リクエストを受け付けない状態になります。
ヘルスエンドポイントが500を返す主な原因:
- アプリが初期化中(DBハンドシェイク、キャッシュウォームアップ、設定ファイルの解析がまだ進行中)
- 依存サービス(Postgres、Redis、上流API)がダウンしているか、Podから到達不能になっている
- ヘルスエンドポイント自体がハンドルされていない例外をスローしている
- 環境変数やシークレットが未設定、空、または誤ったホストを指している
- probeが間違ったパスまたはポートを確認している
ステップごとの修正手順
ステップ1:PodのステータスとEventを確認する
kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
describeの出力の一番下にあるEventsセクションまでスクロールします。次のような行が繰り返し表示されます:
Warning Unhealthy 5s kubelet Readiness probe failed: HTTP probe failed with statuscode: 500
あわせて、出力に表示されているprobe設定も確認しましょう。パス、ポート、タイミングの値が実際にアプリが公開しているものと一致しているか確認してください。
ステップ2:アプリケーションのログを確認する
kubectl logs <pod-name> -n <namespace>
# Pod内に複数のコンテナがある場合:
kubectl logs <pod-name> -c <container-name> -n <namespace>
# リアルタイム追跡:
kubectl logs -f <pod-name> -n <namespace>
500の原因はほぼ必ずアプリ自身がログに出力しています。起動後の最初の数秒間のスタックトレース、接続拒否エラー、「必須環境変数が見つからない」といったメッセージを探してください。
ステップ3:ヘルスエンドポイントに直接アクセスする
Pod内にexecして、probe対象のパスに自分でcurlを実行します。推測は不要です:
kubectl exec -it <pod-name> -n <namespace> -- sh
# Pod内で:
curl -v http://localhost:8080/health
レスポンスボディを読んでください。Spring Bootアプリからの500は"Unable to acquire JDBC Connection"と表示されることがあります。Node.jsサービスであればスタックトレースが出力されるかもしれません。ボディこそが実際の診断結果です。無視しないでください。
ステップ4:Probe設定を確認する
Deploymentの完全なYAMLを取得します:
kubectl get deployment <deployment-name> -n <namespace> -o yaml
readinessProbeブロックを確認します:
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
よく見かける3つのミス:
- パスの間違い — アプリは
/healthzを公開しているのにprobeが/healthを確認し、フレームワークによっては404が500に変換される - ポートの間違い — アプリは3000番でリッスンしているのにprobeが8080番を叩いている
- initialDelaySecondsが短すぎる — JVMアプリは通常45〜60秒かかるのに、probeが10秒後に発火している
ステップ5:環境変数とシークレットを確認する
kubectl exec -it <pod-name> -n <namespace> -- env | grep -i db
kubectl exec -it <pod-name> -n <namespace> -- env | grep -i redis
DATABASE_URLが空だったり、実際のDBサービスではなくlocalhostを指していたりすると、接続を試みるヘルスチェックはすべて失敗します。シークレットがマウントされているか確認してください:
kubectl get secret <secret-name> -n <namespace> -o yaml
kubectl describe pod <pod-name> -n <namespace> | grep -A5 "Environment"
ステップ6:initialDelaySecondsを一時的に増やす
起動が遅いアプリの場合は、根本的な修正をロールアウトするまでの間、遅延を増やしておきましょう:
kubectl patch deployment <deployment-name> -n <namespace> --type='json' \
-p='[{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/initialDelaySeconds", "value": 60}]'
または対話的に編集する場合:
kubectl edit deployment <deployment-name> -n <namespace>
こうすることで、probe失敗が連鎖してロールアウトが壊れるリスクを避けながら、本当の問題を診断する時間が得られます。
ステップ7:依存サービスの問題を修正する
500の原因がアプリからPostgresやRedisへの接続失敗である場合、まずPod内部から接続をテストします:
# Postgres:
kubectl exec -it <pod-name> -n <namespace> -- nc -zv <db-host> 5432
# Redis:
kubectl exec -it <pod-name> -n <namespace> -- nc -zv <redis-host> 6379
到達できない場合、probe設定は問題ではありません。NetworkPolicy、Service DNSの名前解決、DBポッド自体がready状態かどうかを確認してください。接続性を修正すれば、probeの結果は自然と改善されます。
修正の確認
Podがreadyになる様子をリアルタイムで確認します:
kubectl get pods -n <namespace> -w
READYが数回のprobeサイクル以内に0/1から1/1に切り替わるはずです。次に、PodがEndpointリストに加わったことを確認します:
kubectl get endpoints <service-name> -n <namespace>
そこにPodのIPが表示されるはずです。エンドツーエンドのテストも簡単に行いましょう:
kubectl port-forward svc/<service-name> 8080:80 -n <namespace>
curl http://localhost:8080/
次回のためのヒント
- livenessとreadinessを分離する:livenessはプロセスが生きているかどうか(ハング・デッドロックしていないか)のみをチェックすべきです。readinessはトラフィックを処理できるかどうかを確認します。DB接続確認をlivenessに組み込むと、深夜2時にDBが一時的に不安定になるたびに不要なPod再起動が発生します。
- ヘルスエンドポイントを軽量に保つ:probe実行のたびに
SELECT 1クエリを実行しないでください。20レプリカで10秒間隔の場合、ヘルスチェックだけで毎分120のDBクエリが発生します。コネクションプールの確認で十分です。 - 起動が遅いアプリにはstartupProbeを使う:Kubernetes 1.18以降では
startupProbeが使えます。initialDelaySecondsを大きくする代わりにこちらを使いましょう。startupProbeが通過するまでliveness/readinessを無効にすることで、アプリに余裕を持たせつつ、後の障害検知も確実に行えます。 - failureThresholdを1にしない:一時的な瞬断でPodをローテーションから外すべきではありません。ほとんどのサービスでは3回失敗を基準にするのが妥当です。
# 一般的なWebサービス向けの堅実なprobe設定
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 5
startupProbe:
httpGet:
path: /health/live
port: 8080
failureThreshold: 30
periodSeconds: 10

