Files
jellybit/docs/adr/ADR-2026-07-24-local-image-build.md
T
av 08bef2cac0 deploy: переход на локальную сборку образа и доставку docker save/load
- task image принимает BUILD_ID и тегает <app>:$BUILD_ID (контракт роли app_image в umbar)
- добавлен ADR-2026-07-24-local-image-build, старый docker-deploy помечен superseded
- README/architecture/roadmap/Dockerfile обновлены под новую схему (без сборки на сервере)
2026-07-24 18:33:51 +03:00

5.6 KiB
Raw Blame History

Образ собирается локально и едет на сервер через docker save/load

  • Дата: 2026-07-24

Контекст

Заменяет ADR-2026-06-13-docker-deploy.

Docker как единица деплоя и распределение ответственности (Dockerfile — упаковка — живёт в jellybit; оркестрация — в umbar) остаются в силе. Пересматривается только как образ попадает на сервер.

Прежняя схема собирала статический бинарь на control-хосте, копировала на сервер бинарь + Dockerfile и делала docker build на месте. У этого два неудобства. Во-первых, на сервере остаётся шаг сборки: пусть дешёвый на Intel N150, но результат косвенно завязан на состояние сервера (его docker, его кэш), а не только на исходник. Во-вторых, схема одноразовая — она была вписана в playbook-jellybit.yml и не переиспользовалась. Появление второго такого же приложения (trackers) потребовало вынести доставку образа в общую ansible-роль app_image и заодно унифицировать контракт с приложением.

Рассмотренные варианты

  • Оставить сборку на сервере — прежнее решение. Держит на сервере шаг docker build и делает результат зависимым от состояния сервера; переиспользовать без копипасты плейбука неудобно.
  • Реестр (CI пушит образ, сервер тянет) — каноничнее, но в домашней лаборатории это лишний реестр и пайплайн ради одного узла. Отвергнуто по той же причине, что и в исходной ADR.
  • Собрать полный образ локально и доставить его docker save/load — выбрано: сборка целиком на control-хосте, сервер — только получатель, и реестр не нужен.

Решение

Доставку образа ведёт переиспользуемая роль app_image в umbar. Схема:

  • Контракт с приложением. Приложение реализует команду task image: получает BUILD_ID из окружения и собирает полный образ <app>:$BUILD_ID. Без BUILD_ID собирается <app>:dev — обратная совместимость для локальной работы. Приложение полностью владеет тем, как собирается его образ (Dockerfile — в репозитории приложения).
  • Сборка локальна. Роль генерит случайный BUILD_ID и гоняет с ним task image на control-хосте → образ <app>:$BUILD_ID. На сервере Go-тулчейн и docker build больше не нужны.
  • Тег = BUILD_ID. Deploy-тег — тот самый случайный BUILD_ID. Дедупликации по содержимому нет: тег нов на каждый прогон. Content-адресацию (тег из хеша слоёв/конфига образа) рассматривали и отвергли — на сценариях ручного нечастого деплоя выгода от неё не окупала сложности.
  • Доставка без реестра. docker save → copy tar → docker load. Каждый деплой везёт образ и пересоздаёт контейнер.
  • Уборка. Старые образы на сервере (тег каждого прошлого деплоя) подчищает docker image prune -af по крону (playbook-system.yml).

Последствия

  • + Сервер — только получатель образа: без Go-тулчейна и без шага docker build.
  • + Сборка целиком на control-хосте — воспроизводимее; состояние сервера на результат не влияет.
  • + Реестр по-прежнему не нужен — доставка дешёвая (save/load tar).
  • + Механизм общий (роль app_image), а не вписан в один плейбук — им же доставляется trackers.
  • - Дедупликации нет: тег = случайный BUILD_ID, поэтому каждый деплой везёт образ и пересоздаёт контейнер, даже если ничего не менялось. Для ручного нечастого деплоя это осознанный размен — простота роли важнее экономии одного рестарта.
  • - save/load везёт весь образ (базовый слой + бинарь), а не только бинарь, как в прежней схеме. Для distroless/static это единицы мегабайт — дёшево.