Files
enduro-trails/docs/work-items/ET-008/07-infra-requirements.md
claude-bot d33f360a2f
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 2s
architect(ET-008): ADRs, infra/data requirements, tech risks
2026-06-01 12:15:05 +00:00

22 KiB
Raw Permalink 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-008 Инфраструктурные требования — ET-008: GPS-треки с публичных платформ 1 approved 2026-06-01
agent:architect

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

1. Резюме

В отличие от ET-007 (только-фронтенд), ET-008 — серверная фича со scheduled-pipeline. Изменения охватывают:

  • Новый docker-compose service gps-collector (тот же образ, что app, с profiles: [batch]).
  • Новый файл БД на mva154: data/gps_tracks.sqlite (≤ 2 ГБ).
  • Новая cron-запись на хосте mva154.
  • Новый каталог логов /var/log/enduro-trails/.
  • Новые Python-зависимости в общем образе: defusedxml, pyyaml.
  • Новые исходящие HTTPS-вызовы из контейнера gps-collector к 13 внешним источникам.

Все изменения помещаются в существующий docker-compose стек без введения новых контейнеров API/нового reverse-proxy/новой БД-движка. Эскалация: arch:major-change (см. ADR-005, ADR-007).

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

Аспект Требование
Новый сервис app (FastAPI) Не вводится; существующий API расширяется новыми routes /api/gps-tracks/* через регистрацию роутера из src/api/gps_tracks/endpoint.py
Новый сервис gps-collector Да. docker-compose service, profiles: ["batch"], тот же build: ., command python -m scripts.gps_collect, restart: "no". Не стартует штатно при docker compose up -d. Активируется только запуском docker compose --profile batch run --rm gps-collector
Изменение Dockerfile COPY scripts/ ./scripts/, COPY config/ ./config/. Текущий Dockerfile (COPY src/api/ src/api/, COPY src/web/ src/web/) не содержит scripts/ и config/ — нужно добавить две COPY-строки
Новый блок в docker-compose.yml ≈ 15 строк (см. ADR-007 §1)
Изменения OSRM, nginx Нет
Перезапуск API после деплоя Нужен (новые routes регистрируются при старте FastAPI) — стандартный docker compose up -d --no-deps app
Простой API ≤ 5 секунд (рестарт контейнера API). Pipeline-сервис independent — его запуск/остановка не аффектит API

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

  • gps-collector не имеет depends_on: [app]. Он работает с БД-файлом напрямую через примонтированный volume /app/data.
  • В конце прогона pipeline дёргает HTTP POST http://app:5556/api/gps-tracks/cache/clear (внутренняя docker-сеть). Если app недоступен — pipeline пишет WARNING в лог, успех прогона не отменяется (ADR-007 §7).
  • Сетевое имя app доступно потому что оба сервиса в одной default-сети docker-compose.

2.2 Конфликт с production API во время прогона

  • Pipeline пишет в data/gps_tracks.sqlite в WAL-mode (ADR-005 §5). API читает ту же БД — видит снэпшот checkpoint'а; конкуренция не блокирует читателей.
  • CPU/RAM: pipeline ограничен через docker-cgroup limits (см. §9 ниже). Параллельный API не деградирует.

3. Сеть

Аспект Требование
Новые серверные порты на mva154 Нет
Изменения reverse proxy (/enduro/ в nginx) Минимальные. Новые routes /api/gps-tracks/* уже попадают под существующий location /api/ proxy_pass. Дополнительных правил не нужно
Внутренние DNS / docker-сеть Стандартная default-сеть docker-compose. Service-name app резолвится в адрес API-контейнера; используется pipeline для cache-clear
Endpoint POST /api/gps-tracks/cache/clear Ограничен docker-internal: блок RealIPFromTrustedProxy в nginx (proxy mva154) не пропускает POST на этот endpoint извне. Деталь: в nginx-конфиге location = /api/gps-tracks/cache/clear { allow 172.0.0.0/8; deny all; } — допуск только из docker-сетей
Новые исходящие HTTPS-вызовы из mva154 Да. Из контейнера gps-collector:
api.openstreetmap.org (ADR-009) — всегда;
enduro-russia.ru (ADR-010) — пока accepted;
ttrails.ru (ADR-011) — пока accepted
Firewall mva154 Исходящие HTTPS уже разрешены (BRD §7); правил не добавляется
Внешние входящие Только существующий /enduro/ через nginx — без изменений

3.1 Ограничение cache-clear

Cache-clear endpoint должен быть закрыт от внешнего интернета (он сбрасывает производительный кэш, потенциальный DoS-вектор). Реализация:

# /etc/nginx/sites-available/openclaw — добавляется в существующий server { } для /enduro/
location = /enduro/api/gps-tracks/cache/clear {
    allow 172.16.0.0/12;     # docker default networks
    allow 127.0.0.1;
    deny all;
    proxy_pass http://app:5556/api/gps-tracks/cache/clear;
}

Pipeline дёргает endpoint напрямую через docker-сеть (http://app:5556/...), не через nginx → реальный путь обходит правило allow/deny и работает. Snippet выше защищает только публичный путь через /enduro/.

4. Хранилища данных

Аспект Требование
Новая БД data/gps_tracks.sqlite (SQLite + Spatialite extension)
Расположение на хосте /home/slin/enduro-trails/data/gps_tracks.sqlite (./data в docker-compose.yml)
Расположение в контейнерах /app/data/gps_tracks.sqlite
Создание Pipeline создаёт при первом запуске; миграция migrations/gps_tracks_001_init.sql применяется автоматически (см. §4.2)
Размер Ожидаемо ≤ 500 МБ для ЦФО+Чувашии при 5000 треков; верхний предел операционный — 2 ГБ (REQ-NF-03). Алерт > 2 ГБ — см. 10-tech-risks.md R-4
Spatialite-extension Уже доступен в python-образе через pysqlite3-binary? Нет: текущий образ использует stdlib sqlite3. Нужно установить системный пакет libsqlite3-mod-spatialite (см. §4.3)
Изменения схемы существующей centralfederal.sqlite Нет
Миграции существующих таблиц Нет

4.1 Зачем отдельная БД

См. ADR-005 §«Решение D-A». Изоляция backup-цикла, ротации, риска повреждения, write-конкуренции.

4.2 Миграция

migrations/gps_tracks_001_init.sql — IDempotent CREATE TABLE IF NOT EXISTS + R-tree creation. Применяется автоматически из src/api/gps_tracks/db.py::ensure_schema() при первом коннекте (ленивая инициализация). Никакого alembic или внешнего раннера миграций.

4.3 Установка Spatialite в Docker-образе

Изменение Dockerfile:

FROM python:3.12-slim
WORKDIR /app
# ET-008: Spatialite extension для slot.api.gps_tracks.db
RUN apt-get update && apt-get install -y --no-install-recommends \
    libsqlite3-mod-spatialite \
    && rm -rf /var/lib/apt/lists/*
COPY src/api/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/api/ ./src/api/
COPY src/web/ ./src/web/
COPY scripts/ ./scripts/        # ET-008
COPY config/ ./config/          # ET-008
ENV STATIC_DIR=/app/src/web
ENV PORT=5556
EXPOSE 5556
CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "5556"]

Образ увеличится на ≈ 30 МБ (модуль Spatialite). На размер production-нагрузки не влияет.

4.4 Backup

  • Ежедневный snapshot через cron на mva154:
    0 5 * * * root sqlite3 /home/slin/enduro-trails/data/gps_tracks.sqlite ".backup /home/slin/enduro-trails/backups/gps_tracks-$(date +\%F).sqlite"
    
  • Retention 14 дней — отдельный find ... -mtime +14 -delete.
  • Pipeline-running во время backup допустим: .backup в sqlite3 — атомарный, использует WAL.
  • Восстановление: остановить gps-collector запуски, cp snapshot в data/gps_tracks.sqlite, перезапустить API (cache-clear автоматически).

4.5 Клиентское хранилище

Ключ localStorage Значение Default
gps-tracks-enabled "true" | "false" "false"
gps-tracks-activities JSON-array все ACTIVITY_TYPES
gps-tracks-sources JSON-array все enabled source IDs
gps-tracks-color-mode "source" | "activity" "source"

Суммарный объём ≤ 256 байт. Конвенция имён согласуется с существующими (enduro-theme-mode, terrain-*, trails-*, map-base-layer).

Подробности — 08-data-requirements.md §4.

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

Аспект Требование
Новые env-переменные API-контейнера GPS_TRACKS_DB_PATH=/app/data/gps_tracks.sqlite
Новые env-переменные gps-collector GPS_TRACKS_DB_PATH, GPS_SOURCES_CONFIG=/app/config/gps_sources.yaml, GPS_REGIONS_CONFIG=/app/config/gps_regions.yaml
Новые секреты / API-ключи Нет — все источники без авторизации (см. ADR-009, ADR-010, ADR-011 — outside source без ключа; платные API явно out of scope BRD §3)
Новые конфиг-файлы в репозитории config/gps_sources.yaml, config/gps_regions.yamlоба под git-контролем
Изменения reverse-proxy / nginx Только cache-clear защита (§3.1)
Изменения OSRM Нет

6. Зависимости

Аспект Требование
Python-пакеты (src/api/requirements.txt) — добавить defusedxml==0.7.1 (безопасный XML-парсинг GPX), pyyaml==6.0.1 (конфиги pipeline)
Python-пакеты — НЕ добавлять lxml (упомянут в BRD §7 как опция; для GPX-парсинга достаточно defusedxml.ElementTree; экономит ≈ 8 МБ образа). tenacity — реализуем backoff inline (≈ 30 строк, TRZ §6.3) чтобы не вводить ещё один пакет
Системные библиотеки в Dockerfile libsqlite3-mod-spatialite (см. §4.3)
Версия Python 3.12, без изменений
Новые third-party runtime-зависимости (внешние сервисы) api.openstreetmap.org — OSM API (ADR-009)
enduro-russia.ru — после ADR-010 accepted
ttrails.ru — после ADR-011 accepted
Альтернативные источники / fail-over Не закладывается; каждый source изолирован (ADR-007 §I-A); падение одного не валит других

7. Сборка и деплой

  • Pipeline CI: существующий Gitea Actions (make lint + make test + make build). Новые backend-tests (tests/api/test_gps_tracks_*.py) добавляются в существующий pytest. Новые frontend-tests — в существующий ESLint и JS-test pipeline.

  • Артефакт: Docker-образ. После ET-008 один образ запускается двумя сервисами (app и gps-collector через profiles). Это стандартный паттерн docker-compose.

  • Деплой шаг-за-шагом:

    1. git pull origin main на mva154.
    2. docker compose build (пересобирает образ с libsqlite3-mod-spatialite).
    3. docker compose up -d --no-deps app (перезапускает только API; gps-collector profile-disabled).
    4. Установить cron-запись (см. §8).
    5. Первый ручной запуск pipeline в dry-run: docker compose --profile batch run --rm gps-collector python -m scripts.gps_collect --dry-run
    6. Проверить /api/gps-tracks/health — БД создана, пуста.
    7. Запустить production-сбор: docker compose --profile batch run --rm gps-collector (≤ 6 часов).
    8. Smoke: открыть /enduro/, включить чекбокс «Публичные треки», убедиться что слой виден.
  • Время простоя API: ≤ 5 секунд на шаге 3.

  • Время простоя pipeline: не применимо — pipeline не daemon.

7.1 Cron-запись

/etc/cron.d/enduro-gps (root-owned, 0644):

# ET-008: GPS Tracks Pipeline
# Mon + Thu 03:00 UTC — full collection
0 3 * * 1,4 root cd /opt/enduro-trails && /usr/bin/docker compose --profile batch run --rm gps-collector >> /var/log/enduro-trails/gps-collect.log 2>&1

# 1-е число каждого месяца 04:00 UTC — GC stale tracks
0 4 1 * * root cd /opt/enduro-trails && /usr/bin/docker compose --profile batch run --rm gps-collector python -m scripts.gps_collect --gc >> /var/log/enduro-trails/gps-gc.log 2>&1

Никаких отдельных flock / lockfile — cron-окно (3 дня) > длительности прогона (≤ 6 ч).

7.2 Rollback

Откат Действие Время
Откат кода (revert + redeploy) git revert <commit> && docker compose up -d --build app ≈ 2 мин
Откат БД (повреждение / неверная схема) Остановить gps-collector cron, cp backups/gps_tracks-<date>.sqlite data/gps_tracks.sqlite, рестарт API ≈ 1 мин
Полный отказ от фичи (kill switch) Закомментировать cron-строки, удалить gps-tracks-cb checkbox в UI через display:none ≈ 1 мин
Откат от pipeline без отката API Закомментировать cron-строки — API продолжает отдавать собранное мгновенно

Скрипт scripts/disable_gps_pipeline.sh (TODO в 04-test-plan.yaml) автоматизирует «kill switch».

8. Cron / scheduled jobs

См. §7.1.

Мониторинг cron:

  • При сбое cron-job отправляется email на адрес администратора через стандартный cron MAILTO= (mva154 уже настроен). Опционально — алерт в Telegram, но это outside scope (если в проекте уже есть алерт-канал — используется он).
  • /api/gps-tracks/health отдаёт last_pipeline_run.sources_error — оператор видит при ручной проверке/мониторинге.

9. Ресурсы (CPU / RAM / диск)

9.1 API-контейнер

  • CPU: +5% от текущего baseline за счёт MVT-генерации нового слоя. На существующем mva154 (по BRD §1 одиночный сервер) — не критично.
  • RAM: +50 МБ baseline (новые модули) + до 64 МБ LRU-кэш MVT-тайлов (1024 × ~64 КБ). Итого +120 МБ. Текущий API использует ≈ 200 МБ; после ET-008 — ≈ 320 МБ.
  • Network egress: +0 (внутри сервера; клиент скачивает с того же mva154).

9.2 gps-collector контейнер (во время прогона)

  • CPU: ограничен docker-compose cgroup cpus: "1.0" (один логический CPU) — pipeline не вытесняет API.
  • RAM: ограничен mem_limit: 512m. На практике pipeline + asyncio + httpx + shapely + спарс одного парсера ≤ 200 МБ; запас 2.5×.
  • Network egress (mva154 → external): для OSM ≈ 100 МБ за прогон (≤ 5000 треков × ≤ 20 КБ), для скрейпинга — порядок 10100 МБ. Полная стоимость cron-прогона ≈ 200 МБ / неделю — пренебрежимо.
  • Network ingress: не применимо.
# docker-compose.yml фрагмент
services:
  gps-collector:
    # ...
    cpus: "1.0"
    mem_limit: 512m
    pids_limit: 256

9.3 Диск

  • data/gps_tracks.sqlite — ≤ 2 ГБ.
  • Лог-файлы /var/log/enduro-trails/*.log — ротация через logrotate, default 14 дней × ≤ 50 МБ = ≤ 700 МБ.
  • Backup-снапшоты — ≤ 14 × 2 ГБ = ≤ 28 ГБ (с retention; см. §4.4).
  • Сумма: + ≈ 30 ГБ на текущий disk-budget mva154.

10. Наблюдаемость

Артефакт Источник Использование
GET /api/gps-tracks/health API (читает pipeline_runs из БД) Оператор проверяет вручную или через monitoring
/var/log/enduro-trails/gps-collect.log Cron stdout/stderr Лог cron-выполнений: успех/код возврата/исключения
/var/log/enduro-trails/pipeline-<run_id>.jsonl Pipeline structured log Per-run JSON-lines: source, region, статус, tracks_new
pipeline_runs в БД Pipeline-side Историческая трассировка для health-эндпоинта
Docker docker compose logs app API stdout Запросы /api/gps-tracks/*, ошибки SQL

10.1 Алерты

  • Cron MAILTO при ненулевом exit code прогона — стандартный механизм.
  • 2 неудачных прогона подряд для одного sourcepipeline_runs собирает; алерт не автоматический (out of MVP), оператор увидит при ручной проверке /health или в weekly review. Алерт-канал — отдельный work item.
  • db_size_mb > 2 ГБ — health отдаёт значение; внешний мониторинг (если есть) пинает.
  • Ошибка лицензионного guard'а (status: "skipped_license") — оператор видит в pipeline_runs; не алерт-кейс, нормальное поведение до accepted-ADR.

10.2 Logrotate

# /etc/logrotate.d/enduro-gps
/var/log/enduro-trails/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
}
/var/log/enduro-trails/pipeline-*.jsonl {
    weekly
    rotate 8
    compress
    missingok
    notifempty
}

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

  • Парсинг XML на сервере (GPX) — через defusedxml.ElementTree (защита XXE / billion laughs). lxml не используется.
  • Endpoint POST /api/gps-tracks/cache/clear — ограничен docker-internal сетью на уровне nginx (§3.1). Pipeline ↔ API остаются связаны через docker-сеть.
  • Скрейпинг — только outgoing с mva154. Никаких open ports.
  • Атаки на pipeline через подделанные GPX (источник вернул malformed XML, exploding XML) — митигируется defusedxml и timeout httpx.get(timeout=30). Per-track exception isolated в pipeline-loop.
  • CSP-заголовок — в проекте отсутствует (см. ET-007 §3.2). ET-008 ничего не меняет.

12. Влияние на C4 / архитектурную документацию

Изменения состава компонентов:

  • Новый компонент в стеке mva154: docker-compose service gps-collector (batch).
  • Новая БД data/gps_tracks.sqlite.
  • Новые внешние зависимости рантайма: 13 платформы (OSM всегда + 0/1/2 после ADR-010/011).
  • Новые scheduled-jobs: 2 cron-записи.

docs/architecture/README.md обновляется новым разделом «GPS Tracks Pipeline (ET-008)» с описанием компонента, БД, внешних зависимостей и расписания.

docs/architecture/adr/README.md пополняется записями ADR-005..ADR-011.

C4 mmd-диаграмм в проекте нет — текстовое описание (по прецеденту ADR-004 §8).

13. Вывод

ET-008 — major-change на инфра-уровне:

  • Новый docker-compose service.
  • Новый файл БД.
  • Первые scheduled jobs (cron) на mva154.
  • Новые исходящие сетевые соединения с обязательными licensing-ADR.

Все элементы — расширение существующего стека (не новый stack). Реверсная процедура и rollback — однострочные операции.

Эскалация: лейбл arch:major-change выставлен на ADR-005 и ADR-007. Архитектурный approve обязателен перед merge.