Проверки работоспособности

OxPHP запускает внутренний HTTP-сервер на отдельном порту для мониторинга работоспособности, сбора метрик и просмотра конфигурации. Он изолирован от прикладного трафика, поэтому мониторинг никогда не конкурирует с пользовательскими запросами.

Настройка

Задайте INTERNAL_ADDR, чтобы включить внутренний сервер:

bash
INTERNAL_ADDR=127.0.0.1:9090

Если INTERNAL_ADDR не задан, внутренний сервер не запускается и эндпоинты проверки работоспособности недоступны.

Note

Если в INTERNAL_ADDR указан только порт (:9090), он привязывается к 127.0.0.1; чтобы открыть внутренний сервер за пределами хоста, укажите явный 0.0.0.0:9090. Когда он доступен извне, ограничьте доступ через INTERNAL_ALLOW_IPS (список разрешений по CIDR/IP — /metrics, /config и пути плагинов возвращают 403 узлам за его пределами, тогда как пробы работоспособности остаются доступными); loopback не подразумевается неявно, поэтому укажите 127.0.0.1/32, чтобы сохранить доступ с localhost. При запуске сервер предупреждает, если слушатель открыт наружу без заданного списка разрешений.

Пробы Kubernetes

OxPHP предоставляет отдельные эндпоинты для каждого типа проб Kubernetes. Каждый эндпоинт также доступен под коротким псевдонимом (/healthz, /readyz, /startupz).

Эндпоинт Псевдоним Проверки 200 503
/health/liveness /healthz Нет (жив, если отвечает) Всегда Никогда
/health/readiness /readyz Не завершается, потоки воркеров работают, пул не заклинило, нет сбойных плагинов Готов Не готов
/health/startup /startupz Потоки воркеров работают Готов Не готов

Liveness всегда возвращает 200 OK. Если процесс способен ответить на HTTP-запрос, он считается живым. Проверки исполнителя или плагинов не выполняются — это не даёт Kubernetes перезапускать поды из-за временных проблем с пулом воркеров.

Readiness возвращает 503 Service Unavailable, когда:

  • Сервер завершает работу (идёт корректное завершение работы)
  • Все потоки PHP-воркеров завершились (пулу больше некому передать запрос)
  • Пул заклинило: запросы ждут воркеров, которые простаивают и ничего не выполняют. Это обнаруживается только в режиме воркеров (см. Сигнал заклинившего пула)
  • Любой плагин сообщает о сбое

Во время корректного завершения работы readiness сразу возвращает 503, из-за чего Kubernetes удаляет под из эндпоинтов Service ещё до завершения слива соединений.

Startup возвращает 503 Service Unavailable, пока не запущен ни один поток воркера. Используйте эту пробу, чтобы предотвратить преждевременный перезапуск пода по liveness-пробе при медленной инициализации. Она сообщает о потоках, которые существуют с момента создания пула, но не о начальной загрузке (bootstrap) самого приложения, выполняющейся внутри них.

Сигнал заклинившего пула

Пул может перестать забирать работу из очереди, хотя все его потоки живы: запросы накапливаются, ни один воркер не занят, и ничего из очереди так и не выполняется. Каждый запрос всё равно получает ответ, как только исчерпает свой бюджет ожидания (529 или 499, если клиент перестал ждать раньше), поэтому снаружи это не выглядит как зависание. Запросы по-прежнему получают ответы, но ни один из них не выполняется, а очередь всё равно заполняется, потому что освобождает место в ней только взятие запроса в работу. Все остальные сигналы при этом выглядят исправными: потоки на месте, плагины в порядке, статические файлы отдаются как обычно. Пул, который остаётся в таком состоянии и минуту спустя, обычно сам не восстанавливается, поэтому readiness о нём сообщает.

Сервер отслеживает сочетание, которое обслуживающий пул не может удерживать долго: есть ожидающая работа (запросы в очереди или растущее число отказов в допуске), при этом хотя бы один воркер простаивает, а пул не завершил ничего с момента предыдущей проверки.

  • Для записи в журнал достаточно двух последовательных проверок с интервалом в одну секунду.
  • Readiness ждёт ещё минуту. Пул, все воркеры которого остановились одновременно (чаще всего это пул из одного воркера), выглядит именно так, пока замена заново выполняет начальную загрузку приложения. Новый воркер снова обслуживает запросы задолго до истечения минуты, а преждевременный вывод реплики из ротации лишил бы её того трафика, который нужен, чтобы это показать.
  • Readiness восстанавливается, когда воркер действительно завершает запрос (затишье восстановлением не считается), или после минуты, в течение которой пул ни разу не выглядел заклинившим. Второй выход не требует трафика: заклинивший пул удерживает свою очередь и потому не может выполнить это условие, а пул, ошибочно принятый за заклинивший, может. Без него это состояние было бы постоянным, потому что 503 останавливает те самые запросы, завершение которых служит другим выходом.
Note

Сигналу нужно число запросов, обработанных воркерами, а его ведёт только режим воркеров (WORKER_MODE_ENABLED=true), поэтому он существует только в режиме воркеров. В остальных случаях readiness отвечает по трём другим условиям, а oxphp_pool_stalled отсутствует, а не экспортируется как константа 0.

Что пробы не сообщают

Нагрузку. Перегруженный сервер остаётся в состоянии Ready. Когда очередь допуска заполнена, а бюджет ожидания исчерпан, запросы отклоняются с 529 Site is overloaded, и реплика, которая так делает, по-прежнему отвечает, причём быстро, на исправно работающем пуле. Вывод её из ротации переложил бы её трафик на соседей, и при равномерной перегрузке это по очереди убрало бы все реплики, пока у Service не осталось бы ни одного эндпоинта: деградировавший сайт превратился бы в недоступный, а нагрузка никуда бы не делась. Вместо этого следите за oxphp_admission_refused_total{reason=...} и добавляйте мощности. Однако читайте oxphp_admission_wait_wasted_total вместе с oxphp_admission_wait_budget_us: отказ wait_timeout сам по себе не означает, что пул справляется со своей работой, и если пул не освобождает ни одного воркера за всё время ожидания запроса, средство обратное: сократить бюджет ожидания, а не увеличивать пул. Этот счётчик считает, только пока бюджет находится на своём потолке. Сервер также сообщает об этом в журнале: WARN на проверке, с которой начинается сброс нагрузки, повторяется примерно раз в минуту, пока он длится, и INFO, когда проходит целая минута без отказов. См. Метрики Prometheus.

Исчерпанный бюджет соединений. Когда разрешения MAX_CONNECTIONS заканчиваются, цикл приёма соединений приостанавливается и новые клиенты вообще не получают ответа, но внутренний слушатель через этот бюджет не проходит, поэтому пробы продолжают отвечать. Это тоже нагрузка, а не сбой, и на readiness она не влияет; о ней сообщает oxphp_accept_stalled.

Медленные ответы. Здесь ничто не измеряет задержку. Пул, который на всё отвечает с опозданием, остаётся в состоянии Ready.

Все эндпоинты проб возвращают Content-Type: text/plain с именем пробы в теле ответа (например, readiness). Kubernetes проверяет только HTTP-код состояния.

bash
# Quick check curl -s -o /dev/null -w '%{http_code}' http://localhost:9090/health/readiness

GET /health

Возвращает полный статус работоспособности сервера в формате JSON. Используйте его для дашбордов и систем мониторинга, а не для проб Kubernetes.

bash
curl http://localhost:9090/health

Ответ при исправном состоянии (200 OK):

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

Ответ при деградации (503 Service Unavailable):

json
{ "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-воркера. false означает, что у пула не осталось потока, которому можно передать запрос
pool_stalled boolean Заклинило ли пул: запросы ждут воркеров, которые простаивают и ничего не выполняют. Вне режима воркеров, где это состояние не обнаруживается, всегда false (см. Сигнал заклинившего пула). true переключает HTTP-статус на 503
plugins object<string, string> Работоспособность по плагинам: ключи — имена плагинов, значения — "ok", "degraded" или "failed". Пустой {}, когда ни один плагин не сообщает о состоянии. Плагин со значением "failed" переключает HTTP-статус на 503; "degraded" отображается здесь, но оставляет статус равным 200.

GET /metrics

Возвращает совместимые с Prometheus метрики в текстовом формате экспозиции. Полный справочник метрик см. в разделе Метрики Prometheus.

bash
curl http://localhost:9090/metrics

GET /config

Возвращает активную конфигурацию сервера в формате JSON. Пути к TLS-сертификату и ключу, internal_addr и error_pages_dir удаляются из ответа в целях безопасности.

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": {"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 }
Note

Пути к TLS-сертификату и ключу никогда не выводятся (tls_enabled показывает, активен ли TLS), а internal_addr и error_pages_dir удаляются из отдаваемого ответа.

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

Используйте отдельные эндпоинты проб для каждого типа проб:

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

compose.yaml
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 после того, как заданное число повторных попыток завершится неудачей, что может запустить политики перезапуска или удаление из балансировщика нагрузки.

Промпты для вашего агента

Эти промпты рассчитаны на агента с подключённым MCP-сервером документации OxPHP — нужные страницы он прочитает сам.

prompt
Настрой проверки работоспособности OxPHP для моего проекта. Прочитай страницу `operations/health-checks` через MCP-сервер документации OxPHP. Добавь healthcheck в мой docker-compose.yml и пробы liveness и readiness в мой Deployment для Kubernetes, опираясь на сервисы и порты, которые этот репозиторий уже описывает.
prompt
Оркестратор перезапускает мой контейнер с OxPHP, и я не понимаю почему. Прочитай страницу `operations/health-checks` через MCP-сервер документации OxPHP. Сверь мою конфигурацию проверок работоспособности с тем, что на самом деле отдаёт внутренний сервер, и скажи мне, какая из двух сторон неправа.

См. также

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