ヘルスチェック
OxPHP は、ヘルスモニタリング、メトリクス収集、設定確認のために、別ポートで内部 HTTP サーバーを実行します。このサーバーはアプリケーショントラフィックから隔離されているため、モニタリングがユーザーリクエストと競合することはありません。
セットアップ
INTERNAL_ADDR を設定すると内部サーバーが有効になります。
INTERNAL_ADDR=127.0.0.1:9090INTERNAL_ADDR が設定されていない場合、内部サーバーは起動せず、ヘルスチェックエンドポイントは一切利用できません。
ポートのみを指定した INTERNAL_ADDR(:9090)は 127.0.0.1 にバインドされます。内部サーバーをホスト外に公開するには、明示的に 0.0.0.0:9090 を指定してください。ホスト外から到達可能にする場合は、INTERNAL_ALLOW_IPS(CIDR/IP の許可リスト)でアクセスを制限してください。許可リストの範囲外のピアに対しては /metrics、/config、およびプラグインのパスが 403 を返しますが、ヘルスプローブは引き続き到達可能なままです。ループバックは暗黙的に許可されないため、localhost からのアクセスを維持するには 127.0.0.1/32 を列挙してください。許可リストを設定せずにリスナーが公開されている場合、サーバーは起動時に警告を出します。
Kubernetes プローブ
OxPHP は、各 Kubernetes プローブタイプ専用のエンドポイントを提供します。各エンドポイントは短いエイリアス(/healthz、/readyz、/startupz)でも利用できます。
| エンドポイント | エイリアス | チェック内容 | 200 | 503 |
|---|---|---|---|---|
/health/liveness |
/healthz |
なし(応答すれば生存) | 常に | 一度もなし |
/health/readiness |
/readyz |
シャットダウン中でない、ワーカースレッドが稼働中、プールが行き詰まっていない、失敗したプラグインがない | 準備完了 | 準備未完了 |
/health/startup |
/startupz |
ワーカースレッドが稼働中 | 準備完了 | 準備未完了 |
Liveness は常に 200 OK を返します。プロセスが HTTP リクエストに応答できれば、生存していると見なされます。executor やプラグインのチェックは行われません。これにより、一時的なワーカープールの問題が原因で Kubernetes がポッドを再起動してしまうのを防ぎます。
Readiness は次の場合に 503 Service Unavailable を返します。
- サーバーがシャットダウン中である(グレースフルシャットダウンの進行中)
- すべての PHP ワーカースレッドが終了している(リクエストを渡せる先がプールに残っていない)
- プールが行き詰まっている: リクエストがワーカーを待っているのに、ワーカーはアイドル状態のまま何も処理していない。これはワーカーモードでのみ検出されます(行き詰まったプールのシグナル を参照)
- いずれかのプラグインが障害を報告している
グレースフルシャットダウン中、readiness はただちに 503 を返し、これによりドレインが完了する前に Kubernetes がポッドを Service エンドポイントから取り除きます。
Startup は稼働中のワーカースレッドが 1 つもない間、503 Service Unavailable を返します。このプローブを使うと、初期化が遅い間に liveness による早すぎる強制終了を防げます。報告対象はスレッドであり、スレッドはプールが作成された時点で存在します。スレッド内で実行されるアプリケーション自身のブートストラップについては報告しません。
行き詰まったプールのシグナル
プールが所有するスレッドがすべて生きているのに、キューから仕事を取り出さなくなることがあります。リクエストは溜まり続け、ビジーなワーカーはなく、キューに入ったものは一向に実行されません。各リクエストは自身の待機予算を使い切った時点で応答を受け取る(529、またはクライアントが先に待つのをやめた場合は 499)ため、外から見るとハングには見えません。リクエストへの応答は返っていても、実際に実行されるものは 1 つもなく、それでもキューは埋まっていきます。エントリを解放するのはワーカーによる取り出しだけだからです。この間、ほかのシグナルはすべて正常を示します。スレッドはすべて揃っており、プラグインにも問題はなく、静的ファイルも普通に配信されます。1 分経ってもこの状態のままのプールは、通常は自力では回復しません。readiness がこれを報告するのはそのためです。
サーバーは、処理を続けているプールでは持続しない組み合わせを監視しています。待機中の仕事がある(キューに入ったリクエストがある、または受け入れ拒否が増え続けている)一方で、少なくとも 1 つのワーカーがアイドル状態であり、かつ前回のスキャン以降プールが何も完了していない、という状態です。
- 1 秒間隔のスキャンで 2 回連続すれば、ログ行が出力されます。
- readiness はさらに 1 分待ちます。ワーカーが一斉に停止したプール(多くの場合、ワーカーが 1 つだけのプール)は、代わりのワーカーがアプリケーションのブートストラップを再実行している間、まさにこの形を示します。新しいワーカーは 1 分が経つよりずっと前に処理を再開しており、レプリカを早々に外してしまうと、それを示すのに必要なトラフィックまで奪うことになります。
- readiness が回復するのは、ワーカーが実際にリクエストを 1 つ完了したとき(単なる小康状態は回復とは見なしません)、またはプールが一度も行き詰まって見えなかった 1 分が経過したときです。後者の抜け道にはトラフィックは不要です。行き詰まったプールはキューを抱えたままなのでこの条件を満たせませんが、誤って行き詰まりと判定されたプールなら満たせます。これがなければ状態は永続してしまいます。
503によって、完了することでもう一方の抜け道となるはずのリクエストそのものが届かなくなるからです。
このシグナルにはワーカーが処理したリクエスト数が必要で、それを保持しているのはワーカーモード(WORKER_MODE_ENABLED=true)だけなので、このシグナルはワーカーモードにのみ存在します。それ以外のモードでは readiness は残りの 3 つの条件で応答し、oxphp_pool_stalled は定数 0 としてエクスポートされるのではなく、存在しません。
プローブが報告しないもの
負荷。 過負荷のサーバーは Ready のままです。受け入れキューが満杯になり待機予算が尽きると、リクエストは 529 Site is overloaded で拒否されますが、そうしているレプリカは、正常に動くプールの上で素早く応答し続けています。それをローテーションから外すと、そのトラフィックが隣のレプリカに移り、均等な過負荷のもとではレプリカが順に外されていき、最終的に Service のエンドポイントが 1 つも残らなくなります。劣化していたサイトが、負荷はそのままに到達不能なサイトになってしまうのです。代わりに oxphp_admission_refused_total{reason=...} を監視し、キャパシティを追加してください。ただし oxphp_admission_wait_wasted_total は oxphp_admission_wait_budget_us と併せて読んでください。wait_timeout による拒否は、それだけではプールが仕事をこなしていることを意味しません。リクエストの待機期間中ずっとプールがワーカーを 1 つも解放しない場合、対処は逆になります。プールを大きくするのではなく、待機予算を短くしてください。このカウンターは、予算が上限に達している間だけカウントします。サーバーはこのことをログでも伝えます。リクエストの切り捨て(シェディング)を始めたスキャンで WARN を出し、それが続く間はおよそ 1 分ごとに繰り返し、拒否のないまま丸 1 分が経過すると INFO を 1 回出します。Prometheus メトリクス を参照してください。
接続予算の枯渇。 MAX_CONNECTIONS の許可数が尽きると accept ループは停止し、新しいクライアントには一切応答が返りません。しかし内部リスナーはこの予算を経由しないため、プローブは応答し続けます。これも障害ではなく負荷であり、readiness は変わりません。これを報告するのは oxphp_accept_stalled です。
遅いレスポンス。 ここにはレイテンシを測るものはありません。すべてに遅れて応答するプールも Ready です。
すべてのプローブエンドポイントは Content-Type: text/plain を返し、ボディにはプローブ名(例: readiness)が入ります。Kubernetes は HTTP ステータスコードのみを参照します。
# Quick check
curl -s -o /dev/null -w '%{http_code}' http://localhost:9090/health/readinessGET /health
サーバー全体のヘルス状態を JSON で返します。Kubernetes プローブではなく、ダッシュボードやモニタリングシステムで使用してください。
curl http://localhost:9090/health正常時のレスポンス(200 OK):
{
"active_connections": 7,
"executor_healthy": true,
"plugins": {},
"pool_stalled": false,
"status": "ok",
"total_requests": 48203,
"uptime_secs": 3612
}劣化時のレスポンス(503 Service Unavailable):
{
"active_connections": 7,
"executor_healthy": false,
"plugins": {},
"pool_stalled": false,
"status": "degraded",
"total_requests": 48203,
"uptime_secs": 3612
}| フィールド | 型 | 説明 |
|---|---|---|
status |
string | すべてのサブシステムが正常なとき "ok"、それ以外は "degraded"。負荷はサブシステムの障害ではありません。過負荷で 529 を返してリクエストを切り捨てているサーバーは "ok" を報告します |
uptime_secs |
integer | サーバー起動からの経過秒数 |
total_requests |
integer | メインポートで処理した HTTP リクエストの総数 |
active_connections |
integer | メインポートで現在オープンしている接続数 |
executor_healthy |
boolean | PHP ワーカースレッドがまだ 1 つでも稼働しているかどうか。false は、リクエストを渡せるスレッドがプールに 1 つも残っていないことを意味します |
pool_stalled |
boolean | プールが行き詰まっているかどうか: リクエストがワーカーを待っているのに、ワーカーはアイドル状態のまま何も処理していない状態。ワーカーモード以外ではこの状態は検出されないため、常に false です(行き詰まったプールのシグナル を参照)。true になると HTTP ステータスは 503 に切り替わります |
plugins |
object<string, string> |
プラグインごとのヘルス状態。キーはプラグイン名、値は "ok"、"degraded"、"failed" のいずれか。ヘルスを報告するプラグインがない場合は空の {}。"failed" のプラグインがあると HTTP ステータスは 503 に切り替わります。"degraded" はここに表示されますが、ステータスは 200 のまま保たれます。 |
GET /metrics
Prometheus 互換のメトリクスをテキスト公開フォーマットで返します。すべてのメトリクスの完全なリファレンスは Prometheus メトリクス を参照してください。
curl http://localhost:9090/metricsGET /config
有効なサーバー設定を JSON で返します。TLS 証明書と鍵のパス、internal_addr、error_pages_dir は、セキュリティ上の理由からレスポンスから除去されます。
curl -s http://localhost:9090/config | jq .{
"access_log": "all",
"async_in_flight_cap": 0,
"async_max_fibers": 256,
"async_queue_capacity": 0,
"async_workers": 0,
"brotli_level": 5,
"deny_file": {"loaded": false},
"document_root": "/var/www/html/public",
"drain_timeout_seconds": 25,
"entry_file": "/var/www/html/public/index.php",
"executor_type": "sapi",
"gzip_level": 6,
"header_timeout_seconds": 5,
"listen_addr": "0.0.0.0:443",
"log_level": "info",
"max_connections": 10000,
"max_query_body": 524288,
"php_workers": "8",
"plugins": {},
"queue_capacity": 1024,
"queue_max_waiting": 1024,
"queue_max_waiting_bytes": 67108864,
"queue_wait_timeout_ms": 1000,
"rate_limit": 100,
"rate_window_seconds": 60,
"runtime_hooks": [],
"static_max_age": 2592000,
"static_revalidate": false,
"superglobals_enabled": true,
"tls_enabled": true,
"tls_min_version": "1.2",
"tokio_workers": 4,
"trace_context": false,
"trusted_proxies": false,
"worker_max_memory_mib": 0,
"worker_mode_enabled": false,
"zstd_level": 6
}TLS 証明書と鍵のパスは一切出力されません(TLS が有効かどうかは tls_enabled が示します)。また internal_addr と error_pages_dir は返されるレスポンスから除去されます。
Kubernetes との統合
各プローブタイプには専用のプローブエンドポイントを使用してください。
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: oxphp
image: ghcr.io/oxphp/oxphp:0.13.0
env:
- name: INTERNAL_ADDR
value: "0.0.0.0:9090"
- name: INTERNAL_ALLOW_IPS
# the Prometheus scraper's network, plus loopback
value: "10.0.0.0/8,127.0.0.1/32"
ports:
- containerPort: 80
- containerPort: 9090
startupProbe:
httpGet:
path: /health/startup
port: 9090
initialDelaySeconds: 1
periodSeconds: 2
failureThreshold: 15
livenessProbe:
httpGet:
path: /health/liveness
port: 9090
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/readiness
port: 9090
periodSeconds: 5
failureThreshold: 2| プローブ | 失敗時の挙動 |
|---|---|
| Startup | Kubernetes は待機します。初期化中はポッドを強制終了しません |
| Liveness | Kubernetes はポッドを再起動します |
| Readiness | Kubernetes はポッドを Service エンドポイントから取り除きます(再起動はしません) |
短いエイリアス(/healthz、/readyz、/startupz)は完全に同等であり、代わりに使用できます。
Docker Compose のヘルスチェック
この例では内部サーバーを 127.0.0.1 にバインドしていますが、Kubernetes の例では 0.0.0.0 にバインドしています。Docker は healthcheck コマンドをコンテナ自身のネットワーク名前空間の内部で実行し、そこではループバックでリスナーに到達できます。一方、kubelet は httpGet プローブをポッドのネットワーク名前空間の外部から送信するため、ループバックでは到達できません。
services:
oxphp:
image: ghcr.io/oxphp/oxphp:0.13.0
ports:
- "8080:80"
environment:
INTERNAL_ADDR: "127.0.0.1:9090"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:9090/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s設定した回数だけリトライが失敗すると、Docker はコンテナを unhealthy としてマークします。これにより再起動ポリシーやロードバランサーからの除外がトリガーされることがあります。
エージェント向けプロンプト
これらのプロンプトは、OxPHP ドキュメント MCP サーバー を接続したエージェントを前提としています — 必要なページはエージェントが自分で読み取ります。
私のプロジェクト向けに OxPHP のヘルスチェックをセットアップしてください。OxPHP ドキュメント MCP サーバー経由で `operations/health-checks` ページを読んでください。
このリポジトリがすでに定義しているサービスとポートをもとに、私の docker-compose.yml にヘルスチェックを、私の Kubernetes の Deployment に liveness プローブと readiness プローブを追加してください。私の OxPHP コンテナがオーケストレーターに再起動されてしまい、理由がわかりません。OxPHP ドキュメント MCP サーバー経由で `operations/health-checks` ページを読んでください。
私のヘルスチェックの設定を、内部サーバーが実際に公開しているものと突き合わせて、この 2 つのうちどちらが間違っているのかを教えてください。関連情報
- Prometheus メトリクス — 公開されるすべてのメトリクスの完全なリファレンス
- グレースフルシャットダウン — ヘルスプローブがシャットダウンのドレインとどのように連携するか
- 設定リファレンス —
INTERNAL_ADDRを含むすべての環境変数