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

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 или 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. Используйте его для проб readiness и liveness в Kubernetes.

200 OK — все системы исправны:

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

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

json
{ "status": "degraded", "uptime_secs": 3612, "total_requests": 48203, "active_connections": 7, "executor_healthy": true, "plugins": { "otel": "failed" } }

Объект plugins перечисляет все загруженные плагины с их статусом работоспособности: "ok", "degraded" или "failed". Эндпоинт возвращает 503, когда любой плагин сообщает "failed" или исполнитель скриптов неисправен (executor_healthy: false). Плагины со статусом "degraded" присутствуют в теле JSON, но HTTP-статус остаётся 200.

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
{ "listen_addr": "0.0.0.0:80", "document_root": "/var/www/html/public", "entry_file": "/var/www/html/public/index.php", "log_level": "info", "executor_type": "sapi", "php_workers": "8", "tokio_workers": 4, "queue_capacity": 1024, "max_connections": 10000, "drain_timeout_seconds": 30, "header_timeout_seconds": 5, "rate_limit": 100, "rate_window_seconds": 60, "tls_enabled": true, "tls_min_version": "1.2", "compression_level": 4, "access_log": "all", "max_query_body": 524288, "worker_mode_enabled": false, "worker_max_memory_mib": 0, "static_max_age": 2592000, "static_revalidate": false, "async_workers": 0, "async_queue_capacity": 0, "async_max_fibers": 256, "async_in_flight_cap": 0, "trace_context": false, "superglobals_enabled": true, "trusted_proxies": false, "plugins": {} }

Пути к файлам TLS-сертификата и ключа никогда не выводятся (доступен только булев флаг tls_enabled), а internal_addr и error_pages_dir вычищаются из отдаваемого ответа — это топология развёртывания и пути в файловой системе, которые помогают злоумышленнику и не нужны сборщикам метрик.

Эндпоинты плагинов

Плагины могут регистрировать собственные эндпоинты с префиксом /__<plugin_name>/. Например, плагин с именем otel может предоставлять /__otel/status. Эти эндпоинты доступны только в том случае, если соответствующий плагин загружен и зарегистрировал обработчик.

Любой путь, не соответствующий встроенному эндпоинту или эндпоинту плагина, возвращает 404 Not Found.

Интеграция с Kubernetes

Проба readiness

Используйте /health, чтобы управлять тем, направляет ли Kubernetes трафик на под:

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

Когда /health возвращает 503, Kubernetes удаляет под из списка эндпоинтов Service. Трафик возобновляется, как только эндпоинт снова возвращает 200.

Проба liveness

Используйте тот же эндпоинт, чтобы перезапускать поды, которые перестают отвечать:

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

Проба startup

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

yaml
startupProbe: httpGet: path: /health port: 9090 initialDelaySeconds: 1 periodSeconds: 2 failureThreshold: 15

Безопасность

Внутренний сервер не имеет аутентификации по bearer-токену; доступ определяется тем, к чему он привязан, и переменной INTERNAL_ALLOW_IPS. Чтобы защитить его:

  • Привяжите к localhostINTERNAL_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.

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.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. Проверьте объект plugins в ответе health, чтобы определить, какой плагин даёт сбой:

bash
curl -s http://localhost:9090/health | jq '.plugins'
Не удаётся достучаться до внутреннего сервера снаружи контейнера

Сервер привязан к 127.0.0.1, который доступен только изнутри контейнера.

Исправление: Измените на 0.0.0.0 для внешнего доступа или используйте пробы Kubernetes, которые обращаются к контейнеру напрямую.

Метрики не показывают режим воркеров или асинхронные метрики

Метрики режима воркеров появляются только при включённом режиме воркеров (WORKER_MODE_ENABLED=true). Асинхронные метрики появляются только при ASYNC_WORKERS > 0 и когда хотя бы одна задача была отправлена или отклонена.

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