Playbook: Zarządzanie Incydentami — p24-infra
Scope: Pinbox24 W3/W4, MongoDB rs0, n8n, WAHA, monitoring, AI workers
Ostatnia aktualizacja: 2026-07-01
1. Poziomy severności
| Poziom | Definicja | Przykłady | SLA odpowiedzi |
|---|---|---|---|
| P0 | Produkcja całkowicie niedostępna lub ryzyko utraty danych | MongoDB rs0 down, bms-1 unreachable, nginx-proxy crashed, wyciek credentials w logach | Natychmiast — przerywa wszystko |
| P1 | Kluczowa funkcja niesprawna, wszyscy lub większość użytkowników dotknięta | Zepsuty upload plików, Excel import 500, auth 504, GPS sync zatrzymany, s3 microservice dead | ≤15 min |
| P2 | Pojedynczy użytkownik/biuro lub degradacja wydajności, alert Grafana | Jeden endpoint zwraca 422, Redis reconnect loop, alert EndpointDown bez potwierdzenia globalnej awarii | ≤2h |
| P3 | Brak wpływu na użytkowników, kosmetyka | Stary ghost entry w nginx-proxy, nieaktualna dokumentacja, alert false-positive | Najbliższa sesja robocza |
Trigger examples
P0: MongoDB rs0 PRIMARY down → wszystkie zapisy odrzucone
P0: v42-prod nie może połączyć z RabbitMQ przy starcie → wszystkie endpointy zawieszają się (ETIMEDOUT)
P0: PM2 loguje pełne MongoDB URI → potencjalny wyciek credentials (issue #2397)
P1: s3-v2-v42-prod zwraca OP_QUERY error → upload/Excel broken (Mongoose 4.x vs MongoDB 6.x)
P1: v42-prod oczekuje body.result[0] ale microservice zwraca bare array → 500 na wszystkich uploadach
P1: nginx-proxy nie widzi kontenera (błędna sieć Docker) → 502 na wszystkich API calls
P2: Redis OVH DBaaS zamknął idle connection → sporadyczne timeouty
P2: alert EndpointDown w Grafanie dla jednego URL-a
P2: iptables DROP przed UFW blokuje MongoDB RS heartbeat → SECONDARY lag
2. Jak zgłosić incydent
Kto zgłasza
- Claude agent (jeśli wykryje anomalię w odpowiedzi HTTP lub logach)
- Grafana Alertmanager (webhook → Discord)
- Użytkownik (przez Discord lub bezpośrednio)
- GitHub Action (failed deployment)
Gdzie zgłosić — OBOWIĄZKOWE przed workaroundem
ZAWSZE utwórz GH issue PRZED zastosowaniem workaroundu. Nie pomijaj zgłoszenia w milczeniu.
gh issue create \
--repo radieu/p24-infra \
--label "bug" \
--title "[Incident] <komponent> — <symptom>" \
--body "$(cat <<'EOF'
## Symptom
<dokładny komunikat błędu, HTTP status>
## Blast radius
<ilu użytkowników dotyczy, jakie funkcje>
## Severność
P0 / P1 / P2
## Pierwsze kroki dochodzenia
<co już sprawdzono>
EOF
)"Format tytułu GH issue
[Incident] v42-prod — RabbitMQ ETIMEDOUT, wszystkie endpointy zawieszają się
[Incident] nginx-proxy — 502 na api.w4.pinbox24.com (błędna sieć Docker)
[Incident] MongoDB rs0 — PRIMARY bms-2 niedostępny
[Incident] s3-v2-v42-prod — OP_QUERY error na Mongoose 4.x / MongoDB 6.x
Discord notification
MSG="[P1] v42-prod — upload broken, s3 microservice returns 500\nPrzyczynia: Mongoose 4.x OP_QUERY\nGH: https://github.com/radieu/p24-infra/issues/NNN"
curl -s -X POST "$P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"embeds\":[{\"title\":\"🔴 INCIDENT P1 — p24-infra\",\"color\":15158332,\"description\":\"$MSG\"}]}"3. Dochodzenie
Pierwsze kroki — w tej kolejności
| Krok | Narzędzie | Co sprawdzić |
|---|---|---|
| 1 | Mezmo (centralne logi) | Logi z ostatnich 15 min dla affected container / serwisu. To jest PIERWSZY krok. |
| 2 | curl endpoint | HTTP status — 502 (nginx nie widzi kontenera) vs 504 (timeout) vs 4xx/5xx (app) |
| 3 | docker ps na bms-1 | Czy kontener działa? Uptime? Status? |
| 4 | PM2 logs | Błędy aplikacji — Mongoose disconnect, RabbitMQ ETIMEDOUT, OP_QUERY |
| 5 | nginx-proxy config | Ghost entries, błędna sieć Docker |
| 6 | MongoDB rs.status() | Stan replica set, PRIMARY/SECONDARY health |
Komendy diagnostyczne
# --- bms-1 (94.23.26.113) ---
# 1. Status kontenerów
ssh root@94.23.26.113 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "v4[0-9]|v3[0-9]|nginx"'
# 2. PM2 logs (v42-prod)
ssh root@94.23.26.113 'docker exec v42-prod tail -50 /var/log/v42-prod/pm2/pm2_v42-prod_production_err.log'
ssh root@94.23.26.113 'docker exec v42-prod tail -50 /var/log/v42-prod/pm2/pm2_v42-prod_production_out.log'
# 3. PM2 logs (v32-prod)
ssh root@94.23.26.113 'docker exec v32-prod tail -50 /var/log/v32-prod/pm2/pm2_v32-prod_production_err.log'
# 4. nginx-proxy — czy widzi kontenery?
ssh root@94.23.26.113 'docker exec nginx-proxy nginx -T 2>&1 | grep -E "server_name|upstream|error" | head -40'
# 5. Sprawdź sieci Docker kontenera
ssh root@94.23.26.113 'docker inspect v42-prod --format "{{range \$k,\$v := .NetworkSettings.Networks}}{{println \$k}}{{end}}"'
# --- bms-4 (54.36.123.110) — RabbitMQ ---
ssh root@54.36.123.110 'docker ps | grep rabbit; ss -tlnp | grep 5672'
# --- MongoDB rs0 (z bms-2) ---
# Hasło z secrets/bms-servers.env.sops (klucz: mongodb_rs0_admin_password)
# NIGDY nie drukuj hasła w logu — używaj bezpiecznego wzorca ekstrakcji
ssh root@145.239.133.104 'mongosh --quiet --eval "rs.status().members.forEach(function(m){print(m.name,m.stateStr,m.health)})" mongodb://admin:<pass>@localhost:27017/admin'Łańcuch root cause (typowy)
Użytkownik: upload nie działa
→ nginx 504 → v42-prod timeout
→ PM2 log: RabbitMQ ETIMEDOUT
→ bms-4: rabbitmq container stopped
→ Fix A: docker run rabbitmq + pm2 restart
→ nginx 502 → kontener w innej sieci Docker
→ Fix: docker network connect + docker restart nginx-proxy
→ 500 na endpoint → PM2 log: OP_QUERY command: insert
→ Mongoose 4.x vs MongoDB 6.x driver mismatch
→ Tylko długoterminowy fix: upgrade driver
4. Naprawianie
OBOWIĄZKOWE przed modyfikacją produkcji
Zawsze powiedz co zamierzasz zrobić i poczekaj na potwierdzenie użytkownika (“tak”) przed zapisem na serwer produkcyjnym.
Zamierzam:
1. docker cp v42-prod:/app/path/file.js /tmp/backup-file.js (backup)
2. Zmodyfikować /tmp/file.js (zmiana: linia 42, stary → nowy kod)
3. docker cp /tmp/file.js v42-prod:/app/path/file.js
4. docker exec v42-prod pm2 restart app-name
Wykonać? (tak/nie)
Hotfix pattern — plik w kontenerze
# 1. Backup PRZED zmianą
ssh root@94.23.26.113 'docker cp v42-prod:/app/path/controller.js /tmp/backup-controller-$(date +%Y%m%d-%H%M).js'
# 2. Pobierz plik do edycji
ssh root@94.23.26.113 'docker cp v42-prod:/app/path/controller.js /tmp/controller.js'
# 3. Zrób zmianę (na local dev machine lub przez heredoc na serwerze)
# NIGDY przez CRLF — pliki JS są uruchamiane przez PM2 na Linuxie
# 4. Wgraj z powrotem
ssh root@94.23.26.113 'docker cp /tmp/controller.js v42-prod:/app/path/controller.js'
# 5. Restart PM2
ssh root@94.23.26.113 'docker exec v42-prod pm2 restart app-name'
# 6. Weryfikacja
sleep 5
curl -sw "%{http_code}" https://api.w4.pinbox24.com/api/i18n/langs -o /dev/null
# Oczekiwane: 200Hotfix — zmiana env var w działającym kontenerze (PM2 restart only, NIE Docker restart)
# Wyciągnij secret z SOPS (bez wyświetlania wartości)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$env:THE_PASS = (sops --decrypt --input-type dotenv --output-type dotenv secrets\bms-servers.env.sops `
| Select-String "^KEY_NAME=").ToString().Split("=",2)[1]
$encoded = [System.Uri]::EscapeDataString($env:THE_PASS)
$envVar = "KEY_NAME=value:${encoded}@host/db?..."
ssh root@94.23.26.113 "docker exec -e '$envVar' v42-prod pm2 restart all --update-env"
$env:THE_PASS = ""; $encoded = ""; $envVar = ""Dokumentuj każdą zmianę
Po każdym hotfixie zapisz:
- Plik przed (
old:) i po (new:) zmianie - Timestamp i kontener
- Który backup gdzie leży (
/tmp/backup-controller-YYYYMMDD-HHMM.js)
5. Rollback
Kiedy rollbackować vs fix forward
| Sytuacja | Decyzja |
|---|---|
| Fix powoduje nowy błąd | Rollback natychmiast |
| Fix działa, ale nie w pełni | Fix forward (nie mieszaj stanów) |
| Hotfix in-memory, kontener zostanie zrestartowany | Rollback nie wystarczy — zaplanuj permanent fix |
| Nie masz backupu | STOP — nie modyfikuj bez backupu |
Rollback — plik w kontenerze
# Przywróć backup
ssh root@94.23.26.113 'docker cp /tmp/backup-controller-YYYYMMDD-HHMM.js v42-prod:/app/path/controller.js'
ssh root@94.23.26.113 'docker exec v42-prod pm2 restart app-name'Rollback — restart z obrazu Docker (PM2 restart nie pomaga)
# v42-prod z docker-compose
ssh root@94.23.26.113 'cd /root/builds/7N4sbbrB/0/pinbox24/p24-back-ts && CONTAINER_NAME=v42-prod IMAGE_NAME=563740926945.dkr.ecr.eu-central-1.amazonaws.com/v42-prod docker-compose up -d --no-build backend'UWAGA: In-memory hotfix (zmiana env var przez pm2 restart --update-env) NIE przeżyje docker recreate. Jeśli zrobisz recreate, wszystkie tymczasowe env var wrócą do wartości z docker-compose.yml / backend-environment.env. Zawsze planuj permanent fix przed restartem kontenera.
Rollback — nginx-proxy (sieć Docker)
# Odłącz od sieci dodanej przez pomyłkę
ssh root@94.23.26.113 'docker network disconnect wrongnet v42-prod && docker restart nginx-proxy'6. Dokumentacja po incydencie
Obowiązkowe kroki PRZED zamknięciem sesji
-
Dokument incydentu — skopiuj template i wypełnij:
cp docs/pinbox24/incident-template.md docs/pinbox24/incident-YYYY-MM-DD-slug.mdWypełnij: symptom, oś czasu, root cause, zmienione pliki, weryfikacja, lekcje.
-
Zamknij GH issue z komentarzem resolution:
gh issue comment NNN --repo radieu/p24-infra --body "RESOLVED: <krótki opis fixu>" gh issue close NNN --repo radieu/p24-infra -
Aktualizuj
docs/priorities.md:- Oznacz rozwiązane P0/P1 jako done
- Dodaj nowe ryzyka odkryte podczas incydentu
- Zaktualizuj datę
-
Napisz lub zaktualizuj playbook jeśli scenariusz może się powtórzyć:
docs/playbooks/<komponent>-<scenariusz>.md -
Commit wszystkiego przed zamknięciem sesji:
git add docs/pinbox24/incident-YYYY-MM-DD-slug.md docs/priorities.md docs/playbooks/... git commit -m "docs: incident YYYY-MM-DD — <slug>, priorities update"
7. Znane słabe punkty — zawsze sprawdź
Architektura (mapa)
| Komponent | Serwer | Stack | Znana słabość |
|---|---|---|---|
| v42-prod (W4 backend) | bms-1 | Node.js + PM2 w Dockerze | Wymaga RabbitMQ na bms-4 przy starcie |
| v32-prod (W3 backend) | bms-1 | Node.js + PM2 w Dockerze | Dwa niezależne połączenia MongoDB (PMONGODB_URL + MONGODB_URL) |
| nginx-proxy | bms-1 | nginx-proxy auto | Ghost entries dla zatrzymanych kontenerów (confusing, nie groźne) |
| MongoDB rs0 | bms-2/3/4 | MongoDB 6.x | s3-v32-prod używa Mongoose 4.x / driver 2.x — OP_QUERY incompatible |
| s3-v2-v42-prod | bms-1 | microservice S3 | v42-prod oczekuje body.result[0], microservice zwraca bare array |
| RabbitMQ | bms-4 | Docker (manual run) | NIE w docker-compose → nie przeżywa rebootu bms-4 (issue #1716) |
| PM2 logs | bms-1 | /var/log/*/pm2/ | Loguje pełne MongoDB URI przy starcie → wyciek credentials (issue #2397) |
| iptables / UFW | bms-2/3/4 | ufw + legacy iptables | DROP rules przed UFW mogą blokować RS heartbeat na port 27017 |
Checklista przy każdym incydencie MongoDB/upload
[ ] Sprawdź rs.status() — PRIMARY up, SECONDARY lag < 10s
[ ] Sprawdź PM2 logs v42-prod pod kątem "Mongoose disconnected" BEZ poprzedzającego "Mongoose connected"
[ ] Sprawdź PM2 logs v32-prod — OBA połączenia: PMONGODB_URL I MONGODB_URL
[ ] Sprawdź czy RabbitMQ działa na bms-4 (port 5672)
[ ] NIE uruchamiaj `pm2 env 0` bez | grep + redact — wyciek URI
[ ] Po każdej rotacji hasła rs0 → zaktualizuj backend-environment.env na bms-1 dla v42 i v32
[ ] URL-encode hasło MongoDB przed wstawieniem do URI (@ ! = # łamią parsowanie)
8. Weryfikacja po naprawie
# W4 i18n (powinno zwrócić 200 z JSON)
Invoke-WebRequest "https://api.w4.pinbox24.com/api/i18n/langs" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
# W3 i18n
Invoke-WebRequest "https://api.w3.pinbox24.com/api/i18n/langs" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
# Login responds (401/422 = OK; 504 = wciąż zepsuty)
Invoke-WebRequest "https://api.w4.pinbox24.com/api/auth" -Method POST `
-Body '{"email":"x","password":"x"}' -ContentType "application/json" `
-TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
# Frontendy ładują się
Invoke-WebRequest "https://w4.pinbox24.com" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
Invoke-WebRequest "https://w3.pinbox24.com" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCodePoczekaj ≥5 min i sprawdź Grafanę — czy alert EndpointDown wygasł.
Powiązane playbooks
docs/playbooks/pinbox24-w3-w4-outage-diagnosis.md— szczegółowy flowchart 502/504docs/playbooks/mongodb-credential-rotation.md— rotacja hasła rs0 + aktualizacja kontenerówdocs/playbooks/bms1-dr-plan.md— DR dla bms-1docs/playbooks/static-api-key-incident-rotation.md— wyciek credentialsdocs/pinbox24/incident-template.md— szablon dokumentu incydentu
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="other",
resource="<incident-resource>",
result="success", # "success" | "failed" | "skipped"
detail="Incident response action — see incident ticket for specific resource and detail",
env="<env>",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'other', '<incident-resource>', 'success', 'Incident response action — see incident ticket for specific resource and detail', '<env>')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''