diff --git a/docs/adr/ADR-2026-07-25-local-image-build.md b/docs/adr/ADR-2026-07-25-local-image-build.md new file mode 100644 index 0000000..a829542 --- /dev/null +++ b/docs/adr/ADR-2026-07-25-local-image-build.md @@ -0,0 +1,78 @@ +# Локальная сборка образов вместо Yandex Container Registry + +- Дата: 2026-07-25 + +## Контекст + +Три приложения собственной разработки — homepage, transcriber, remembos — +деплоились через Yandex Container Registry. Схема у всех одинаковая: образ +собирается локально, пушится в `cr.yandex/...` отдельным плейбуком +(`playbook-*-registry.yml`), плейбук деплоя делает `docker_login` с +`yc_oauth_token`, а compose тянет образ по registry-пути с тегом, зашитым в git. + +Схема тянула за собой три проблемы. + +**Секрет несопоставимого радиуса.** Ради одного `docker login` в vault лежал +OAuth-токен всего аккаунта Yandex Cloud — реестр, виртуалки, биллинг. 24 июля он +вместе с ещё 52 переменными ушёл открытым текстом в транскрипт: цена одной такой +утечки — весь аккаунт, а не один сервис. + +**Разнесённый деплой.** Сборка жила в `task deploy` репозитория приложения, +который сам звал `ansible-playbook`. У transcriber этот таск дополнительно правил +`vars/transcriber.images.yml` в чужом репозитории через `yq` и делал там `git +commit` с новым тегом. У remembos registry-плейбука не было вовсе — образ пушился +руками, а в compose стоял тег `v0.2.0`, вбитый в git. У homepage тег был +плавающий `:latest`, то есть ночной `docker image prune` мог подтянуть другое +содержимое без возможности отката. + +**Автоматизировать сборку пока нечем.** Стабильного онлайн-раннера для +workflow gitea у нас нет (отдельная задача — раннер on-demand), так что уехать в +CI сборки не могут. Плюс после переезда сервера в Timeweb реестр остался +единственной ниткой, тянущейся в Yandex Cloud. + +## Решение + +Приложения переведены на общую роль `app_image` (канон — +`~/projects/private/ansible-roles`, в репозитории лежит завендоренная копия в +`roles/`, синхронизация — `inv roles-pull`/`roles-push`). Роль: + +1. генерит случайный `BUILD_ID` — он же тег образа; +2. запускает в директории приложения его собственную команду сборки (контракт — + `task image`, `BUILD_ID` приходит через окружение), приложение обязано собрать + полный образ и затегать его `:$BUILD_ID`; +3. печатает размер архива, везёт его на сервер через `docker save` → `copy` → + `docker load`; +4. отдаёт факт `app_image_tag`, которым compose-шаблон подставляет образ + (`pull_policy: never` — в реестре образа нет). + +Два мотива за этим. Первый — **упростить деплой**, пока нет стабильного +онлайн-раннера: теперь `inv pl -- ` собирает, доставляет и запускает +приложение одной командой, отдельного ритуала в репозитории приложения не +осталось. Второй — **сократить набор секретов**: реестр и OAuth-токен выпали из +схемы, `yc_oauth_token`, `yc_container_registry` и +`yc_container_registry_repository` удалены из vault (коммит `6a0d786`). + +Registry-плейбуки и `vars/transcriber.images.yml` удалены; у remembos заодно +снесены gitea-workflow с goreleaser — они существовали только ради пуша образа в +`cr.yandex`. + +## Последствия + +- `+` Из деплоя ушли внешний реестр и токен на весь аккаунт облака; в vault на + три переменные меньше. +- `+` Деплой любого из трёх приложений — одна команда `inv pl -- `; + «правка тега в чужом репозитории + коммит» как шаг деплоя исчезла. +- `+` Тег каждого деплоя уникален, плавающего `:latest` больше нет. Откат — + повторный деплой нужного коммита приложения. +- `-` Дедупликации по слоям нет: каждый деплой везёт полный архив. Замеры + `docker save`: homepage — 61 МБ, transcriber — 73 МБ, remembos — 21 МБ. Для + ручного нечастого деплоя размен принят осознанно. +- `-` Для деплоя нужен ноутбук с docker и тулчейнами: с чужой машины + задеплоить нельзя, а `playbook-all-applications.yml` теперь собирает три + образа за прогон. +- `-` На сервере копятся образы от каждого прогона; чистит их ночной + `docker image prune -af` (см. `playbook-system.yml`). +- Контракт для новых приложений: чтобы деплоиться этой ролью, приложение должно + иметь `task image`, тегающий образ из `$BUILD_ID`. +- Осталось: `buckland/playbook-trackers.yml` всё ещё ходит в `cr.yandex` со своей + копией токена в собственном vault — пока он жив, отзывать токен в облаке рано. diff --git a/docs/adr/README.md b/docs/adr/README.md index b4940f9..285e538 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -63,6 +63,7 @@ | Дата | Запись | Статус | | ---------- | ---------------------------------------------------------------------------------------------- | ------ | +| 2026-07-25 | [Локальная сборка образов вместо Yandex Container Registry](ADR-2026-07-25-local-image-build.md) | — | | 2026-06-22 | [Разнесение restic-операций на фазы под Intelligent Tiering](ADR-2026-06-22-restic-intelligent-tiering-phases.md) | — | | 2026-05-23 | [Переезд сервера с Yandex Cloud на Timeweb VPS](ADR-2026-05-23-migrate-to-timeweb.md) | — | | 2026-04-04 | [Apprise как шлюз уведомлений](ADR-2026-04-04-apprise-notifications.md) | — |