Files
enduro-trails/docs/work-items/ET-015/07-infra-requirements.md
claude-bot 4f80c250cf
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 10s
CI / build (push) Successful in 3s
architect(ET): auto-commit from architect run_id=102
2026-06-05 15:27:58 +00:00

23 KiB
Raw Blame History

type, work_item_id, title, version, status, created_at, authors
type work_item_id title version status created_at authors
infra-requirements ET-015 Инфраструктурные требования — ET-015: Healthcheck enduro-trails-app через python urllib 1 approved 2026-06-05
agent:architect

Инфраструктурные требования — ET-015

1. Резюме

ET-015 — infrastructure-only bug-fix одной YAML-секции в docker-compose.yml. Меняется:

  • docker-compose.yml — секция healthcheck сервиса app (-1 строка test, +5 строк новый массив + start_period).
  • CHANGELOG.md — +1 строка в Unreleased.
  • docs/work-items/ET-015/06-adr/ADR-020-...md — новый ADR.
  • docs/architecture/adr/README.md — +1 строка в глобальном индексе.

Инфраструктура почти не меняется:

  • 0 новых docker-сервисов.
  • 0 изменений в Dockerfile (образ python:3.12-slim остаётся as-is).
  • 0 новых пакетов в образе (никаких apt-get install curl/wget).
  • 0 новых файлов БД, миграций, индексов.
  • 0 новых cron-записей.
  • 0 новых env-переменных, секретов, API-ключей.
  • 0 новых исходящих HTTPS-соединений (healthcheck — на loopback контейнера).
  • 0 новых портов.
  • 0 изменений в nginx (на хосте).
  • 0 изменений в backend (src/api/*).
  • 0 изменений во фронтенде (src/web/*).
  • 0 изменений в стилях, конфигах, скриптах деплоя.

Меняется только:

  • Команда, которую Docker запускает для healthcheck'а контейнера app.
  • Конфигурация healthcheck'а: добавляется start_period: 20s.

Эскалация: minor change (см. ADR-020 §«Классификация изменения»).

2. Контейнеры и сервисы

2.1 Сводная таблица

Аспект Требование
Новый сервис Нет
Изменения Dockerfile Нет (образ python:3.12-slim без новых пакетов)
Изменения docker-compose.ymlapp Да: секция healthcheck.test + start_period: 20s
Изменения docker-compose.ymlgps-collector Нет (BRD §7 out of scope)
Изменения docker-compose.yml — networks/volumes/profiles Нет
Перезапуск app после деплоя Нужен — docker compose up -d app (пересоздание контейнера, ~5 сек простоя HTTP)
Ребилд образа app (docker compose build) Не нужен (TRZ R-2, AC-04, IT-04). Допускается, но не обязателен
Перезапуск gps-collector Не нужен (не затронут, batch profile)
Очистка серверных кэшей Не требуется
Очистка клиентских кэшей Не требуется (фронтенд не меняется)

2.2 Зависимости между сервисами

Без изменений vs PH-1..PH-8:

  • app (uvicorn :5556 внутри контейнера) — отдаёт /api/health, /api/route/*, /api/gps-tracks/*, /terrain/*, статику /enduro/*.
  • nginx (хост mva154) → app:5556 через docker bridge.
  • gps-collector (profile batch) → пишет в data/gps_tracks.sqlite, не имеет открытого порта, не задействован в healthcheck.

Healthcheck живёт внутри network namespace контейнера app и обращается к http://localhost:5556/api/health — это loopback самого контейнера, не хост и не другой контейнер. Не зависит от nginx, iptables хоста, OSRM_URL или gps-collector.

2.3 Образ app

Параметр До ET-015 После ET-015
Базовый образ python:3.12-slim python:3.12-slim
Размер ~250 МБ (приблизительно) ~250 МБ (тот же)
Пакеты apt базовый набор slim + pip-зависимости без изменений
Python 3.12 (alias pythonpython3) 3.12 (без изменений)
urllib.request, sys stdlib (входят в Python) stdlib (входят в Python)
curl отсутствует (источник бага) отсутствует (не нужен)
wget отсутствует отсутствует
Слои Docker без изменений без изменений

2.4 gps-collector — почему без healthcheck'а

Причина Источник
profiles: ["batch"] — не стартует при docker compose up -d docker-compose.yml:30
restart: "no" — контейнер не должен подниматься обратно docker-compose.yml:40
Нет открытого порта (нет ports: секции) docker-compose.yml:28-40
Команда python -m scripts.gps_collect отрабатывает и завершается ADR-007 (ET-008)
Healthcheck для batch-задачи бессмыслен (это not a daemon) BRD §7 (ET-015)

Если профиль когда-нибудь сменится на long-running daemon — нужен отдельный work-item (см. TD-2 в ADR-020).

3. Сеть

Аспект Требование
Новые входящие порты Нет
Изменения nginx (хост) Нет
Новые исходящие соединения Нет (healthcheck — loopback внутри контейнера)
CORS Без изменений
HTTPS / TLS Без изменений
Docker bridge / networks Без изменений
iptables на хосте Без изменений
Firewall / security groups Без изменений

3.1 Healthcheck network path

[docker exec health probe]
   │
   ▼
python process (in container)
   │
   ▼ urllib.request.urlopen("http://localhost:5556/api/health", timeout=3)
   │
   ▼ TCP connect → 127.0.0.1:5556 (loopback в network namespace контейнера)
   │
   ▼
uvicorn (тот же процесс, который запущен `CMD ["uvicorn", "src.api.main:app", ...]`)
   │
   ▼ FastAPI router → @app.get("/api/health") → src/api/main.py:1224
   │
   ▼ HTTP 200 + JSON {"status": "ok", ...}
   │
   ▼
python sys.exit(0)
   │
   ▼
Docker: exit code 0 → State.Health.Status = healthy

Никаких внешних сетевых вызовов. Никакого DNS resolve. Никаких TLS. Никакой зависимости от nginx, OSRM, gps-collector, тайл-провайдеров.

3.2 Ingress/Egress — оценка дельты

ET-015 не меняет паттерн трафика приложения. Healthcheck-трафик (/api/health каждые 30 с) уже был до фикса — Docker и раньше пытался его делать через curl, но проваливался до connect'а. Теперь запросы реально доходят до uvicorn. Дельта:

  • +2 req/min к /api/health внутри контейнера, ~7 мс ответ, ~0.1 КБ ответ. Пренебрежимо для uvicorn (он и так уже обслуживает реальный трафик пользователей).
  • Egress / nginx-трафик — без изменений.

4. Серверные ресурсы

4.1 Сводная таблица

Аспект Требование Дельта
CPU app Без изменений +0.01% (fork+exec python каждые 30 с — пренебрежимо)
RAM app Без изменений временно ~510 МБ на ~50100 мс жизни healthcheck-процесса
Disk app Без изменений 0
CPU gps-collector Без изменений 0
RAM gps-collector Без изменений 0
Disk gps-collector Без изменений 0

4.2 Оценка дельты CPU/RAM

  • Fork + exec python -c "...": интерпретатор поднимается за ~80150 мс на mva154 (нагретый ФС-кэш). За цикл 30 с — 0.5% от одного ядра в пике (на 100150 мс), что в среднем ≈ 0.005% CPU.
  • RAM: одноразово ~510 МБ на жизнь процесса. После завершения — возвращается ОС.
  • На фоне общего idle-загруза app (uvicorn ~5080 МБ RAM, ~12% CPU в idle) — пренебрежимо.

4.3 Disk

  • Образ не растёт (Dockerfile не меняется).
  • Логи Docker (/var/lib/docker/containers/.../*.log) — Docker пишет результаты healthcheck'а в State.Health.Log (хранится в inspect-структуре контейнера). Объём — небольшой, ограничен ротацией Docker (по умолчанию 5 последних записей).
  • nginx access.log — без изменений (healthcheck-трафик внутренний, через nginx не проходит).

5. Конфигурация и секреты

Аспект Требование
Новые env-переменные Нет
Новые секреты Нет
Новые API-ключи Нет
Изменения config/*.yaml Нет
Изменения runtime config Нет
Изменения style.json/style-dark.json Нет

Healthcheck-URL зашит в YAML-строку (http://localhost:5556/api/health). Порт не параметризован через ${PORT} намеренно (TRZ R-3):

  • PORT=5556 стоит в Dockerfile (ENV PORT=5556) и в compose (PORT=5556). Если в будущем порт станет переменным, healthcheck-строку можно будет переписать через shell-form (CMD-SHELL) с подстановкой $PORT. Сейчас — YAGNI.

6. Деплой

6.1 Среды

  • dev (локально): make dev (или docker compose up -d app). Достаточно git pull && docker compose up -d app для смены healthcheck-команды. Без docker compose build.
  • test (mva154): https://openclaw.mva154.duckdns.org/enduro/. Деплой через make deploy-test (стандартная процедура), либо ручной SSH + docker compose up -d app.
  • prod — пока не задействован; ET-015 деплоится только в test.

6.2 Процедура деплоя в test

  1. Pre-deploy snapshot — зафиксировать «как было»:

    ssh mva154 'docker inspect enduro-trails-app-1 \
      --format "Status: {{.State.Health.Status}} | FailingStreak: {{.State.Health.FailingStreak}} | RestartCount: {{.RestartCount}}"'
    

    Ожидается (для подтверждения бага из BRD §1): Status: unhealthy | FailingStreak: <большое число> | RestartCount: 0.

  2. Pre-deploy smoke — проверить, что приложение реально живо:

    curl -sI 'https://openclaw.mva154.duckdns.org/enduro/api/health' | head -1
    

    Ожидается HTTP/1.1 200 OK.

  3. Pull новой версии на mva154 (после merge в main):

    ssh mva154 'cd /home/slin/enduro-trails && git pull'
    
  4. Пересоздание контейнера app (без ребилда образа — TRZ R-2):

    ssh mva154 'cd /home/slin/enduro-trails && docker compose up -d app'
    

    Docker увидит изменение healthcheck в compose-файле, пересоздаст контейнер enduro-trails-app-1. Контейнер gps-collector не трогается (batch profile + restart: "no").

  5. Pre-stable wait — Docker применит start_period: 20s. Первые ~20 с healthcheck может показывать starting. Затем циклы interval=30s × retries=3 — то есть до healthy пройдёт ≤ 20 + 30 = 50 с в нормальном случае, гарантированный SLA — ≤ 120 с (AC-01).

  6. Post-deploy verification — три замера:

    # T0 = сразу после up -d app, повторять с интервалом 30 с до healthy
    for i in 1 2 3 4 5 6; do
      ssh mva154 'docker inspect enduro-trails-app-1 \
        --format "T+{{.State.Health.Status}} streak={{.State.Health.FailingStreak}}"'
      sleep 30
    done
    

    Ожидается: за ≤ 120 сT+healthy streak=0.

    # Подтверждение AC-02: healthy через 5 и 10 минут
    sleep 300 && ssh mva154 'docker inspect ... --format "{{.State.Health.Status}} {{.State.Health.FailingStreak}}"'
    sleep 300 && ssh mva154 'docker inspect ... --format "{{.State.Health.Status}} {{.State.Health.FailingStreak}}"'
    

    Ожидается: оба замера — healthy 0.

    # Подтверждение AC-08: эндпоинт живой снаружи
    curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' \
      'https://openclaw.mva154.duckdns.org/enduro/api/health'
    

    Ожидается 200 <1.0.

  7. Записать результаты в docs/work-items/ET-015/13-test-report.md и docs/work-items/ET-015/14-deploy-log.md (на следующих этапах).

6.3 Rollback

В случае проблем (например, python one-liner крэшит на нестандартной Docker Engine или эндпоинт /api/health начал отвечать медленнее 3 с):

  1. Revert коммита: git revert <commit> на mva154 в main.
  2. Пересоздание контейнера: docker compose up -d app.
  3. Старая (поломанная) healthcheck-команда curl ... вернётся, но само приложение продолжит работать (доказано в BRD §1).

RTO: ≤ 5 минут. RPO: 0 — никаких данных не теряется (healthcheck — read-only HTTP запрос).

6.4 CI/CD гейты

  • make lint (ruff + eslint + YAML-валидация compose) — должен быть зелёным. Проверяет, что docker-compose.yml парсится.
  • make test (pytest unit + integration):
    • ST-01..ST-07 — статические проверки (grep по compose/Dockerfile/CHANGELOG).
    • UT-01..UT-03 — smoke на python one-liner против live make dev (опционально, требует поднятого uvicorn).
  • Integration-CI / ручная проверка:
    • IT-01..IT-04 — docker compose up -d app локально + проверка переходов healthy/unhealthy.
  • E2E на mva154:
    • E2E-01 — docker inspect после make deploy-test (оператор).
    • E2E-02 — curl https://openclaw.mva154.duckdns.org/enduro/api/health (автоматизируется).

6.5 Зависимости деплоя

  • Docker Engine на mva154: должен поддерживать start_period (введён в Docker 1.12 / 2016). На mva154 — Docker ≥ 20.10 (BRD §6). ✓
  • Compose version: version: "3.8" (docker-compose.yml:1) поддерживает все используемые healthcheck-поля. ✓
  • Образ python:3.12-slim должен оставаться available на Docker Hub. ✓

7. Observability / Логирование

Аспект Требование
Новые лог-сообщения Нет новых на стороне приложения
Логи healthcheck Docker пишет в State.Health.Log (просмотр через docker inspect)
Метрики / Prometheus Не вводим (но State.Health.Status теперь стал достоверным для будущей интеграции)
Health endpoint /api/health без изменений; /api/gps-tracks/health без изменений
uvicorn.access лог +2 req/min на /api/health (внутренний loopback) — фоновый шум, не блокирует анализ

7.1 Что мониторить после деплоя

Сутки наблюдения на mva154 (ручная проверка, без алёртов):

  1. docker inspect enduro-trails-app-1:

    • State.Health.Status должен быть healthy стабильно.
    • State.Health.FailingStreak должен оставаться 0.
    • State.Health.Log[-5:] — все ExitCode: 0, Output: "" (или ничего значимого).
    • RestartCount должен оставаться прежним (контейнер не перезапускается из-за healthcheck — у нас нет restart_policy.condition: unhealthy, но всё равно полезно зафиксировать).
  2. uvicorn access.log в контейнере:

    • GET /api/health HTTP/1.1 200 каждые ~30 с.
    • Время ответа стабильно < 100 мс (на mva154 — ~7 мс по замерам BRD §1).
  3. nginx access.log на хосте (внешний трафик):

    • Без изменений vs до деплоя; healthcheck идёт внутри контейнера и в nginx не виден.

7.2 Алёрты (будущее)

ET-015 закрывает причину «вечного unhealthy» — теперь docker inspect ... .State.Health.Status снова достоверная метрика. Если в проекте появится мониторинг (Prometheus + alertmanager, или простой cron-скрипт), можно настроить:

  • Алёрт «контейнер unhealthy ≥ 5 мин» — теперь это будет реальный сигнал.
  • Алёрт «FailingStreak растёт» — раньше был ложно-положительным, теперь — настоящий.

Это не задача ET-015 (out of scope BRD §7), но ET-015 — необходимое условие для будущей интеграции.

8. Резервное копирование / Disaster recovery

Аспект Требование
Backup БД Без изменений vs ET-013/ET-008 (ET-015 не трогает БД)
Backup тайлов Без изменений
Backup статики Без изменений; git — источник истины
Backup конфигурации docker-compose.yml — в git, перепрочитывается при каждом docker compose up
RTO ≤ 5 минут (rollback через git revert + docker compose up -d app)
RPO 0 — никаких данных не теряется

9. Безопасность

Аспект Требование
Auth / Authorization Без изменений
Валидация входных данных Не применимо — healthcheck не принимает внешних входов
CSP Без изменений
Rate-limit Без изменений (loopback-трафик не подпадает)
TLS Без изменений
Shell injection Снят как риск (см. ADR-020 Вариант A: используется CMD-массив, не CMD-SHELL; нет интерполяции пользовательского ввода)
urllib.request SSRF Не применимо: URL зашит в YAML, не строится из переменных; loopback only
Privilege escalation Не применимо: python запускается от того же user'а, что и uvicorn (root в python:3.12-slim — стандартно для этого образа; ET-015 это не меняет)

9.1 Анализ риска urllib.request vs curl (security delta)

  • curl (если бы был установлен): C-код с историей CVE (HTTP/2, TLS, libidn). Не используется — изначально его нет.
  • urllib.request: чистый Python, stdlib. История CVE значительно меньше; используется только на loopback с фиксированным URL → SSRF поверхность отсутствует.
  • Чистый выигрыш по security: меньше attack surface, меньше кода в образе.

10. Совместимость

Аспект Требование
API контракт Без изменений
Совместимость с PH-1..PH-9 Полностью совместимо: healthcheck — runtime инфра, не задевает фичи
Совместимость с ET-007/008/009/011/012/013/014 Полностью совместимо
Совместимость с Docker Engine ≥ 20.10 (требуется start_period); подтверждено на mva154 (BRD §6)
Совместимость с Docker Compose version: "3.8" поддерживает все используемые поля
Совместимость с базовым образом python:3.12-slimpython alias + urllib.request + sys гарантированы
Совместимость с будущими образами python:3.13-slim и далее Высокая: urllib.request стабильный API с Python 2.x; alias python поддерживается во всех современных python-slim тегах
localStorage migration Не применимо — фронтенд не трогается
Совместимость со старыми вкладками браузера Не применимо

11. Связанные документы

  • 01-brd.md §1§9
  • 02-trz.md §1§9 (особенно §3 — целевое состояние, §4 — альтернативы)
  • 03-acceptance-criteria.md AC-01..AC-10
  • 04-test-plan.yaml ST-01..ST-07, UT-01..UT-03, IT-01..IT-04, E2E-01..E2E-02
  • 06-adr/ADR-020-healthcheck-via-python-urllib.md
  • 08-data-requirements.md (этот пакет)
  • 10-tech-risks.md (этот пакет)
  • docs/architecture/README.md §«Компоненты», §«Деплой»
  • docs/work-items/ET-014/07-infra-requirements.md — образец «zero-infra» work-item (наследие)
  • docs/work-items/ET-008/07-infra-requirements.md (если есть) — образец docker-compose правок с major-change escalation (наследие, для контраста: ET-015 явно minor-change)