chore(infra): correct frontend healthcheck (re-apply #117 safely) #120

Merged
lekss361 merged 1 commit from fix/frontend-healthcheck-correct into main 2026-05-14 20:24:47 +00:00
lekss361 commented 2026-05-14 20:16:38 +00:00 (Migrated from github.com)

Summary

Forward-fix после incident May 14 (см. events/Incident_Prod_Down_May14_2026.md в vault). PR #117 был полностью reverted PR #119 — Caddy с depends_on frontend: service_healthy ждал бесконечно фейлящий wget healthcheck → prod down ~25 мин.

Этот PR делает то же что #117, но корректно:

Что изменено

  1. Frontend healthcheck → node TCP probe (вместо wget HTTP):

    • НЕ зависит от HTTP status (200/404/308 — все OK если порт слушает)
    • node гарантированно есть (запускает server.js)
    • 127.0.0.1 вместо localhost (нет DNS ambiguity)
    • start_period: 60s + 6 retries × 15s = 150s window (надёжно для Next.js cold start)
  2. Caddy depends_on:

    • backend: service_healthy — backend имеет работающий /health endpoint
    • frontend: service_startedНЕ service_healthy (lesson из incident: frontend health flaky → не блокируем Caddy)
  3. backend/worker/beat redisservice_startedservice_healthy (redis уже имеет redis-cli ping).

  4. rosreestr2coord>=4.0.0>=5.0.0 (uv.lock уже на 5.3.3, защита от silent downgrade).

Lesson из incident

Frontend health by nature flaky (Next.js cold start variable, route 200/404 зависит от bundle). Backend health стабильно (один defined endpoint). Strict service_healthy для frontend = single point of failure для всех доменов через Caddy (включая obsidian.gendsgn.ru через shared network).

Test plan

  • CI green
  • После merge → deploy.yml → compose up -d --wait → frontend reaches healthy через ≤90s
  • docker compose ps → все services running, frontend (healthy)
  • curl https://gendsgn.ru/ → 200
  • curl https://obsidian.gendsgn.ru/ → 200
  • If healthcheck дребезжит — Caddy всё равно отвечает (service_started safety net)

Rollback plan

Если что-то снова сломается — gh pr create с git revert <commit> (как было сделано с PR #119). Forward-fix design убирает single-failure path, так что revert не должен быть нужен.

Vault updates

  • events/Incident_Prod_Down_May14_2026.md — full postmortem (timeline, root cause, lessons, action items) уже создан
  • Updates feedback rules: feedback_vault_with_code добавлен в auto-memory

Closes incident gracefully.

## Summary Forward-fix после incident May 14 (см. `events/Incident_Prod_Down_May14_2026.md` в vault). PR #117 был полностью reverted PR #119 — Caddy с `depends_on frontend: service_healthy` ждал бесконечно фейлящий wget healthcheck → prod down ~25 мин. Этот PR делает то же что #117, но **корректно**: ### Что изменено 1. **Frontend healthcheck → node TCP probe** (вместо wget HTTP): - НЕ зависит от HTTP status (200/404/308 — все OK если порт слушает) - `node` гарантированно есть (запускает server.js) - `127.0.0.1` вместо `localhost` (нет DNS ambiguity) - `start_period: 60s` + 6 retries × 15s = 150s window (надёжно для Next.js cold start) 2. **Caddy depends_on**: - `backend: service_healthy` — backend имеет работающий /health endpoint - `frontend: service_started` — **НЕ service_healthy** (lesson из incident: frontend health flaky → не блокируем Caddy) 3. **backend/worker/beat redis** → `service_started` → `service_healthy` (redis уже имеет `redis-cli ping`). 4. **`rosreestr2coord>=4.0.0` → `>=5.0.0`** (uv.lock уже на 5.3.3, защита от silent downgrade). ## Lesson из incident Frontend health by nature flaky (Next.js cold start variable, route 200/404 зависит от bundle). Backend health стабильно (один defined endpoint). **Strict `service_healthy` для frontend = single point of failure для всех доменов через Caddy** (включая obsidian.gendsgn.ru через `shared` network). ## Test plan - [ ] CI green - [ ] После merge → deploy.yml → `compose up -d --wait` → frontend reaches healthy через ≤90s - [ ] `docker compose ps` → все services running, frontend (healthy) - [ ] `curl https://gendsgn.ru/` → 200 - [ ] `curl https://obsidian.gendsgn.ru/` → 200 - [ ] If healthcheck дребезжит — Caddy всё равно отвечает (service_started safety net) ## Rollback plan Если что-то снова сломается — `gh pr create` с `git revert <commit>` (как было сделано с PR #119). Forward-fix design убирает single-failure path, так что revert не должен быть нужен. ## Vault updates - `events/Incident_Prod_Down_May14_2026.md` — full postmortem (timeline, root cause, lessons, action items) уже создан - Updates feedback rules: [[feedback_vault_with_code]] добавлен в auto-memory Closes incident gracefully.
lekss361 commented 2026-05-14 20:19:44 +00:00 (Migrated from github.com)

Bot review — применяю свежий healthcheck-review чеклист

Дизайн решает root cause из incident May 14 по архитектуре, а не по «улучшим параметры». Прохожу свой чеклист:

# Правило из чеклиста Этот PR
1 Probed path → 2xx N/A — TCP probe, не HTTP. Sidesteps 404/308 entirely. Правильный ход.
2 Probe tool в runtime image node гарантированно есть (запускает server.js). Лучше чем wget.
3 start_period ≥60s 60s + 6×15s = 150s window.
4 service_healthy каскад риск Решено architecturally: caddy.depends_on.frontend: service_started (НЕ healthy). Frontend health flake не валит Caddy/obsidian. Это ключевой урок incident, и здесь он применён правильно. backend: service_healthy оставлен — backend имеет надёжный /health.
5 CI green ≠ healthcheck works 🟡 Test plan включает compose up -d --wait post-merge, но architectural safety margin делает это OK: worst case = frontend domain 502, НЕ total prod down. Incident pattern eliminated by design.

Технические заметки

Node TCP probe script (require('net').createConnection(...))— корректен. 127.0.0.1 вместо localhost снимает DNS ambiguity. .once('connect', exit 0).once('error', exit 1) без timeout — но Docker даёт timeout: 5s в обёртке, так что hang на DNS/socket-init обработается извне.

Минор (не блокер): TCP-only probe пройдёт даже если Next.js слушает порт но event loop заблокирован (например, deadlock в bootstrap). Но т.к. caddy на service_started для frontend, это не каскадит — просто frontend route отдаст 502 пока он реально не готов. Acceptable trade-off.

CI status

  • frontend: SUCCESS
  • backend: 🔄 IN_PROGRESS

Мерджу как только backend CI зелёный. (Не пишу маркер аппрува сейчас чтобы next cron tick дождался CI.)

## Bot review — применяю свежий [healthcheck-review](https://github.com/lekss361/-gendesign) чеклист Дизайн решает root cause из incident May 14 **по архитектуре**, а не по «улучшим параметры». Прохожу свой чеклист: | # | Правило из чеклиста | Этот PR | |---|---|---| | 1 | **Probed path → 2xx** | ✅ N/A — TCP probe, не HTTP. Sidesteps 404/308 entirely. Правильный ход. | | 2 | **Probe tool в runtime image** | ✅ `node` гарантированно есть (запускает server.js). Лучше чем wget. | | 3 | **`start_period` ≥60s** | ✅ 60s + 6×15s = 150s window. | | 4 | **`service_healthy` каскад риск** | ✅ Решено architecturally: `caddy.depends_on.frontend: service_started` (НЕ healthy). Frontend health flake **не валит** Caddy/obsidian. Это ключевой урок incident, и здесь он применён правильно. `backend: service_healthy` оставлен — backend имеет надёжный `/health`. | | 5 | **CI green ≠ healthcheck works** | 🟡 Test plan включает `compose up -d --wait` post-merge, но **architectural safety margin** делает это OK: worst case = frontend domain 502, **НЕ** total prod down. Incident pattern eliminated by design. | ### Технические заметки **Node TCP probe script** (`require('net').createConnection(...)`)— корректен. `127.0.0.1` вместо `localhost` снимает DNS ambiguity. `.once('connect', exit 0).once('error', exit 1)` без timeout — но Docker даёт `timeout: 5s` в обёртке, так что hang на DNS/socket-init обработается извне. **Минор (не блокер)**: TCP-only probe пройдёт даже если Next.js слушает порт но event loop заблокирован (например, deadlock в bootstrap). Но т.к. caddy на `service_started` для frontend, это не каскадит — просто frontend route отдаст 502 пока он реально не готов. Acceptable trade-off. ### CI status - frontend: ✅ SUCCESS - backend: 🔄 IN_PROGRESS **Мерджу как только backend CI зелёный.** (Не пишу маркер аппрува сейчас чтобы next cron tick дождался CI.)
lekss361 commented 2026-05-14 20:23:57 +00:00 (Migrated from github.com)

Final review (SHA 0c48166, CI )

  • backend: SUCCESS (20:18:59Z)
  • frontend: SUCCESS (20:18:26Z)
  • Architectural safety (TCP probe + caddy service_started для frontend) — incident pattern архитектурно невозможен

approve merge

## Final review (SHA `0c48166`, CI ✅) - backend: SUCCESS (20:18:59Z) - frontend: SUCCESS (20:18:26Z) - Architectural safety (TCP probe + caddy service_started для frontend) — incident pattern архитектурно невозможен approve merge
lekss361 commented 2026-05-14 20:27:47 +00:00 (Migrated from github.com)

Tracking: part of audit batch #127 (incident fix). Vault: events/Incident_Prod_Down_May14_2026.md.

Tracking: part of audit batch #127 (incident fix). Vault: `events/Incident_Prod_Down_May14_2026.md`.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#120
No description provided.