Serwer wewnętrzny
OxPHP uruchamia osobny serwer HTTP na dedykowanym porcie do kontroli stanu, metryk Prometheus oraz podglądu aktywnej konfiguracji. Pozostaje całkowicie odizolowany od głównego listenera aplikacji: bez TLS, bez ograniczania liczby żądań, bez ID żądań i bez przetwarzania zdarzeń.
Jak to działa
Ustaw INTERNAL_ADDR na adres nasłuchu (np. 127.0.0.1:9090), a OxPHP uruchomi drugi listener HTTP pod tym adresem. Serwer wewnętrzny udostępnia wbudowane endpointy wymienione poniżej:
/health— zbiorczy status w formacie JSON/health/liveness,/healthz(alias) — sonda liveness/health/readiness,/readyz(alias) — sonda readiness/health/startup,/startupz(alias) — sonda startup/metrics— ekspozycja Prometheus/config— aktywna konfiguracja serwera w formacie JSON
Wtyczki mogą rejestrować dodatkowe endpointy pod prefiksem /__<plugin>/. Podczas łagodnego zamknięcia serwer wewnętrzny pozostaje dostępny, dopóki główny serwer nie zakończy opróżniania połączeń.
Serwer wewnętrzny uruchamia się tylko wtedy, gdy INTERNAL_ADDR jest jawnie ustawiony. Bez niego endpointy health, metrics i config nie są dostępne przez HTTP.
Konfiguracja
| Zmienna | Wartość domyślna | Opis |
|---|---|---|
INTERNAL_ADDR |
(nieustawione) | Adres serwera wewnętrznego. Nie uruchamia się, gdy nieustawiony. Wartość zawierająca tylko port (:9090) wiąże się z 127.0.0.1; użyj jawnego 0.0.0.0:9090, aby udostępnić go poza hostem. Przykład: 127.0.0.1:9090 |
INTERNAL_ALLOW_IPS |
(nieustawione) | Lista dozwolonych adresów CIDR/IP rozdzielona przecinkami. Hosty spoza listy otrzymują 403 na ścieżkach /metrics, /config oraz /__<plugin>/; sondy stanu pozostają zawsze osiągalne. Nieustawione/puste = zezwól na wszystkie. Loopback nie jest domyślnie uwzględniony — wpisz 127.0.0.1/32, aby zachować dostęp z localhost. Nieprawidłowa lista przerywa uruchamianie |
Endpointy
GET /health
Zwraca status stanu w formacie JSON na potrzeby dashboardów i kontroli stanu kontenerów. W Kubernetes używaj zamiast niego dedykowanych endpointów sond (zobacz Integracja z Kubernetes): /health raportuje wszystkie podsystemy naraz, więc podpięty pod sondę liveness restartowałby poda przy awarii któregokolwiek z nich.
200 OK — wszystkie systemy sprawne:
{
"active_connections": 7,
"executor_healthy": true,
"plugins": {
"otel": "ok"
},
"pool_stalled": false,
"status": "ok",
"total_requests": 48203,
"uptime_secs": 3612
}503 Service Unavailable — wtyczka zgłosiła awarię:
{
"active_connections": 7,
"executor_healthy": true,
"plugins": {
"otel": "failed"
},
"pool_stalled": false,
"status": "degraded",
"total_requests": 48203,
"uptime_secs": 3612
}Obiekt plugins wymienia każdą załadowaną wtyczkę wraz z jej statusem stanu: "ok", "degraded" lub "failed". Endpoint zwraca 503, gdy dowolna wtyczka zgłasza "failed", gdy nie działa już żaden wątek workera PHP (executor_healthy: false) lub gdy pula się zaklinowała (pool_stalled: true, tylko w trybie worker; zobacz Kontrole stanu). Wtyczki "degraded" pojawiają się w ciele JSON, ale status HTTP pozostaje 200. Obciążenie również nie jest awarią: serwer odrzucający żądania kodem 529 przy przeciążeniu nadal zgłasza "ok".
GET /metrics
Zwraca metryki zgodne z Prometheus w formacie tekstowym (text/plain; version=0.0.4; charset=utf-8). Zawsze zwraca 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
...Pełną listę dostępnych metryk znajdziesz w Metryki.
GET /config
Zwraca aktywną konfigurację serwera w formacie JSON. Zawsze zwraca 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 wymienia kategorie hooków runtime, które proces zainstalował przy uruchomieniu. To sparsowany zbiór, a nie surowa wartość RUNTIME_HOOKS, więc all, 1 i sleep, streams, net odczytują się jednakowo jako ["sleep", "streams", "net"], a scraper nie musi ponownie implementować tej składni. Pusta lista oznacza, że nie zainstalowano żadnego hooka — to samo daje błędnie zapisana nazwa zmiennej. Pole raportuje to, co zainstalowano, a nie to, co faktycznie działa: tam, gdzie w ogóle nie ma fiberów, hooki delegują do zastąpionych funkcji wbudowanych, więc serwer w trybie tradycyjnym wymienia skonfigurowane kategorie, zachowując się przy tym natywnie. Zobacz Hooki runtime.
deny_file podsumowuje plik .oxphpdeny odczytany z DOCUMENT_ROOT przy uruchomieniu: liczbę reguł z podziałem według akcji (deny, entry dla reguł >, allow dla reguł !) oraz to, czym odpowiada reguła blokująca — kodem statusu PHP_DENY_FALLBACK albo "script" (bez ścieżki skryptu). Same reguły są pomijane: na serwerze wewnętrznym osiągalnym z zewnątrz stanowiłyby mapę dokładnie tych ścieżek, które plik chroni. Aby zobaczyć, jak odczytano każdą regułę, uruchom serwer z LOG_LEVEL=debug i poszukaj linii oxphpdeny rule. Bez pliku wpis ma postać {"loaded": false}.
Ścieżki do certyfikatu i klucza TLS nigdy nie są ujawniane (udostępniana jest tylko wartość logiczna tls_enabled), a internal_addr oraz error_pages_dir są usuwane z serwowanej odpowiedzi — to topologia wdrożenia i ścieżki w systemie plików, które pomagają atakującemu, a nie są potrzebne scraperom.
Endpointy wtyczek
Wtyczki mogą rejestrować własne endpointy pod prefiksem /__<plugin_name>/. Na przykład wtyczka o nazwie otel mogłaby udostępniać /__otel/status. Te endpointy są dostępne tylko wtedy, gdy odpowiednia wtyczka jest załadowana i zarejestrowała handler.
Każda ścieżka niepasująca do wbudowanego endpointu ani do endpointu wtyczki zwraca 404 Not Found.
Integracja z Kubernetes
Podepnij każdą sondę pod przeznaczony dla niej endpoint, zamiast kierować wszystkie trzy na /health. Ten endpoint sprowadza wszystkie podsystemy do jednego statusu, więc jako sonda liveness odpowiadałby na awarię wtyczki lub zaklinowanie puli restartem poda.
Sonda readiness
/health/readiness kontroluje, czy Kubernetes kieruje ruch do poda:
readinessProbe:
httpGet:
path: /health/readiness
port: 9090
initialDelaySeconds: 2
periodSeconds: 5Gdy zwraca 503, Kubernetes usuwa poda z listy endpointów Service; ruch wznawia się, gdy ponownie zwróci 200. Raportuje awarie, a nie obciążenie, więc przeciążony pod pozostaje w rotacji. Zobacz Kontrole stanu.
Sonda liveness
/health/liveness odpowiada 200, dopóki proces jest w stanie obsłużyć jakiekolwiek żądanie HTTP. Właśnie to powinna oznaczać sonda liveness, bo restart nie naprawi przeciążonej puli, a zabiera ze sobą pracę w toku:
livenessProbe:
httpGet:
path: /health/liveness
port: 9090
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3Sonda startup
Dla aplikacji z wolnym rozruchem (duże frameworki, ciężkie autoloadery):
startupProbe:
httpGet:
path: /health/startup
port: 9090
initialDelaySeconds: 1
periodSeconds: 2
failureThreshold: 15Bezpieczeństwo
Serwer wewnętrzny nie ma uwierzytelniania za pomocą tokenu bearer; dostęp jest kontrolowany przez to, gdzie się wiąże, oraz przez INTERNAL_ALLOW_IPS. Aby go zabezpieczyć:
- Powiąż z localhost —
INTERNAL_ADDRzawierający tylko port (:9090) już wiąże się z127.0.0.1; jawne127.0.0.1:9090sprawia, że port jest osiągalny tylko z wnętrza kontenera lub hosta - Ustaw
INTERNAL_ALLOW_IPS, gdy listener musi być osiągalny poza hostem — lista dozwolonych adresów CIDR/IP, która zwraca403na ścieżkach/metrics,/configoraz ścieżkach wtyczek hostom spoza niej, podczas gdy sondy stanu pozostają osiągalne. Loopback nie jest domyślnie uwzględniony, więc dodaj127.0.0.1/32, jeśli nadal potrzebujesz dostępu z localhost. Serwer ostrzega przy uruchamianiu, jeśli listener jest wystawiony poza hostem bez ustawionej listy dozwolonych - Nie udostępniaj jako Service w Kubernetes — zadeklaruj port jako
containerPort, ale nie twórz dla niego Service. Sondy Kubernetes mają bezpośredni dostęp do portów kontenera - Używaj polityk sieciowych — ogranicz dostęp na warstwie sieciowej, jeśli port musi być wystawiony
Endpoint /config ujawnia szczegóły operacyjne (document root, limity liczby żądań, liczby workerów, wartości limitów czasu). Ścieżki TLS, internal_addr oraz error_pages_dir są usuwane, ale zastanów się, czy reszta powinna być dostępna spoza poda.
Przykład Docker
Aby szybko rzucić okiem, możesz opublikować port wewnętrzny; w środowisku produkcyjnym powiąż serwer wewnętrzny z localhost, a w Kubernetes, którego sondy httpGet nie docierają do listenera na loopbacku, powiąż go z 0.0.0.0:9090 i ustaw 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:9090Rozwiązywanie problemów
Endpointy health, metrics i config są niedostępne
INTERNAL_ADDR nie jest ustawiony.
Rozwiązanie: Dodaj zmienną środowiskową:
INTERNAL_ADDR=0.0.0.0:9090/health zwraca 503, ale aplikacja działa
Załadowana wtyczka zgłasza PluginHealth::Failed lub, rzadziej, executor_healthy ma wartość false albo pool_stalled ma wartość true. Sprawdź obiekt plugins w odpowiedzi health, aby zidentyfikować, która wtyczka zawodzi:
curl -s http://localhost:9090/health | jq '.plugins'Nie można połączyć się z serwerem wewnętrznym spoza kontenera
Serwer jest powiązany z 127.0.0.1, który jest dostępny tylko z wnętrza kontenera.
Rozwiązanie: Zmień na 0.0.0.0 dla dostępu z zewnątrz i ustaw INTERNAL_ALLOW_IPS. Sondy httpGet w Kubernetes wymagają tej samej zmiany, ponieważ nie docierają do listenera na loopbacku.
Metryki nie pokazują metryk trybu worker ani async
Metryki trybu worker pojawiają się tylko wtedy, gdy tryb worker jest włączony (WORKER_MODE_ENABLED=true). Metryki async pojawiają się tylko wtedy, gdy ASYNC_WORKERS > 0 i co najmniej jedno zadanie zostało wysłane lub odrzucone.
Zobacz też
- Metryki — pełne odniesienie do metryk Prometheus
- Kontrole stanu — szczegółowe zachowanie kontroli stanu
- Odniesienie do konfiguracji — wszystkie zmienne środowiskowe
- Łagodne zamknięcie — sekwencja zamykania i cykl życia serwera wewnętrznego