diff --git a/dev/.env.example b/dev/.env.example index 446ee8a..271708d 100644 --- a/dev/.env.example +++ b/dev/.env.example @@ -22,6 +22,13 @@ MINIO_API_PORT=9000 MINIO_CONSOLE_PORT=9001 MINIO_BUCKET=arm-ka +# Порты хоста сервисов профиля `services` (docker compose --profile services ...). Внутри контейнера каждый сервис +# слушает 8000; эти значения меняют только порт на хосте. Настройки сервисов в контейнерах compose задаёт сам +# (docker-compose.yml, x-service-env), блок ниже на них не влияет. +ARM_CORE_PORT=8000 +TABULAR_PARSER_PORT=8001 +SCAN_GATEWAY_PORT=8002 + # --- Настройки сервисов при запуске из venv против compose по localhost (формат шаблона команды) --- # Значения согласованы с блоком выше: при смене порта/пароля/бакета меняйте обе части. # Если порт хоста изменён, поправьте и порты ниже. diff --git a/dev/README.md b/dev/README.md index e6553eb..8d2eccb 100644 --- a/dev/README.md +++ b/dev/README.md @@ -1,17 +1,20 @@ # Окружение разработки АРМ КА (`docs/dev/`) Локальная инфраструктура workspace одной командой: PostgreSQL 16, NATS 2.10 без JetStream и MinIO с бакетом `arm-ka`. - -**Статус (фаза 1):** в compose сейчас только инфраструктура (`postgres`, `nats`, `minio`, `minio-init`). Профиль -`services` (контейнеры `arm-core`, `tabular-parser`, `scan-gateway`, `scan-emulator`) **планируется** — добавляется позже -(задача 5.3), здесь не описан. До этого сервисы запускаются из venv против этой инфраструктуры (см. ниже). +Профиль `services` добавляет к ней сервисы системы, собранные из соседних репозиториев workspace: `arm-core`, +`tabular-parser`, `scan-gateway` и эмулятор ИС Сканирование `scan-emulator` (см. «Профиль `services`»). Без профиля +поднимается только инфраструктура, а сервисы можно запускать из venv (см. «Запуск сервисов из venv»). ## Предварительные требования - Docker и compose v2 (проверено на docker 28 / compose 2.34). - `uv` и Python 3.11 — для запуска сервисов из venv (версия Python — как на платформе). -- Свободные порты хоста (по умолчанию доступны только с `127.0.0.1`, см. `BIND_ADDR`): 5432 (PostgreSQL), 4222 и 8222 (NATS и его мониторинг), 9000 и 9001 (MinIO API и консоль). +- Свободные порты хоста (по умолчанию доступны только с `127.0.0.1`, см. `BIND_ADDR`): 5432 (PostgreSQL), 4222 и 8222 (NATS и его мониторинг), 9000 и 9001 (MinIO API и консоль); + для профиля `services` ещё 8000 (`arm-core`), 8001 (`tabular-parser`), 8002 (`scan-gateway`). Занятые порты меняются в `.env` (см. «Типичные проблемы»). +- Для профиля `services`: соседние каталоги `../../arm-core`, `../../tabular-parser`, `../../scan-gateway` и + `../../fixtures` относительно `docs/dev/` (в workspace и в зеркальном worktree `.wt/<срез>/` это одни и те же соседи + `docs`). Сборка образов ставит зависимости из PyPI (строки `raisa_*` отфильтрованы, Nexus не нужен). ## Запуск инфраструктуры @@ -46,6 +49,91 @@ docker compose run --rm --entrypoint sh minio-init -c 'mc alias set local http:/ Консоль MinIO — (по умолчанию `minioadmin` / `minioadmin`); S3 API — `localhost:9000`. Схема БД (`dkb`) создаётся миграциями `arm-core`, не compose. +## Профиль `services` + +Сервисы системы в контейнерах против той же инфраструктуры. Образы собираются из `Dockerfile` в корне соседних +репозиториев (`../../arm-core`, `../../tabular-parser`, `../../scan-gateway`) и получают теги `arm-core:dev`, +`tabular-parser:dev`, `scan-gateway:dev`. Настройки сервисов задаёт сам compose (переменные `OPENBAO__*` шаблона с +адресами внутри сети compose — `postgres:5432`, `nats:4222`, `minio:9000`, `DEBUG=true`); второй блок `.env` и +`api/.env` сервисов на контейнеры не влияют. + +Команды — из каталога `docs/dev/`: + +```bash +cp .env.example .env # если ещё не сделано +docker compose --profile services up -d --build # сборка образов и запуск; первая сборка — около минуты +docker compose --profile services up -d --wait arm-core tabular-parser scan-gateway scan-emulator # ждать healthy +``` + +| Сервис | Порт хоста (`.env`) | Порт в контейнере | Что делает | +|---|---|---|---| +| `arm-core` | 8000 (`ARM_CORE_PORT`) | 8000 | ядро: API, outbox/inbox `recognition_job`, подписка `recognition.result` | +| `tabular-parser` | 8001 (`TABULAR_PARSER_PORT`) | 8000 | подписка `recognition.tabular.request` (queue group `tabular-parser`) | +| `scan-gateway` | 8002 (`SCAN_GATEWAY_PORT`) | 8000 | подписка `recognition.image.request` (queue group `scan-gateway`), горячая папка — том `hotfolder` | +| `scan-emulator` | — | — | эмулятор ИС Сканирование: следит за `Input/` тома `hotfolder`, ответы — из `../../fixtures/ocr` (только чтение) | +| `arm-core-migrate` | — | — | одноразовый: `alembic upgrade head` и `python -m scripts.seed --dev`, затем `arm-core` стартует | + +Все три сервиса внутри контейнера слушают **8000** (значение по умолчанию `SERVER__PORT`); различаются только порты +хоста. Сервис, запущенный из venv, по умолчанию тоже слушает 8000 на хосте — он конфликтует с контейнером `arm-core` +(остановите контейнер или задайте `SERVER__PORT` в `api/.env`). + +Порядок старта задан `depends_on`: `arm-core-migrate` ждёт healthy `postgres`; `arm-core` — `postgres`, `nats`, `minio`, +успешного завершения `minio-init` и `arm-core-migrate`; `tabular-parser` и `scan-gateway` — `nats`, `minio` и +`minio-init`. Одноразовые `minio-init` и `arm-core-migrate` выполняются при каждом `up` профиля (миграции и seed +идемпотентны: повторный seed пишет `0 / 0 / N` — без изменений) и остаются в `ps -a` как `Exited (0)`. + +Проверка (фактический вывод на чистых томах, 2026-10-04, сокращён): + +```bash +docker compose --profile services ps -a +# arm-core Up (healthy) 127.0.0.1:8000->8000/tcp +# arm-core-migrate Exited (0) +# minio-init Exited (0) +# scan-emulator Up 8000/tcp (healthcheck нет: процесс без HTTP) +# scan-gateway Up (healthy) 127.0.0.1:8002->8000/tcp +# tabular-parser Up (healthy) 127.0.0.1:8001->8000/tcp +curl -s localhost:8000/healthcheck # {"uptime":"…","version":"1.0.0","nats":"ok"} +curl -s localhost:8001/healthcheck # {"uptime":"…","version":"1.0.0","nats":"ok"} +curl -s localhost:8002/healthcheck # {"uptime":"…","version":"1.0.0","nats":"ok","hotfolder":"ok"} +docker compose --profile services logs arm-core-migrate # Running upgrade -> 0001_dbml_v5_13 …; Seed применён … +docker compose --profile services logs scan-emulator # … корень /data/hotfolder …; Фикстуры: /fixtures/ocr, фикстур: 2 +``` + +`/healthcheck` всех трёх сервисов **всегда отвечает 200**, поэтому healthcheck контейнеров читает поля ответа: +`nats = ok` (и `hotfolder = ok` у `scan-gateway`). Статус `healthy` означает, что сервис подключён к NATS. + +Dev-токен `arm-core` (в compose включён dev-эмитент, `AUTH_DEV_ISSUER=true`, `DEBUG=true`; пользователи — из +`seed --dev`, см. `../../arm-core/README.md`): + +```bash +curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' -d '{"email":"ka_rf@armka.local"}' +curl -s localhost:8000/api/v1/me -H "Authorization: Bearer " # email, роли KA_RF, права +``` + +Образ `arm-core` содержит только бэкенд: фронт (`web/`) в `Dockerfile` не собирается, `GET /` отвечает 404. UI — +`npm run dev` из `../../arm-core/web` (см. README `arm-core`). + +**Ожидание готовности и коды возврата `--wait`** (compose 2.34, проверено 2026-10-04): + +| Команда | Код | Комментарий | +|---|---|---| +| `docker compose --profile services up -d --wait arm-core tabular-parser scan-gateway scan-emulator` | 0 | рекомендуемая форма | +| `docker compose --profile services up -d --wait scan-gateway scan-emulator` | 0 | и на чистых томах, и на работающем окружении: `minio-init` поднимается как зависимость `scan-gateway` (`service_completed_successfully`) и его выход с кодом 0 сбоем не считается | +| `docker compose --profile services up -d --wait` (без списка) | 0 | все одноразовые контейнеры профиля — зависимости с `service_completed_successfully` | +| `docker compose up -d --wait` (без профиля и списка) | 1 | `container arm-ka-minio-init-1 exited (0)`: без профиля у `minio-init` нет зависимых сервисов | + +Для скриптов указывайте долгоживущие сервисы явно — форма не зависит от состава профиля. + +**Пересборка после изменения кода сервиса:** `docker compose --profile services up -d --build` (или +`up -d --build <сервис>`). Образ `arm-core:dev` собирает только сервис `arm-core`, образ `scan-gateway:dev` — только +`scan-gateway`; `arm-core-migrate` и `scan-emulator` используют готовый тег (`pull_policy: never`): две сборки одного +тега давали разные ID образа, и каждый следующий `up` пересоздавал контейнеры. Поэтому `arm-core-migrate` или +`scan-emulator` в одиночку (`up -d scan-emulator`) на машине без образа не стартуют — сначала соберите его +(`docker compose --profile services build scan-gateway`) или поднимайте профиль целиком. + +**Остановка и сброс профиля** — только с `--profile services`: без него compose не видит контейнеры профиля +(см. «Сброс окружения»). + ## Переменные `.env` Файл читают `docker compose` (из этого каталога) и, вторым блоком, сервисы при запуске из venv. Значения по умолчанию — @@ -67,6 +155,9 @@ docker compose run --rm --entrypoint sh minio-init -c 'mc alias set local http:/ | `MINIO_API_PORT` | `9000` | порт S3 API на хосте | | `MINIO_CONSOLE_PORT` | `9001` | порт консоли MinIO на хосте | | `MINIO_BUCKET` | `arm-ka` | единственный бакет, создаётся `minio-init` | +| `ARM_CORE_PORT` | `8000` | порт хоста `arm-core` (профиль `services`; в контейнере 8000) | +| `TABULAR_PARSER_PORT` | `8001` | порт хоста `tabular-parser` (профиль `services`; в контейнере 8000) | +| `SCAN_GATEWAY_PORT` | `8002` | порт хоста `scan-gateway` (профиль `services`; в контейнере 8000) | Блок сервисов (запуск из venv против compose по `localhost`, формат шаблона команды): @@ -92,10 +183,15 @@ docker compose run --rm --entrypoint sh minio-init -c 'mc alias set local http:/ ## Сброс окружения ```bash -docker compose down # останавливает контейнеры; тома postgres-data и minio-data сохраняются -docker compose down -v # полный сброс: удаляет тома postgres-data и minio-data (БД и бакет — с нуля) +docker compose --profile services down # останавливает все контейнеры проекта; тома сохраняются +docker compose --profile services down -v # полный сброс: тома postgres-data, minio-data и hotfolder (БД, бакет, горячая папка — с нуля) ``` +Без `--profile services` команда `down` (с `-v` или без) **не трогает** контейнеры профиля: проверено — после +`docker compose down -v` контейнеры `arm-core`/`tabular-parser`/`scan-gateway`/`scan-emulator` продолжают работать, +том `hotfolder` и сеть `arm-ka_default` остаются (`Network arm-ka_default Resource is still in use`). Если профиль не +поднимался, `docker compose down [-v]` достаточно. + Имя проекта compose фиксировано (`name: arm-ka`), поэтому тома общие для любого worktree (`.wt/<срез>/docs/dev`): сброс из одного worktree затрагивает все. @@ -120,10 +216,12 @@ cd api && .venv/bin/python run_server.py # сервис подключаетс curl -s localhost:<порт сервиса>/healthcheck ``` -Порт сервиса задаётся его настройками; в профиле `services` по spec это 8000 (`arm-core`), 8001 (`tabular-parser`), -8002 (`scan-gateway`). Для `arm-core` в ответе `/healthcheck` ожидаются `version` и `nats = ok` — при `NATS_ENABLED=true` -(задан в `.env.example`; без него `nats = off`). Для `tabular-parser` и `scan-gateway` ожидаемые поля и переменные -зависят от их настроек — будет уточнено в задачах 5.x. +Из venv каждый сервис слушает порт из своих настроек (`SERVER__PORT`, по умолчанию **8000** у всех трёх) — порты +хоста 8001/8002 относятся только к контейнерам профиля `services` (см. таблицу портов выше). Два сервиса из venv +одновременно или сервис из venv рядом с профилем `services` требуют разных `SERVER__PORT`. Для `arm-core` в ответе +`/healthcheck` ожидаются `version` и `nats = ok` — при `NATS_ENABLED=true` (задан в `.env.example`; без него +`nats = off`). Поля `/healthcheck` в контейнерах: `tabular-parser` — `uptime`, `version`, `nats`; `scan-gateway` — ещё +`hotfolder` (шлюзу нужен `SCAN_HOTFOLDER_PATH`). Переменные сервисов из venv уточняются в задаче 5.4. ## Интеграционные тесты сервисов против compose (черновик) @@ -137,8 +235,13 @@ curl -s localhost:<порт сервиса>/healthcheck Интеграционные тесты **требуют поднятой инфраструктуры**: сначала `docker compose up -d && docker compose up -d --wait postgres nats minio`, затем `make test-integration` (или `make check-full`) в репозитории сервиса. Для `scan-gateway` -эмулятор ИС Сканирование поднимается в процессе интеграционного теста (отдельный контейнер появится вместе с профилем -`services`). Общее описание проверок — [`checks.md`](checks.md). +эмулятор ИС Сканирование поднимается в процессе интеграционного теста; контейнер `scan-emulator` профиля `services` +тестами не используется. Общее описание проверок — [`checks.md`](checks.md). + +> **Не запускайте интеграционные тесты при поднятом профиле `services`** (не проверялось, вывод из устройства обмена): +> контейнеры подписаны на те же subject'ы общего NATS в тех же queue group (`arm-core` на `recognition.result`, +> `tabular-parser` и `scan-gateway` на свои `*.request`), поэтому NATS может отдать сообщение теста контейнеру, а не +> экземпляру из теста. Перед тестами: `docker compose --profile services stop arm-core tabular-parser scan-gateway scan-emulator`. ## Проверка контракта @@ -162,7 +265,13 @@ postgres nats minio`, затем `make test-integration` (или `make check-ful ## Типичные проблемы - **Порт занят** (`address already in use`): измените `*_PORT` в `.env` и одновременно соответствующий порт в - `OPENBAO__*` (`DATABASE_URL`, `MINIO_URL`, `NATS_URL`); перезапустите `docker compose up -d`. + `OPENBAO__*` (`DATABASE_URL`, `MINIO_URL`, `NATS_URL`); перезапустите `docker compose up -d`. Для портов сервисов + (`ARM_CORE_PORT`, `TABULAR_PARSER_PORT`, `SCAN_GATEWAY_PORT`) достаточно `.env`. +- **Сервис профиля не становится healthy:** `docker compose --profile services logs <сервис>`. `nats = down` — + NATS недоступен; `hotfolder = down` у `scan-gateway` — каталог `SCAN_HOTFOLDER_PATH` (том `hotfolder`) недоступен. `arm-core` не стартует, пока + `arm-core-migrate` не завершился с кодом 0: `docker compose --profile services logs arm-core-migrate`. +- **`No such image: arm-core:dev` / `scan-gateway:dev`** при запуске `arm-core-migrate` или `scan-emulator` по + отдельности: образ ещё не собран — `docker compose --profile services build` (см. «Профиль `services`»). - **`minio-init` завис или завершился с ошибкой:** `docker compose logs minio-init`; чаще всего `minio` не стал healthy — `docker compose logs minio`. Повторный `docker compose up -d` идемпотентен. - **Остаточные тома от прошлых запусков** (старый пароль БД, бакет не тот): `docker compose down -v` и запуск заново.