Постфактум по сегодняшней работе: почему ушли от cr.yandex к роли app_image (упрощение деплоя без раннера + сокращение секретов) и чем за это платим.
6.1 KiB
Локальная сборка образов вместо 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). Роль:
- генерит случайный
BUILD_ID— он же тег образа; - запускает в директории приложения его собственную команду сборки (контракт —
task image,BUILD_IDприходит через окружение), приложение обязано собрать полный образ и затегать его<app_image_name>:$BUILD_ID; - печатает размер архива, везёт его на сервер через
docker save→copy→docker load; - отдаёт факт
app_image_tag, которым compose-шаблон подставляет образ (pull_policy: never— в реестре образа нет).
Два мотива за этим. Первый — упростить деплой, пока нет стабильного
онлайн-раннера: теперь inv pl -- <app> собирает, доставляет и запускает
приложение одной командой, отдельного ритуала в репозитории приложения не
осталось. Второй — сократить набор секретов: реестр и 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 -- <app>; «правка тега в чужом репозитории + коммит» как шаг деплоя исчезла.+Тег каждого деплоя уникален, плавающего: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 — пока он жив, отзывать токен в облаке рано.