Łagodne zamknięcie

OxPHP obsługuje SIGTERM i SIGINT tak, aby będące w trakcie realizacji żądania zdążyły się zakończyć, zanim proces się zamknie. Ma to znaczenie dla wdrożeń bez przestojów oraz aktualizacji kroczących (rolling updates) w orkiestracji kontenerów.

Obsługa sygnałów

OxPHP reaguje na dwa sygnały zamknięcia:

Sygnał Źródło Zachowanie
SIGTERM Orkiestratory kontenerów, docker stop, kill Rozpoczyna łagodne zamknięcie
SIGINT Ctrl+C w terminalu Rozpoczyna łagodne zamknięcie

Oba sygnały uruchamiają tę samą sekwencję zamknięcia. Wystarczy tylko pierwszy sygnał. Serwer natychmiast rozpoczyna wygaszanie.

Sekwencja zamknięcia

Po otrzymaniu sygnału zamknięcia OxPHP wykonuje następującą sekwencję:

  1. Zaprzestanie przyjmowania nowych połączeń — serwer przestaje przyjmować nowe połączenia TCP na głównym porcie. Workery PHP nadal działają, przetwarzając żądania będące w trakcie realizacji.
  2. Wygaszanie aktywnych połączeń — klienci HTTP/2 otrzymują ramkę GOAWAY, a bezczynne połączenia keep-alive HTTP/1.1 zostają zamknięte, dzięki czemu klienci przechodzą na sprawną instancję, zamiast multipleksować nowe żądania do zamierającej. Otwarte strumienie kończone są sprawnie i czysto — liczy się każda odpowiedź, która zaczęła wysyłać dane w trybie chunked, zarówno skończone pobrania, jak i SSE — zobacz Server-Sent Events.
  3. Wygaszanie żądań w trakcie realizacji — zwykłe aktywne żądania są pozostawione, aby dokończyć się z pełnymi odpowiedziami. Serwer sprawdza ich zakończenie co 100 ms. Wewnętrzny serwer stanu/metryk pozostaje dostępny przez cały czas wygaszania, dzięki czemu sondy gotowości (readiness) nadal działają.
  4. Egzekwowanie terminu wygaszania — żądania nadal działające po upływie DRAIN_TIMEOUT_SECONDS zostają anulowane (ich funkcje wywoływane zwrotnie przez register_shutdown_function() i tak są uruchamiane) i otrzymują ~2 sekundy na uporządkowanie, zanim serwer przejdzie dalej.
  5. Opróżnienie buforów wtyczek — wpisy rejestru dostępu oraz spany APM zbuforowane w oknie wygaszania zostają zapisane.
  6. Zamknięcie puli asynchronicznej — pula asynchronicznych zadań w tle zostaje zatrzymana.
  7. Zatrzymanie serwera wewnętrznego — serwer stanu/metryk zostaje zatrzymany po zakończeniu wygaszania.
  8. Wyjście — proces kończy działanie z kodem statusu 0.
Note

Workery PHP są zatrzymywane jawnie w kroku 1 — metoda shutdown() egzekutora jest wywoływana w ramach zatrzymywania głównego serwera, co sygnalizuje wątkom worker, aby zakończyły działanie po dokończeniu ewentualnego trwającego żądania.

Konfiguracja

Zmienna Wartość domyślna Opis
DRAIN_TIMEOUT_SECONDS 25 Maksymalna liczba sekund, jaką będące w trakcie realizacji żądania dostają na zakończenie, zanim zostaną anulowane; proces kończy działanie w ciągu ~2 sekund po upływie terminu. Wartość domyślna pozostawia zapas na porządkowanie po terminie i opróżnienie telemetrii w ramach domyślnego 30-sekundowego okresu karencji na zakończenie (termination grace period) w Kubernetes

Ustaw DRAIN_TIMEOUT_SECONDS tak, aby uwzględniał najwolniejsze spodziewane żądanie:

  • Serwery API z szybkimi odpowiedziami: 1015 sekund
  • Aplikacje z przesyłaniem plików lub długimi zapytaniami: 3060 sekund
  • Tryb worker z przetwarzaniem w tle: dopasuj do najdłuższej spodziewanej operacji

Kubernetes

W Kubernetes przebieg zamknięcia podczas aktualizacji kroczącej wygląda następująco:

  1. Kubernetes wysyła SIGTERM do poda.
  2. Pod zostaje usunięty z listy endpointów Service.
  3. OxPHP wygasza połączenia będące w trakcie realizacji w czasie DRAIN_TIMEOUT_SECONDS, następnie anuluje maruderów i kończy działanie w ciągu ~2 kolejnych sekund.
  4. Jeśli pod nadal działa po upływie terminationGracePeriodSeconds, Kubernetes wysyła SIGKILL.

Ustaw terminationGracePeriodSeconds powyżej wartości DRAIN_TIMEOUT_SECONDS + 2, aby wygaszanie — łącznie z porządkowaniem po terminie i opróżnieniem telemetrii — zdążyło się zakończyć przed wymuszonym zabiciem procesu:

yaml
apiVersion: apps/v1 kind: Deployment spec: template: spec: terminationGracePeriodSeconds: 45 containers: - name: oxphp image: ghcr.io/oxphp/oxphp:0.10.0 env: - name: DRAIN_TIMEOUT_SECONDS value: "30"

Hak pre-stop

Jeśli twoja usługa odbiera ruch z zewnętrznych load balancerów, które wolno propagują zmiany endpointów, dodaj hak pre-stop, aby opóźnić sekwencję zamknięcia:

yaml
lifecycle: preStop: exec: command: ["sleep", "5"]

Daje to load balancerowi czas na usunięcie poda ze swojej listy celów, zanim OxPHP przestanie przyjmować połączenia.

Docker

Docker wysyła SIGTERM, gdy uruchamiasz docker stop. Domyślny limit czasu zatrzymania w Docker wynosi 10 sekund, po którego upływie Docker wysyła SIGKILL.

Aby dać OxPHP wystarczająco dużo czasu na wygaszenie, zwiększ limit czasu zatrzymania:

bash
docker stop --time 45 my-oxphp-container

Albo ustaw go w pliku Compose:

compose.yaml
services: oxphp: image: ghcr.io/oxphp/oxphp:0.10.0 stop_grace_period: 45s environment: DRAIN_TIMEOUT_SECONDS: "30"

Komunikaty w logach

Podczas łagodnego zamknięcia OxPHP emituje ustrukturyzowane komunikaty w logach, które możesz monitorować:

Udane wygaszenie:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"INFO","message":"Draining in-flight connections","active_connections":3} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Osiągnięto termin wygaszania:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Jeśli regularnie widzisz ostrzeżenie „Drain timeout reached", zwiększ DRAIN_TIMEOUT_SECONDS lub zbadaj długo działające żądania za pomocą histogramu oxphp_request_duration_us.

Zobacz też

  • Kontrole stanu — sondy gotowości i interakcja z zamknięciem
  • Dokumentacja konfiguracji — wszystkie zmienne środowiskowe, w tym DRAIN_TIMEOUT_SECONDS
  • Metrykioxphp_active_connections śledzi połączenia podczas wygaszania