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

72 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Образ собирается локально и едет на сервер через docker save/load
- Дата: 2026-07-24
## Контекст
Заменяет [ADR-2026-06-13-docker-deploy](ADR-2026-06-13-docker-deploy.md).
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` это единицы
мегабайт — дёшево.