Внутрішній сервер
OxPHP запускає окремий HTTP-сервер на виділеному порту для перевірок стану, метрик Prometheus і перегляду поточної конфігурації. Він повністю ізольований від основного слухача застосунку: жодного TLS, обмеження частоти запитів, ідентифікаторів запитів чи обробки подій.
Як це працює
Задайте в INTERNAL_ADDR адресу для прослуховування (наприклад, 127.0.0.1:9090), і OxPHP запустить на ній другий HTTP-слухач. Внутрішній сервер надає такі вбудовані ендпоінти:
/health— зведений статус у JSON/health/liveness,/healthz(псевдонім) — проба liveness/health/readiness,/readyz(псевдонім) — проба readiness/health/startup,/startupz(псевдонім) — проба startup/metrics— метрики у форматі Prometheus/config— JSON з активною конфігурацією сервера
Плагіни можуть реєструвати додаткові ендпоінти з префіксом /__<plugin>/. Під час плавного завершення роботи внутрішній сервер лишається доступним, доки основний сервер не закінчить завершувати зʼєднання.
Внутрішній сервер запускається лише тоді, коли INTERNAL_ADDR задано явно. Без неї ендпоінти стану, метрик і конфігурації через HTTP недоступні.
Налаштування
| Змінна | За замовчуванням | Опис |
|---|---|---|
INTERNAL_ADDR |
(не задано) | Адреса внутрішнього сервера. Якщо не задано, сервер не запускається. Значення лише з портом (:9090) привʼязується до 127.0.0.1; щоб відкрити доступ ззовні хоста, вкажіть явно 0.0.0.0:9090. Приклад: 127.0.0.1:9090 |
INTERNAL_ALLOW_IPS |
(не задано) | Список дозволених CIDR/IP через кому. Клієнти поза списком отримують 403 на шляхах /metrics, /config і /__<plugin>/; проби стану завжди лишаються доступними. Не задано або порожньо — дозволено всім. Loopback не додається неявно: щоб зберегти доступ з localhost, додайте 127.0.0.1/32. Некоректний список перериває запуск |
Ендпоінти
GET /health
Повертає статус стану в JSON для дашбордів і перевірок стану контейнерів. Для Kubernetes натомість використовуйте окремі ендпоінти проб (див. Інтеграція з Kubernetes): /health звітує про всі підсистеми одразу, тож, підключений до liveness, він перезапускав би под через збій будь-якої з них.
200 OK — усі системи справні:
{
"active_connections": 7,
"executor_healthy": true,
"plugins": {
"otel": "ok"
},
"pool_stalled": false,
"status": "ok",
"total_requests": 48203,
"uptime_secs": 3612
}503 Service Unavailable — плагін повідомив про збій:
{
"active_connections": 7,
"executor_healthy": true,
"plugins": {
"otel": "failed"
},
"pool_stalled": false,
"status": "degraded",
"total_requests": 48203,
"uptime_secs": 3612
}Обʼєкт plugins містить кожен завантажений плагін із його станом: "ok", "degraded" або "failed". Ендпоінт повертає 503, коли будь-який плагін звітує "failed", коли не лишилося жодного робочого потоку PHP (executor_healthy: false) або коли пул заклинило (pool_stalled: true, лише в режимі воркерів; див. Перевірки стану). Плагіни зі станом "degraded" зʼявляються в тілі JSON, але HTTP-статус лишається 200. Навантаження теж не вважається несправністю: сервер, який під перевантаженням відкидає запити з 529, усе одно звітує "ok".
GET /metrics
Повертає метрики, сумісні з Prometheus, у текстовому форматі (text/plain; version=0.0.4; charset=utf-8). Завжди повертає 200.
curl http://localhost:9090/metrics# HELP oxphp_requests_total Total HTTP requests
# TYPE oxphp_requests_total counter
oxphp_requests_total 48203
# HELP oxphp_active_connections Current open connections
# TYPE oxphp_active_connections gauge
oxphp_active_connections 7
...Повний перелік доступних метрик див. у розділі Метрики.
GET /config
Повертає активну конфігурацію сервера у форматі JSON. Завжди повертає 200.
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": {
"allow": 1,
"deny": 3,
"entry": 1,
"fallback": "404",
"loaded": true,
"rules": 5
},
"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": ["sleep", "streams", "net"],
"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
}runtime_hooks перелічує категорії хуків середовища виконання, які процес встановив під час запуску. Це розібрана множина, а не сире значення RUNTIME_HOOKS, тож all, 1 і sleep, streams, net однаково читаються як ["sleep", "streams", "net"], і збирачу метрик не треба заново реалізовувати цю граматику. Порожній список означає, що жоден хук не встановлено; те саме дасть і помилка в назві змінної. Поле показує, що було встановлено, а не що діє: там, де файберів немає взагалі, хуки передають виклик вбудованим функціям, які вони замінили, тому сервер у традиційному режимі перелічує налаштовані категорії, але поводиться як зазвичай. Див. Хуки середовища виконання.
deny_file підсумовує файл .oxphpdeny, прочитаний з DOCUMENT_ROOT під час запуску: кількість правил з розбивкою за дією (deny, entry для правил >, allow для правил !) і те, чим відповідає правило заборони, — або кодом статусу з PHP_DENY_FALLBACK, або "script" (без шляху до скрипта). Самі правила не виводяться: на внутрішньому сервері, доступному ззовні, вони були б картою саме тих шляхів, які файл захищає. Щоб побачити, як було прочитано кожне правило, запустіть сервер з LOG_LEVEL=debug і шукайте рядки oxphpdeny rule. Якщо файлу немає, запис має вигляд {"loaded": false}.
Шляхи до файлів TLS-сертифіката й ключа ніколи не виводяться (надається лише булеве значення tls_enabled), а internal_addr і error_pages_dir вилучаються з відповіді — це топологія розгортання та шляхи файлової системи, які допомагають зловмисникові й не потрібні збирачам метрик.
Ендпоінти плагінів
Плагіни можуть реєструвати власні ендпоінти з префіксом /__<plugin_name>/. Наприклад, плагін з назвою otel міг би надавати /__otel/status. Ці ендпоінти доступні, лише якщо відповідний плагін завантажено і він зареєстрував обробник.
Будь-який шлях, що не відповідає вбудованому ендпоінту чи ендпоінту плагіна, повертає 404 Not Found.
Інтеграція з Kubernetes
Підключайте кожну пробу до призначеного для неї ендпоінту, а не спрямовуйте всі три на /health. Цей ендпоінт зводить стан усіх підсистем в один статус, тож у ролі liveness він відповідав би на збій плагіна чи заклинений пул перезапуском пода.
Проба readiness
/health/readiness визначає, чи спрямовує Kubernetes трафік на под:
readinessProbe:
httpGet:
path: /health/readiness
port: 9090
initialDelaySeconds: 2
periodSeconds: 5Коли вона повертає 503, Kubernetes вилучає под зі списку ендпоінтів Service; трафік відновлюється, щойно вона знову повертає 200. Проба звітує про несправності, а не про навантаження, тому перевантажений под лишається в ротації. Див. Перевірки стану.
Проба liveness
/health/liveness відповідає 200 щоразу, коли процес узагалі здатен обслужити HTTP-запит. Саме це й має означати liveness, адже перезапуск не виправить перевантажений пул, зате забере з собою всю роботу, що виконується:
livenessProbe:
httpGet:
path: /health/liveness
port: 9090
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3Проба startup
Для застосунків із повільним початковим завантаженням (великі фреймворки, важкі автозавантажувачі):
startupProbe:
httpGet:
path: /health/startup
port: 9090
initialDelaySeconds: 1
periodSeconds: 2
failureThreshold: 15Безпека
Внутрішній сервер не має автентифікації за bearer-токеном; доступ визначається тим, до якої адреси він привʼязаний, і змінною INTERNAL_ALLOW_IPS. Щоб захистити його:
- Привʼязуйте до localhost —
INTERNAL_ADDRлише з портом (:9090) вже привʼязується до127.0.0.1; явне127.0.0.1:9090залишає порт доступним лише зсередини контейнера чи хоста - Задайте
INTERNAL_ALLOW_IPS, коли слухач має бути доступним ззовні хоста — це список дозволених CIDR/IP, який повертає403на/metrics,/configі шляхах плагінів для клієнтів поза ним, тоді як проби стану лишаються доступними. Loopback не додається неявно, тож додайте127.0.0.1/32, якщо вам і далі потрібен доступ з localhost. Під час запуску сервер попереджає, якщо слухач відкритий ззовні хоста без заданого списку дозволених адрес - Не відкривайте його як Service у Kubernetes — оголосіть порт як
containerPort, але не створюйте для нього Service. Проби Kubernetes звертаються до портів контейнера напряму - Використовуйте мережеві політики — обмежте доступ на мережевому рівні, якщо порт усе ж потрібно відкрити
Ендпоінт /config розкриває операційні подробиці (корінь документів, обмеження частоти запитів, кількість воркерів, значення тайм-аутів). Шляхи TLS, internal_addr і error_pages_dir вилучаються, але зважте, чи має решта бути доступною ззовні пода.
Приклад для Docker
Щоб швидко подивитися, можна опублікувати внутрішній порт; у продакшені привʼяжіть внутрішній сервер до localhost, а в Kubernetes, чиї проби httpGet не дістаються до слухача на loopback, привʼяжіть його до 0.0.0.0:9090 і задайте INTERNAL_ALLOW_IPS.
services:
app:
image: ghcr.io/oxphp/oxphp:0.13.0
ports:
- "80:80"
- "9090:9090"
environment:
- DOCUMENT_ROOT=/var/www/html/public
- INTERNAL_ADDR=0.0.0.0:9090services:
app:
image: ghcr.io/oxphp/oxphp:0.13.0
ports:
- "80:80"
environment:
- DOCUMENT_ROOT=/var/www/html/public
- INTERNAL_ADDR=127.0.0.1:9090Усунення несправностей
Ендпоінти стану, метрик і конфігурації недоступні
INTERNAL_ADDR не задано.
Рішення: додайте змінну середовища:
INTERNAL_ADDR=0.0.0.0:9090/health повертає 503, хоча застосунок працює
Один із завантажених плагінів звітує PluginHealth::Failed або, рідше, executor_healthy дорівнює false чи pool_stalled дорівнює true. Перегляньте обʼєкт plugins у відповіді перевірки стану, щоб зʼясувати, який плагін збоїть:
curl -s http://localhost:9090/health | jq '.plugins'Не вдається дістатися до внутрішнього сервера ззовні контейнера
Сервер привʼязаний до 127.0.0.1, який доступний лише зсередини контейнера.
Рішення: змініть адресу на 0.0.0.0 для зовнішнього доступу й задайте INTERNAL_ALLOW_IPS. Пробам Kubernetes httpGet потрібна та сама зміна, бо вони не дістаються до слухача на loopback.
Метрики не показують режим воркерів або асинхронні метрики
Метрики режиму воркерів зʼявляються, лише коли цей режим увімкнено (WORKER_MODE_ENABLED=true). Асинхронні метрики зʼявляються, лише коли ASYNC_WORKERS > 0 і хоча б одне завдання було передано на виконання або відхилено.
Див. також
- Метрики — повний довідник метрик Prometheus
- Перевірки стану — докладна поведінка перевірок стану
- Довідник конфігурації — усі змінні середовища
- Плавне завершення роботи — послідовність завершення та життєвий цикл внутрішнього сервера