Operacje

Playbooki operacyjne i runbooki

Ta strona zbiera procedury operacyjne do obsługi platformy DataFlow AI Platform: runbook przełączania awaryjnego do odzyskiwania po awarii dla udokumentowanej topologii GKE oraz zestaw praktycznych runbooków dla produkcyjnego systemu na pojedynczym VPS, który faktycznie obsługuje dziś użytkowników. Każda procedura to ponumerowana sekwencja zrób-to-potem-tamto.


Zanim zaczniesz

Każdą procedurę tutaj kształtują dwa fakty:

  • Produkcja to pojedynczy VPS Debian etl.exai.cloud (57.129.120.73), uruchomiony na Docker Compose. Na tej ścieżce nie ma canary, nie ma automatycznego wycofywania zmian ani automatycznych testów.
  • Runbook DR/przełączania awaryjnego opisuje wieloregionowy model GKE, który nie jest tym, co działa na produkcji. Jest udokumentowany poniżej dla kompletności i dlatego, że stanowi docelową topologię — ale prawdziwy incydent na VPS obsługuje się runbookami Compose, a nie przełączaniem awaryjnym GKE.

Poświadczenia znajdują się w kodzie źródłowym — obchodź się ostrożnie

Kilka skryptów wdrożeniowych (deploy_to_vps.py, apply-nginx-fix.py, seed_keycloak_vps.py) oraz deploy/.env.production zawiera zakodowane na sztywno lub jawnotekstowe sekrety produkcyjne. To znane odkrycie dotyczące bezpieczeństwa. Postępując według tych runbooków, preferuj pozyskiwanie poświadczeń z menedżera sekretów i potraktuj rotację (zobacz poniżej) jako priorytet.


Runbook 1 — Odzyskiwanie po awarii / przełączanie awaryjne (GKE)

To udokumentowana wieloregionowa procedura DR z docs/runbooks/dr-failover-runbook.md. Dotyczy topologii GKE, a nie VPS.

Cele: RTO < 4 godziny, RPO < 1 minuta (opóźnienie asynchronicznej replikacji Cloud SQL).

Topologia:

   europe-central2 (PRIMARY)            europe-west3 (DR)
   ┌──────────────────────────┐         ┌──────────────────────────┐
   │ GKE dataflow-autopilot-   │         │ GKE dataflow-autopilot-dr │
   │     production            │         │  (1 replica, passive,     │
   │ Cloud SQL primary         │  async  │   read-only)              │
   │                           │ ──────▶ │ Cloud SQL read replica    │
   │                           │  repl.  │  dataflow-pg-dr-replica   │
   └────────────┬──────────────┘         └────────────┬─────────────┘
                │                                     │
                └────── DNS health-check failover ────┘
                     api.dataflow.polkomtel.pl

Procedura przełączania awaryjnego (7 kroków)

  1. Zweryfikuj, że region podstawowy jest niedostępny. Sprawdź status klastra GKE, status Cloud SQL, endpoint stanu oraz stronę statusu GCP. Przełączanie awaryjne wymaga zatwierdzenia przez lidera dyżuru on-call przed kontynuacją.
  2. Awansuj replikę Cloud SQL. Uruchom gcloud sql instances promote-replica — to nieodwracalne. Odpytuj instancję, aż jej stan będzie RUNNABLE.
  3. Przełącz wdrożenia DR na tryb odczytu-zapisu. Załataj ConfigMap, aby ustawić DB_READ_ONLY=false.
  4. Zwiększ skalę wdrożeń DR. Skaluj z 1 → 3 replik i poczekaj na zakończenie wdrożenia (rollout).
  5. Przełącz DNS awaryjnie. Zweryfikuj lub wymuś przełączenie awaryjne DNS na adres IP DR 10.2.0.100.
  6. Wykonaj test dymny DR. Potwierdź, że pody działają, że wszystkie 5 endpointów stanu usług odpowiada oraz że łączność z bazą danych działa.
  7. Powiadom interesariuszy. Wyślij powiadomienia przez Slacka, e-mail oraz PagerDuty.

Procedura powrotu (failback) (F1–F7)

  1. Zweryfikuj, że region podstawowy został odzyskany.
  2. Zbuduj świeżą replikę w europe-central2 z bieżącego (awansowanego) regionu podstawowego.
  3. Zsynchronizuj nową replikę.
  4. Awansuj ją z powrotem na region podstawowy.
  5. Zwiększ skalę wdrożeń podstawowych i zmniejsz skalę wdrożeń DR.
  6. Przełącz DNS z powrotem na adres IP podstawowy 10.0.0.100.
  7. Ponownie ustanów replikę DR i zaktualizuj stan Terraform.

Harmonogram testów

CzęstotliwośćCzynność
CiągleMonitorowanie opóźnienia replikacji
Co tydzieńWeryfikacja kontroli stanu (health-check)
Co miesiącWalidacja infrastruktury DR
Co kwartałĆwiczenie tabletop + pełny test przełączania awaryjnego

Runbook 2 — Zrestartuj usługę (VPS)

Użyj tego, gdy pojedyncza usługa jest w złej kondycji, ale host i warstwa infrastruktury są w porządku.

  1. Potwierdź objaw. docker compose ps — zidentyfikuj, która usługa nie jest healthy.
  2. Najpierw przechwyć dowody. docker compose logs --tail=300 <service> i zapisz wynik przed restartem, aby przyczyna awarii nie została utracona.
  3. Zrestartuj pojedynczą usługę. docker compose restart <service>.
  4. Obserwuj jej powrót. docker compose logs -f <service>, aż uruchamianie się zakończy (Flyway uruchamia się przy starcie usług Kotlina — wypatruj sukcesu migracji).
  5. Zweryfikuj stan. curl -sf http://127.0.0.1:<host-port>/actuator/health | jq (na przykład 8085 dla gateway'a, 8084 dla monitor-service).
  6. Sprawdź usługi zależne. Jeśli zrestartowałeś postgres, keycloak lub kafka, zrestartuj usługi aplikacyjne, które od nich zależą, respektując kolejność startu: postgres → keycloak/kafka → usługi aplikacyjne → frontend.
  7. Potwierdź od początku do końca. Załaduj https://etl.exai.cloud i przetestuj dotkniętą stronę.

Respektuj kolejność startu

Plik Compose wymusza kolejność startu przez healthchecki i depends_on. Jeśli restartujesz infrastrukturę (postgres, kafka, keycloak), zwykły restart samego tego kontenera może pozostawić usługi aplikacyjne połączone z nieaktualnym endpointem. W razie wątpliwości zrestartuj potem zależne usługi aplikacyjne.


Runbook 3 — Zbadaj nieudane wdrożenie (VPS)

Użyj tego po tym, jak python deploy/deploy_to_vps.py zgłosi awarię lub zakończy się niejednoznacznie.

  1. Przeczytaj log wdrożenia. Sprawdź deploy/.deploy.log, gdzie uruchomienie się zatrzymało. Uwaga: windowsowy UnicodeEncodeError (cp1252) może spowodować awarię lokalnego skryptu podczas strumieniowania wyniku budowania — to kwestia kosmetyczna i niekoniecznie oznacza, że wdrożenie po stronie serwera się nie powiodło.
  2. Połącz się przez SSH z VPS i sprawdź stan kontenerów. docker compose ps — czy wszystkie 8 usług oraz warstwa infrastruktury są obecne i healthy?
  3. Zidentyfikuj nieudany krok. Skrypt ma 7 kroków (archiwizacja → połączenie → wysyłka → konfiguracja nginx → budowanie → start w falach → weryfikacja). Awarie budowania (krok 5) najczęściej wynikają z presji pamięci VPS podczas budowania usług Kotlina.
  4. Sprawdź wynik budowania. Jeśli obraz nie zbudował się, ponownie uruchom to pojedyncze budowanie na serwerze: docker compose build <service> (każde budowanie Kotlina może trwać kilka minut; skrypt dopuszcza 900s).
  5. Sprawdź migracje Flyway. docker compose logs <kotlin-service> | grep -i flyway — migracje uruchamiają się przy starcie, a nieudana migracja blokuje usługę. Zobacz Runbook 6, jeśli migracje utknęły.
  6. Zwaliduj nginx. nginx -t na hoście; jeśli krok 4 pozostawił zepsutą konfigurację, przywróć kopię zapasową ze znacznikiem czasu.
  7. Uruchom ponownie lub zakończ ręcznie. Albo ponownie uruchom deploy_to_vps.py (wykonuje pełne przebudowanie), albo, jeśli zawiodła tylko fala, uruchom pozostałe usługi: docker compose up -d.
  8. W razie potrzeby ponownie zasiej (re-seed). Jeśli Keycloak został odtworzony, ponownie uruchom python deploy/seed_keycloak_vps.py.
  9. Zweryfikuj. Wykonaj curl na frontend na 127.0.0.1:3006 i potwierdź, że https://etl.exai.cloud się ładuje.

Runbook 4 — Odzyskaj zablokowany pipeline

Użyj tego, gdy uruchomienie pipeline'u zawiesiło się, nie postępuje lub utknęło w stanie nieterminalnym.

  1. Znajdź uruchomienie. Zidentyfikuj runId i bieżący status — przez interfejs Monitor lub bezpośrednio:
    docker compose exec postgres psql -U postgres -d dataflow_metadata \
      -c "SELECT id, status, started_at FROM monitor_pipeline_runs ORDER BY started_at DESC LIMIT 20;"
    
  2. Strumieniuj log uruchomienia. Pipeline-engine strumieniuje logi przez WebSocket na /api/v1/runs/{runId}/stream; przeglądarka logów w interfejsie to renderuje. Potwierdź, czy uruchomienie jest faktycznie zawieszone, czy tylko wolne.
  3. Sprawdź silnik. docker compose logs --tail=300 pipeline-engine — szukaj błędów na poziomie zadań, niepowodzeń wykrywania cyklu/wiszących węzłów DAG lub nasycenia puli wątków.
  4. Sprawdź samonaprawianie. Przy awarii zadania SelfHealingService silnika klasyfikuje awarię i stosuje strategie odzyskiwania. Potwierdź, czy odzyskiwanie już się uruchomiło i do czego doprowadziło.
  5. Sprawdź obliczenia w dół łańcucha. Jeśli pipeline używa Flink lub Spark/Dataproc, zweryfikuj, czy zewnętrzne zadanie faktycznie się uruchomiło — zablokowane uruchomienie może być zablokowanym zadaniem zewnętrznym, a nie problemem silnika.
  6. Anuluj kooperatywnie. ExecutionContext silnika wspiera kooperatywne anulowanie — anuluj uruchomienie przez API/interfejs, a nie przez zabicie kontenera.
  7. Jeśli sam silnik jest zaklinowany, postępuj według Runbooka 2, aby zrestartować pipeline-engine. Uruchomienia w trakcie zostaną utracone; sprawdź potem monitor_pipeline_runs, aby potwierdzić, że uruchomienie jest w stanie terminalnym.
  8. Uruchom pipeline ponownie, gdy przyczyna jest zrozumiana i usunięta.

Runbook 5 — Rotacja poświadczeń

Użyj tego, aby rotować sekrety produkcyjne, które obecnie znajdują się w deploy/.env.production oraz w skryptach wdrożeniowych.

  1. Zinwentaryzuj sekrety. Sekrety produkcyjne VPS to: hasło Postgres, hasło administratora Keycloak, hasło administratora Grafany, klucze dostępu/sekretne MinIO oraz hasła użytkowników demo. Hasło SSH do VPS jest zakodowane na sztywno w deploy_to_vps.py oraz apply-nginx-fix.py.
  2. Wygeneruj nowe wartości. Użyj silnych, losowo generowanych sekretów.
  3. Zaktualizuj deploy/.env.production. Zastąp odpowiednie wartości. Ten plik jest źródłem prawdy dla środowiska Compose.
  4. Rotuj poświadczenia magazynu danych w miejscu. W przypadku Postgres zmień hasło roli na nową wartość przed zrestartowaniem zależnych usług, aby pomyślnie się ponownie połączyły.
  5. Rotuj hasło SSH do VPS na hoście i zaktualizuj je w skryptach wdrożeniowych (lub, najlepiej, przenieś je poza kod źródłowy do menedżera sekretów).
  6. Wdróż ponownie. Uruchom python deploy/deploy_to_vps.py, aby nowy .env.production został wysłany, a usługi zrestartowały się z nowymi wartościami.
  7. Ponownie zasiej Keycloak. Uruchom python deploy/seed_keycloak_vps.py — jest idempotentny i resetuje hasła użytkowników demo oraz konfigurację klienta dataflow-app.
  8. Zweryfikuj. Potwierdź, że logowanie działa na https://etl.exai.cloud, że logowanie do Grafany działa oraz że docker compose ps pokazuje wszystko jako healthy.

Rotuj wszystko, jeśli skrypt wyciekł

Ponieważ hasło SSH i hasła magazynów danych są zakomitowane w kodzie źródłowym, traktuj każde ujawnienie repozytorium jako kompromitację poświadczeń. Rotuj pełny zestaw — SSH do VPS, Postgres, administrator Keycloak, Grafana, MinIO — a nie tylko to, które podejrzewasz.


Runbook 6 — Napraw migrację bazy danych

Użyj tego, gdy usługa Kotlina nie uruchamia się, ponieważ Flyway nie może zastosować lub zwalidować swoich migracji. Jest to najbardziej prawdopodobne na VPS, który rozpoczął życie z tabelami sprzed Flyway.

  1. Zidentyfikuj zawodzącą usługę. docker compose logs <service> | grep -i flyway — błąd nazywa zawodzącą wersję oraz tabelę historii (flyway_schema_history, flyway_schema_history_engine lub flyway_schema_history_monitor).
  2. Potwierdź, że flagi utwardzania są ustawione. Środowisko Compose powinno już ustawiać, dla dotkniętej usługi:
    SPRING_FLYWAY_VALIDATE_ON_MIGRATE=false
    SPRING_FLYWAY_BASELINE_ON_MIGRATE=true
    SPRING_FLYWAY_BASELINE_VERSION=20.1
    SPRING_FLYWAY_OUT_OF_ORDER=true
    SPRING_FLYWAY_REPAIR_ON_MIGRATE=true
    SPRING_FLYWAY_PLACEHOLDER_REPLACEMENT=false
    
    REPAIR_ON_MIGRATE=true pozwala Flyway samodzielnie skorygować rozbieżną historię przy starcie.
  3. Uczyń migracje idempotentnymi. Jeśli migracja zawodzi, ponieważ obiekt już istnieje, uruchom przepisywacz:
    python scripts/make-migrations-idempotent.py <migration-files>
    
    Konwertuje CREATE TABLECREATE TABLE IF NOT EXISTS (tak samo dla INDEX/SEQUENCE), ADD COLUMN IF NOT EXISTS oraz poprzedza CREATE TRIGGER przez DROP TRIGGER IF EXISTS.
  4. W razie potrzeby sprawdź tabelę historii:
    docker compose exec postgres psql -U postgres -d dataflow_metadata \
      -c "SELECT version, description, success FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 15;"
    
    Wiersz z success = false to nieudana migracja, którą trzeba rozwiązać lub naprawić.
  5. Zrestartuj usługę. docker compose restart <service> i obserwuj logi — z REPAIR_ON_MIGRATE Flyway powinien zbiec się (skonwergować) przy starcie.
  6. Pamiętaj o zależności kolejności lineage. lineage-service ma Flyway wyłączony i ponownie wykorzystuje tabele lineage metadata-service (tworzone przez metadata V4/V48). Jeśli lineage zawodzi, przyczyną źródłową jest zwykle to, że metadata-service nie zastosował jeszcze tych migracji — najpierw napraw metadata.
  7. Zweryfikuj. Usługa osiąga stan healthy, a /actuator/health zwraca UP.

Runbook 7 — Reaguj na incydent

Ogólna procedura dla nieplanowanego problemu produkcyjnego na VPS.

  1. Potwierdź i określ zakres. Odtwórz objaw. Określ zasięg rażenia: jedna strona, jedna usługa czy cała platforma? Uruchom docker compose ps.
  2. Zadeklaruj i komunikuj. W przypadku czegokolwiek widocznego dla użytkownika, zadeklaruj incydent i powiadom interesariuszy. (Na ścieżce GKE jest to PagerDuty #dataflow-incidents; na VPS użyj uzgodnionego kanału zespołu.)
  3. Triażuj z obserwowalnością. Użyj listy kontrolnej obserwowalności: gotowość gateway'a, panele latencji/5xx w Grafanie, skoki awarii w monitor_pipeline_runs, monitor_alerts. Przechwyć X-Request-Id nieudanego żądania, aby skorelować logi między usługami.
  4. Ustabilizuj przed naprawą. Preferuj najszybsze bezpieczne działanie przywracające usługę — zwykle restart dotkniętej usługi (Runbook 2) — zamiast dogłębnej naprawy podczas incydentu.
  5. Zastosuj właściwy runbook. Nieudane wdrożenie → Runbook 3. Zablokowany pipeline → Runbook 4. Awaria migracji → Runbook 6. Zawieszenie nginx przy pobieraniu JWT → zastosuj python deploy/apply-nginx-fix.py (tworzy kopię zapasową, waliduje przez nginx -t i przeładowuje tylko przy sukcesie).
  6. Zweryfikuj odzyskanie. Załaduj https://etl.exai.cloud, przetestuj dotknięty przepływ pracy i potwierdź, że endpointy stanu oraz strumienie alertów są czyste.
  7. Zakomunikuj rozwiązanie. Powiadom interesariuszy, że incydent został rozwiązany.
  8. Napisz przegląd poincydentalny. Zapisz oś czasu, przyczynę źródłową oraz działania następcze — zwłaszcza to, co powinno stać się nowym lub zaktualizowanym runbookiem.

Incydent z nagłówkiem upgrade nginx

Powracający, dobrze zrozumiany tryb awarii: jeśli konfiguracja /api/ nginx koduje na sztywno Connection "upgrade", pobrania w przeglądarce uwierzytelnione JWT nigdy nie otrzymują ramki HTTP/2 END_STREAM, a strony (marketplace, templates, my-pipelines, data-browser) kręcą się w nieskończoność. Naprawą jest warunkowa mapa map $http_upgrade $etl_connection_upgrade { default upgrade; '' close; } stosowana przez apply-nginx-fix.py. Jeśli widzisz zawieszone spinnery po zmianie nginx, podejrzewaj to w pierwszej kolejności.


Szybki przegląd runbooków

SytuacjaRunbookKluczowe polecenie
Awaria regionu GKE1 — Przełączanie awaryjne DRgcloud sql instances promote-replica
Jedna usługa w złej kondycji2 — Restart usługidocker compose restart <service>
Wdrożenie nieudane/niejednoznaczne3 — Zbadaj wdrożeniesprawdź deploy/.deploy.log, docker compose ps
Pipeline zawieszony4 — Odzyskaj pipelinekooperatywne anulowanie, potem ponowne uruchomienie
Rotacja sekretów5 — Rotacja poświadczeńedytuj .env.production, wdróż ponownie, ponownie zasiej
Usługa nie uruchamia się przez Flyway6 — Napraw migracjęmake-migrations-idempotent.py
Nieplanowany problem produkcyjny7 — Reagowanie na incydentylista kontrolna obserwowalności + ukierunkowany runbook
Zawieszenie nginx przy pobieraniu JWT7 (callout)python deploy/apply-nginx-fix.py
Poprzednia
Obserwowalność