Внутренний сервер
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. Без него эндпоинты health, metrics и config недоступны по 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 не подразумевается — укажите 127.0.0.1/32, чтобы сохранить доступ с localhost. Некорректный список прерывает запуск |
Эндпоинты
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. Сервер выдаёт предупреждение при запуске, если слушатель открыт за пределами хоста без заданного списка разрешений - Не публикуйте как Kubernetes Service — объявите порт как
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Устранение неполадок
Эндпоинты health, metrics и config недоступны
INTERNAL_ADDR не задан.
Исправление: Добавьте переменную окружения:
INTERNAL_ADDR=0.0.0.0:9090/health возвращает 503, но приложение работает
Загруженный плагин сообщает PluginHealth::Failed или, реже, executor_healthy равен false либо pool_stalled равен true. Проверьте объект plugins в ответе health, чтобы определить, какой плагин даёт сбой:
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
- Проверки работоспособности — подробное поведение проверок работоспособности
- Справочник конфигурации — все переменные окружения
- Корректное завершение работы — последовательность завершения работы и жизненный цикл внутреннего сервера