185 lines
15 KiB
Markdown
185 lines
15 KiB
Markdown
# Контейнерная поставка
|
||
|
||
Поставка для одного Docker host, одного backend и краткого простоя.
|
||
[Топология](containers.md#контейнерная-поставка) и
|
||
[ADR 015](../adr/015-single-server-sqlite-deployment.md) объясняют границы.
|
||
Production-комплект находится в отдельном [репозитории deploy](https://git.personal.ksv741.ru/WealthLedger/deploy).
|
||
Его README — единственная пошаговая инструкция установки, обновления и recovery;
|
||
[ADR 016](../adr/016-versioned-deployment-bundle.md) объясняет выбор релиза комплекта.
|
||
Development-команды ниже выполняются из workspace с checkout `frontend/` и `backend/`.
|
||
|
||
## Предпосылки
|
||
|
||
- Docker Engine с Compose plugin **не ниже 2.24.4** (`!override` в development),
|
||
доступ к registry и достаточно свободного диска для non-root UID 1000.
|
||
Опубликованные images собраны для Linux amd64 (см. README комплекта).
|
||
Другую архитектуру проверяют отдельно; multi-platform images не обещаны.
|
||
- Nexus Docker hosted repository `docker-hosted`, доступный через
|
||
`nexus.personal.ksv741.ru/docker-hosted/`. Docker Bearer Token Realm включён;
|
||
reverse proxy передаёт исходные host и HTTPS scheme. Адрес token realm
|
||
в `WWW-Authenticate` также должен начинаться с `https://`.
|
||
Deployment policy запрещает redeploy существующих release tags.
|
||
Runner label — `ubuntu-latest`, runner должен иметь Docker CLI, buildx и daemon.
|
||
Наличие label само по себе не доказывает Docker capability.
|
||
- В Actions каждого component repo нужны `NEXUS_USERNAME` и
|
||
`NEXUS_PASSWORD` пользователя `wealthledger-ci`. Эта же учётная запись
|
||
используется сервером для загрузки образов. Для публикации нужны
|
||
browse/read/add/edit на `docker-hosted`; фактически выданный набор прав
|
||
настраивается в Nexus. Built-in
|
||
workflow token не заменяет эту настройку. Отдельной read-only учётной записи
|
||
сервера нет: `wealthledger-ci` работает и как CI writer, и как server reader,
|
||
поэтому её пароль на сервере даёт право публикации в `docker-hosted`. Это
|
||
принятое владельцем решение; разделение ролей — возможное усиление.
|
||
- На сервере Caddy уже завершает TLS и передаёт весь домен на
|
||
`127.0.0.1:8080` (или выбранный frontend port). Отдельного `/api` upstream нет.
|
||
DNS, TLS, firewall и provisioning Caddy выполняются вне этого runbook.
|
||
|
||
Серверный login и доступ к images проверяются на целевом сервере.
|
||
Локальный smoke не доказывает серверные registry permissions.
|
||
|
||
## Development
|
||
|
||
Из workspace с исходниками обоих компонентов:
|
||
|
||
```bash
|
||
docker version
|
||
docker compose version
|
||
docker compose --env-file .env.development.example \
|
||
-f compose.yaml -f compose.development.yaml config --quiet
|
||
docker compose --env-file .env.development.example \
|
||
-f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180
|
||
```
|
||
|
||
Откройте `http://localhost:5173`. Vite отдаёт HMR и проксирует `/api/` без
|
||
rewrite; Nest работает в watch mode. Монтируются только source/public,
|
||
`node_modules` и generated files остаются в container filesystem. `.env`
|
||
с хоста не копируется. После изменения manifest, lockfile, tsconfig или Vite
|
||
config пересоберите соответствующий image. `up --build` обновляет Linux
|
||
dependencies без stale named volume для `node_modules`.
|
||
|
||
Для другого порта скопируйте `.env.development.example` в игнорируемый
|
||
`.env.development` и согласованно поменяйте `FRONTEND_PORT`/`FRONTEND_ORIGIN`.
|
||
Пример содержит только вымышленные local secrets; server secrets сюда не входят.
|
||
Поля image/data directory нужны для интерполяции base Compose, но полностью
|
||
заменяются development override. В итоговом config нет production data path.
|
||
|
||
Project `wealthledger-dev` хранит SQLite в `development-data`. Обычные
|
||
`restart` и `down` сохраняют его. Не применяйте `down -v` к данным, которые
|
||
хотите сохранить. Не используйте production project name для development.
|
||
|
||
## Публикация компонентных images
|
||
|
||
Штатный release process каждого repo создаёт защищённый `vX.Y.Z` после
|
||
проверенного release commit. `image.yml` проверяет tag против package/lock
|
||
version, затем выполняет `npm ci`, `npm run verify`; backend дополнительно
|
||
выполняет `npm run test:e2e`. У каждого image OCI version/revision labels.
|
||
|
||
Images публикуются как `nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:X.Y.Z`
|
||
и `.../backend:X.Y.Z`. `latest` не создаётся.
|
||
Workflow отказывается перезаписывать существующие release tags;
|
||
защита release tags и запрет параллельных publishers обязательны. Если одна
|
||
публикация уже состоялась, не удаляйте tag для повторного запуска: проверьте
|
||
опубликованный image и завершите проверку существующего artifact отдельно.
|
||
|
||
Выберите опубликованные version tags, выполните pull и сравните OCI revision
|
||
с release commit. Выполните smoke с disposable data/config до первого server
|
||
rollout. В `release.env` комплекта хранится пара полных image references, например
|
||
`nexus.personal.ksv741.ru/docker-hosted/wealthledger/frontend:1.1.0` и
|
||
`.../backend:2.1.0`. Сохраните versions/revisions
|
||
в deployment record. Release tags не должны переназначаться другим images.
|
||
|
||
## Перенос существующих images из Gitea
|
||
|
||
До переключения CI и server references проверьте HTTPS token realm и настройте
|
||
пользователя `wealthledger-ci`. В Actions добавьте `NEXUS_USERNAME`/`NEXUS_PASSWORD`; прежние
|
||
`REGISTRY_USERNAME`/`REGISTRY_TOKEN` сохраните для отката старого workflow.
|
||
Пароли вводятся интерактивно или через `--password-stdin`, не через аргументы.
|
||
|
||
Прежние images лежат в [Gitea packages](https://git.personal.ksv741.ru/WealthLedger/-/packages)
|
||
(`git.personal.ksv741.ru/wealthledger/{frontend,backend}`); новые публикуются
|
||
только в Nexus. Скопируйте каждый опубликованный release без пересборки. Например, для `0.2.0`
|
||
с установленным Skopeo и доступом чтения Gitea/записи Nexus:
|
||
|
||
```bash
|
||
skopeo login git.personal.ksv741.ru
|
||
skopeo login nexus.personal.ksv741.ru
|
||
for component in frontend backend; do
|
||
skopeo copy --all --preserve-digests \
|
||
"docker://git.personal.ksv741.ru/wealthledger/$component:0.2.0" \
|
||
"docker://nexus.personal.ksv741.ru/docker-hosted/wealthledger/$component:0.2.0" || exit 1
|
||
done
|
||
```
|
||
|
||
`--all` переносит весь image index, включая платформы и вложенные attestation
|
||
manifests; `--preserve-digests` прерывает перенос при изменении digest.
|
||
До копирования проверьте наличие destination tag: если он уже существует,
|
||
сравните исходный и целевой index digest и вложенные manifests. Совпадающий
|
||
artifact повторно не публикуйте; несовпадение требует расследования, не удаления
|
||
или перезаписи tag. Частичный перенос не считается завершённым.
|
||
|
||
После копирования сравните digests через `skopeo inspect --raw` и SHA-256,
|
||
выполните `docker pull` обоих images пользователем `wealthledger-ci` на сервере приложения,
|
||
проверьте OCI revision и container smoke на disposable data.
|
||
Серверные шаги (login, pull, smoke) в репозитории не подтверждены и
|
||
выполняются оператором. Только после них меняйте
|
||
production image references и запускайте обычную
|
||
последовательность обновления ниже. Старые Gitea packages и references сохраните:
|
||
откат registry возвращает прежние адреса тех же artifacts, не восстанавливает
|
||
SQLite и не меняет encryption keys. Не переносите Git release tags и не
|
||
запускайте заново старый tag workflow для копирования существующего image.
|
||
|
||
## Первая установка
|
||
|
||
На подготовленном сервере оператор клонирует `deploy`, настраивает внешний
|
||
HTTPS и registry login, размещает server env, сертификат T-Bank и data directory
|
||
вне checkout. Для новой пустой базы команда вызывается с `--init`.
|
||
Для действующей установки сохраняются прежние данные и ключи, а первый переход
|
||
выполняется как полное обновление с backup. Отсутствующая БД без `--init` — ошибка.
|
||
Точные команды и разрешения каталогов приведены в README комплекта.
|
||
|
||
Git-тег `deploy` закрепляет пару images через `release.env`. Версия комплекта
|
||
не обязана совпадать с версиями компонентов. Публикация image не выпускает
|
||
комплект и не обновляет сервер автоматически. Команда получает выбранный тег,
|
||
загружает images до простоя и использует весь snapshot этого commit.
|
||
|
||
## Backup при остановленном backend
|
||
|
||
Сначала останавливаются писатели, затем копируется весь каталог данных,
|
||
включая возможные WAL/SHM. Только успешная копия допускает миграции. Локальные
|
||
backups находятся рядом с data directory, вне checkout, и автоматически
|
||
не удаляются. Незавершённая копия помечается `.partial-` и не подходит для restore.
|
||
Ключ шифрования хранится отдельно. Локальная копия не защищает от потери диска.
|
||
|
||
## Единый цикл обновления
|
||
|
||
Каждое обновление, включая повтор того же тега и изменение только frontend,
|
||
выполняет stop → backup → migration → readiness. Сравнение hashes контейнеров
|
||
и конфигурации не выполняется. Приняты короткий простой API и дополнительный
|
||
backup; во время простоя возможен 502, вкладкам может потребоваться reload.
|
||
Ошибка migration блокирует старт, ошибка готовности останавливает новый backend;
|
||
ошибка frontend не останавливает исправный backend. Успех фиксируется только
|
||
после проверок запуска обоих компонентов.
|
||
|
||
Одна file lock защищает установку от параллельных команд. Маркер незавершённого
|
||
обновления блокирует повтор до разбора оператором. Он содержит этап, версии
|
||
и путь к backup, но не секреты и не копию данных. После обновления нужен smoke
|
||
через HTTPS: healthcheck сам по себе не доказывает работу Secure refresh-cookie.
|
||
|
||
## Откат image
|
||
|
||
Откат версии и восстановление базы — разные операции. Прежний backend можно
|
||
запускать только после проверки совместимости с текущей схемой. Автоматического
|
||
rollback нет; migration service при ручном image rollback не запускается.
|
||
Порядок ручных Compose-команд и проверки recovery находится в README комплекта.
|
||
|
||
## Explicit restore
|
||
|
||
Оператор выбирает проверенную копию и явно принимает потерю всех более новых
|
||
записей. Сначала восстановление проверяется на isolated данных с прежним ключом
|
||
и совместимым image. На целевой установке останавливаются писатели, текущий
|
||
каталог сохраняется под новым именем, затем восстанавливается выбранная копия.
|
||
Backend пересоздаётся, чтобы заново подключить заменённый bind-каталог:
|
||
простой stop/start может сохранить mount прежних данных.
|
||
Проверяются целостность БД, auth/cards и расшифровка подключения. Только после
|
||
подтверждённого recovery можно снять маркер незавершённой операции.
|