23 KiB
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 |
|
Инфраструктурные требования — 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.yml — app |
Да: секция healthcheck.test + start_period: 20s |
Изменения docker-compose.yml — gps-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(profilebatch) → пишет в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 python → python3) |
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 |
Без изменений | временно ~5–10 МБ на ~50–100 мс жизни healthcheck-процесса |
Disk app |
Без изменений | 0 |
CPU gps-collector |
Без изменений | 0 |
RAM gps-collector |
Без изменений | 0 |
Disk gps-collector |
Без изменений | 0 |
4.2 Оценка дельты CPU/RAM
- Fork + exec
python -c "...": интерпретатор поднимается за ~80–150 мс на mva154 (нагретый ФС-кэш). За цикл 30 с — 0.5% от одного ядра в пике (на 100–150 мс), что в среднем ≈ 0.005% CPU. - RAM: одноразово ~5–10 МБ на жизнь процесса. После завершения — возвращается ОС.
- На фоне общего idle-загруза
app(uvicorn ~50–80 МБ RAM, ~1–2% 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
-
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. -
Pre-deploy smoke — проверить, что приложение реально живо:
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/api/health' | head -1Ожидается
HTTP/1.1 200 OK. -
Pull новой версии на mva154 (после merge в main):
ssh mva154 'cd /home/slin/enduro-trails && git pull' -
Пересоздание контейнера
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"). -
Pre-stable wait — Docker применит
start_period: 20s. Первые ~20 с healthcheck может показыватьstarting. Затем циклыinterval=30s × retries=3— то есть доhealthyпройдёт ≤ 20 + 30 = 50 с в нормальном случае, гарантированный SLA — ≤ 120 с (AC-01). -
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. -
Записать результаты в
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 с):
- Revert коммита:
git revert <commit>на mva154 вmain. - Пересоздание контейнера:
docker compose up -d app. - Старая (поломанная) 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.
- IT-01..IT-04 —
- E2E на mva154:
- E2E-01 —
docker inspectпослеmake deploy-test(оператор). - E2E-02 —
curl https://openclaw.mva154.duckdns.org/enduro/api/health(автоматизируется).
- E2E-01 —
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 (ручная проверка, без алёртов):
-
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, но всё равно полезно зафиксировать).
-
uvicorn access.logв контейнере:GET /api/health HTTP/1.1 200каждые ~30 с.- Время ответа стабильно < 100 мс (на mva154 — ~7 мс по замерам BRD §1).
-
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-slim → python 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–§902-trz.md§1–§9 (особенно §3 — целевое состояние, §4 — альтернативы)03-acceptance-criteria.mdAC-01..AC-1004-test-plan.yamlST-01..ST-07, UT-01..UT-03, IT-01..IT-04, E2E-01..E2E-0206-adr/ADR-020-healthcheck-via-python-urllib.md08-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)