Внутренний сервер

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>/. Во время корректного завершения работы внутренний сервер остаётся доступным, пока основной сервер не завершит слив соединений.

Note

Внутренний сервер запускается только при явно заданном 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 — все системы исправны:

json
{ "active_connections": 7, "executor_healthy": true, "plugins": { "otel": "ok" }, "pool_stalled": false, "status": "ok", "total_requests": 48203, "uptime_secs": 3612 }

503 Service Unavailable — плагин сообщил о сбое:

json
{ "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.

bash
curl http://localhost:9090/metrics
text
# 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.

bash
curl -s http://localhost:9090/config | jq .
json
{ "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 трафик на под:

yaml
readinessProbe: httpGet: path: /health/readiness port: 9090 initialDelaySeconds: 2 periodSeconds: 5

Когда он возвращает 503, Kubernetes удаляет под из списка эндпоинтов Service; трафик возобновляется, как только он снова возвращает 200. Эндпоинт сообщает о сбоях, а не о нагрузке, поэтому перегруженный под остаётся в ротации. См. Проверки работоспособности.

Проба liveness

/health/liveness отвечает 200 всегда, когда процесс вообще способен обслужить HTTP-запрос. Именно это и должна означать liveness: перезапуск не исправит перегруженный пул, а выполняющуюся работу унесёт с собой:

yaml
livenessProbe: httpGet: path: /health/liveness port: 9090 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3

Проба startup

Для приложений с медленным запуском (крупные фреймворки, тяжёлые автозагрузчики):

yaml
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 обращаются к портам контейнера напрямую
  • Используйте сетевые политики — ограничивайте доступ на сетевом уровне, если порт всё же нужно открыть
Warning

Эндпоинт /config раскрывает операционные детали (корень документов, ограничения частоты запросов, число воркеров, значения таймаутов). Пути TLS, internal_addr и error_pages_dir вычищаются, но подумайте, должно ли остальное быть доступно снаружи пода.

Пример Docker

Для быстрого просмотра можно опубликовать внутренний порт; в продакшене привязывайте внутренний сервер к localhost, а в Kubernetes, чьи пробы httpGet не достают до слушателя на loopback, — к 0.0.0.0:9090 с заданным INTERNAL_ALLOW_IPS.

compose.yaml
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:9090

Устранение неполадок

Эндпоинты health, metrics и config недоступны

INTERNAL_ADDR не задан.

Исправление: Добавьте переменную окружения:

bash
INTERNAL_ADDR=0.0.0.0:9090
/health возвращает 503, но приложение работает

Загруженный плагин сообщает PluginHealth::Failed или, реже, executor_healthy равен false либо pool_stalled равен true. Проверьте объект plugins в ответе health, чтобы определить, какой плагин даёт сбой:

bash
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 и когда хотя бы одна задача была отправлена или отклонена.

Смотрите также

Нашли ошибку? Сообщите →