Перевірки стану

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 не додається неявно, тож щоб зберегти доступ із localhost, додайте до списку 127.0.0.1/32. Під час запуску сервер попереджає, якщо слухач відкритий назовні без заданого списку дозволених адрес.

Проби 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 не вбивала под передчасно під час повільної ініціалізації. Вона звітує про потоки, які існують, щойно створено пул. Про власний бутстрап застосунку, що виконується всередині них, вона не звітує.

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

Пул може перестати забирати роботу зі своєї черги, хоча всі його потоки живі: запити накопичуються, жоден воркер не зайнятий, і нічого з черги так і не виконується. Кожен запит усе одно отримує відповідь, щойно вичерпає власний бюджет очікування (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. Звір мою конфігурацію перевірок стану з тим, що насправді надає внутрішній сервер, і скажи, що з двох неправильне.

Див. також