# План: контейнер для приложения (Dockerfile и compose) ## Context Сейчас приложение ставится вручную: venv, два systemd/OpenRC-сервиса, ручное управление пользователем и правами. Контейнеры используются только для тестов (`Dockerfile.test`). Цель: запускать API и демон-сборщик одной командой `docker compose up -d`, с данными на томе, без root и с проверками состояния. Формат данных и API не меняются. Решение пользователя: порт 8000 публикуется наружу без TLS (как сейчас). Риск: токен `X-API-Key` идёт по HTTP открытым текстом, защита только файрволом; это фиксируется в README, TLS - отдельная доработка. ## Артефакты по правилам проекта (создаются при реализации) - `docs/plan-app-container.md` - копия этого плана (первым шагом) - `docs/summary-app-container.md` - итоги (в конце) - обновить `README.md` (раздел про Docker Compose, миграция существующих данных) ## Архитектура Один образ, два сервиса compose (`api`, `collector`) с общим томом `ripe_data`, смонтированным в `/data`. Сервисы по-прежнему общаются только файлами (`config.json`, `data.json`, `fqdn_data.json`, `status.json`, `*.lock`). ## Изменения ### 1. Код: настраиваемый каталог данных (минимальная правка) - `cidr_collector.py`: добавить `DATA_DIR = os.environ.get("RIPE_DATA_DIR", BASE_DIR)`; пути `CONFIG_FILE`, `DATA_FILE`, `FQDN_DATA_FILE`, `STATUS_FILE` строятся от `DATA_DIR`. Без переменной поведение прежнее (systemd/venv-установки не ломаются). - `collector_daemon.py`: `LOCK_FILE` строится от `cc.DATA_DIR` (сейчас `cc.BASE_DIR`). - Тесты не меняются (они подменяют атрибуты модуля). ### 2. `Dockerfile` (новый) - База `python:3.11-slim`; зависимости отдельным слоем из `requirements.txt` (`pip install --no-cache-dir`). - Копируются только рабочие файлы: `api_server.py`, `cidr_collector.py`, `collector_daemon.py`, `formatters.py`, `storage.py`, `healthcheck.py`. - Пользователь `ripe` (uid 10001), `/data` создаётся и принадлежит ему (именованный том при первом создании наследует владельца). - `ENV RIPE_DATA_DIR=/data PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1`, `VOLUME /data`. - `CMD` в exec-форме (сигналы доходят до процесса): по умолчанию `uvicorn api_server:app --host 0.0.0.0 --port 8000`; сборщик переопределяет команду в compose. ### 3. `healthcheck.py` (новый, маленький) - `python healthcheck.py api` - GET `http://127.0.0.1:8000/health`, успех при HTTP 200. - `python healthcheck.py collector` - читает `status.json` (`cc.STATUS_FILE`), успех, если `updated_at` моложе `cc.STATUS_STALE_AFTER` (переиспользуем константы и логику heartbeat). ### 4. `docker-compose.yml` (новый) - `api`: `build: .`, `ports: "8000:8000"`, `env_file: .env` (`RIPE_API_TOKEN`), `healthcheck` (api), `restart: unless-stopped`. - `collector`: тот же образ, `command: ["python", "collector_daemon.py"]`, `healthcheck` (collector), `stop_grace_period: 60s` (идущий сбор успевает завершиться по SIGTERM), токен не передаётся. - Оба: `volumes: ripe_data:/data`, `environment: TZ=${TZ:-UTC}` (расписание cron считается в этом поясе; в README отметить), усиление изоляции: `read_only: true`, `tmpfs: /tmp`, `cap_drop: [ALL]`, `security_opt: [no-new-privileges:true]`, ротация логов `json-file` (`max-size: 10m`, `max-file: 3`). - `volumes: ripe_data:`. - `.env.example` с `RIPE_API_TOKEN=change-me` и `TZ=UTC`; `.env` в `.gitignore` и `.dockerignore`. Если токен не задан, `POST` остаётся отключённым (503) - поведение fail closed сохраняется. ### 5. Документация и миграция (`README.md`) - Раздел «Docker Compose»: `cp .env.example .env`, генерация токена, `docker compose up -d`, `docker compose ps`, логи, обновление (`docker compose build && docker compose up -d`). - Миграция существующих данных в том: копирование `config.json`, `data.json`, `fqdn_data.json` во временный контейнер с `chown 10001`. - Предупреждения: порт 8000 без TLS - токен виден в сети, ограничить файрволом или поставить proxy; часовой пояс `TZ`; `google.com` в текущих данных не входит в конфигурацию и будет стареть по TTL. - Уточнить: systemd/OpenRC-установка остаётся рабочей альтернативой; `Dockerfile.test` используется только для тестов. - `.dockerignore`: добавить `.env`. ### 6. Тесты Новых автотестов не добавляем (минимум по правилам проекта): `healthcheck.py` и сборка проверяются в контейнерном сценарии ниже; 13 существующих тестов продолжают проходить (и в `Dockerfile.test`). ## Критичные файлы Новые: `Dockerfile`, `docker-compose.yml`, `healthcheck.py`, `.env.example`. Правки: `cidr_collector.py` (пути), `collector_daemon.py` (`LOCK_FILE`), `README.md`, `.gitignore`, `.dockerignore`. ## Проверка 1. `docker build -f Dockerfile.test ...` и запуск: 13 passed (регрессия после правки путей). 2. `docker compose build && docker compose up -d` с тестовым `.env` на копии данных (проект в каталоге scratchpad, реальные данные не трогаем); порт можно временно сменить переменной, чтобы не конфликтовать. 3. `docker compose ps`: оба сервиса `healthy` не позднее чем через ~1 минуту. 4. `curl /health`: `collector_alive: true`; `POST /asns` с токеном (201), без токена (401); `GET /addresses?format=nftables` отдаёт конфиг. 5. Данные переживают `docker compose down && up -d` (том), файлы в томе принадлежат uid 10001; `docker compose exec api id` - не root; запись вне `/data` невозможна (read-only rootfs). 6. `docker compose stop collector` завершается штатно (меньше `stop_grace_period`), остановка демона делает `/health` `degraded` через ~2 минуты. 7. Миграция: скопировать реальные `config.json`/`data.json`/`fqdn_data.json` (копии) в том по инструкции README, убедиться, что API видит те же адреса. 8. По окончании убрать тестовые контейнеры, тома и образы, созданные при проверке.