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 обновлены под новую схему (без сборки на сервере)
This commit is contained in:
+5
-2
@@ -1,5 +1,8 @@
|
|||||||
# Упаковка готового статического бинаря в минимальный образ.
|
# Упаковка готового статического бинаря в минимальный образ. Образ целиком
|
||||||
# Бинарь собирается снаружи (см. docs/adr/ADR-2026-06-13-docker-deploy.md):
|
# собирается локально на control-хосте (`task image`) и едет на сервер через
|
||||||
|
# docker save/load — роль app_image в umbar (см.
|
||||||
|
# docs/adr/ADR-2026-07-24-local-image-build.md).
|
||||||
|
# Бинарь собирается снаружи:
|
||||||
# CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o jellybit ./cmd/jellybit
|
# CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o jellybit ./cmd/jellybit
|
||||||
# distroless/static несёт CA-сертификаты (HTTPS к LLM/TMDB). Пользователь
|
# distroless/static несёт CA-сертификаты (HTTPS к LLM/TMDB). Пользователь
|
||||||
# задаётся в compose (user: "1000:1000").
|
# задаётся в compose (user: "1000:1000").
|
||||||
|
|||||||
@@ -114,9 +114,11 @@ jellybit recognize <infohash> --dry-run [--context "..."] --config ./config.toml
|
|||||||
|
|
||||||
Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический
|
Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический
|
||||||
бинарь (`task build`) и `Dockerfile` (упаковка в `distroless/static`). Образ
|
бинарь (`task build`) и `Dockerfile` (упаковка в `distroless/static`). Образ
|
||||||
собирается **на сервере** из доставленного бинаря, поэтому Go-тулчейн на
|
собирается целиком **локально** на control-хосте (`task image`) и едет на
|
||||||
сервере не нужен. В distroless нет shell/curl, поэтому HEALTHCHECK зовёт сам
|
сервер через `docker save`/`load` (роль `app_image` в umbar), поэтому
|
||||||
бинарь: `jellybit healthcheck` (GET `/healthz` по порту из конфига, exit 0/1).
|
Go-тулчейн и `docker build` на сервере не нужны. В distroless нет shell/curl,
|
||||||
|
поэтому HEALTHCHECK зовёт сам бинарь: `jellybit healthcheck` (GET `/healthz`
|
||||||
|
по порту из конфига, exit 0/1).
|
||||||
|
|
||||||
Контейнер: `user 1000:1000`, порт `8080` на хост, mount `/srv/media` (единая
|
Контейнер: `user 1000:1000`, порт `8080` на хост, mount `/srv/media` (единая
|
||||||
песочница для хардлинков) + том `/config` (ro, `config.toml`, восстановим при
|
песочница для хардлинков) + том `/config` (ro, `config.toml`, восстановим при
|
||||||
|
|||||||
+2
-2
@@ -76,10 +76,10 @@ tasks:
|
|||||||
done < web/assets.manifest
|
done < web/assets.manifest
|
||||||
|
|
||||||
image:
|
image:
|
||||||
desc: Docker-образ из готового бинаря (см. docs/adr docker-deploy)
|
desc: 'Docker-образ из готового бинаря. Тег из $BUILD_ID (по умолчанию dev; роль app_image umbar передаёт свой). См. docs/adr local-image-build'
|
||||||
deps: [build]
|
deps: [build]
|
||||||
cmds:
|
cmds:
|
||||||
- docker build -t jellybit:dev .
|
- docker build -t {{.BINARY}}:${BUILD_ID:-dev} .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
desc: Удалить собранный бинарь
|
desc: Удалить собранный бинарь
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Docker как единица деплоя, образ собирается на сервере
|
# Docker как единица деплоя, образ собирается на сервере
|
||||||
|
|
||||||
- Дата: 2026-06-13
|
- Дата: 2026-06-13
|
||||||
|
- Статус: заменено на ADR-2026-07-24-local-image-build
|
||||||
|
|
||||||
## Контекст
|
## Контекст
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Образ собирается локально и едет на сервер через 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` это единицы
|
||||||
|
мегабайт — дёшево.
|
||||||
+2
-1
@@ -56,8 +56,9 @@
|
|||||||
|
|
||||||
| Дата | Запись | Статус |
|
| Дата | Запись | Статус |
|
||||||
| ---------- | ---------------------------------------------------------------- | ------ |
|
| ---------- | ---------------------------------------------------------------- | ------ |
|
||||||
|
| 2026-07-24 | [Локальная сборка образа + доставка docker save/load](ADR-2026-07-24-local-image-build.md) | — |
|
||||||
| 2026-07-23 | [Конвейер ревью: гейт, generative-проходы и триаж](ADR-2026-07-23-review-pipeline-generative.md) | — |
|
| 2026-07-23 | [Конвейер ревью: гейт, generative-проходы и триаж](ADR-2026-07-23-review-pipeline-generative.md) | — |
|
||||||
| 2026-06-13 | [Авто-раскладка только при матче в метабазе](ADR-2026-06-13-auto-link-requires-db-match.md) | — |
|
| 2026-06-13 | [Авто-раскладка только при матче в метабазе](ADR-2026-06-13-auto-link-requires-db-match.md) | — |
|
||||||
| 2026-06-13 | [Docker как единица деплоя](ADR-2026-06-13-docker-deploy.md) | — |
|
| 2026-06-13 | [Docker как единица деплоя](ADR-2026-06-13-docker-deploy.md) | заменено на ADR-2026-07-24-local-image-build |
|
||||||
| 2026-06-13 | [Хардлинки вместо копирования и симлинков](ADR-2026-06-13-hardlinks.md) | — |
|
| 2026-06-13 | [Хардлинки вместо копирования и симлинков](ADR-2026-06-13-hardlinks.md) | — |
|
||||||
| 2026-06-13 | [Go и доставка одним бинарём](ADR-2026-06-13-go-single-binary.md) | — |
|
| 2026-06-13 | [Go и доставка одним бинарём](ADR-2026-06-13-go-single-binary.md) | — |
|
||||||
|
|||||||
@@ -28,9 +28,11 @@
|
|||||||
подтверждение в боте (карточка + кнопки + reply-подсказка, эскалация в
|
подтверждение в боте (карточка + кнопки + reply-подсказка, эскалация в
|
||||||
веб), полный редактор маппинга «файл → серия», триггер скана Jellyfin,
|
веб), полный редактор маппинга «файл → серия», триггер скана Jellyfin,
|
||||||
нотификации.
|
нотификации.
|
||||||
- **Ф6 — деплой.** Сборка статического бинаря здесь; доставка бинаря +
|
- **Ф6 — деплой.** Полный образ собирается локально на control-хосте
|
||||||
`Dockerfile` на сервер, `docker build` и запуск на месте; оркестрация —
|
(`task image`) и едет на сервер через `docker save`/`load` (роль
|
||||||
`playbook-jellybit.yml` в umbar: общая docker-сеть, `user 1000:1000`,
|
`app_image` в umbar), там и запускается — `docker build` на сервере нет;
|
||||||
|
оркестрация — `playbook-jellybit.yml` в umbar: общая docker-сеть,
|
||||||
|
`user 1000:1000`,
|
||||||
mount `/srv/media` + data-том `/srv/applications/jellybit/data`,
|
mount `/srv/media` + data-том `/srv/applications/jellybit/data`,
|
||||||
healthcheck. Сопутствующие правки qBit (том `/srv/media`, savepath/temp
|
healthcheck. Сопутствующие правки qBit (том `/srv/media`, savepath/temp
|
||||||
под `/srv/media`, `WebUI\ServerDomains=*`).
|
под `/srv/media`, `WebUI\ServerDomains=*`).
|
||||||
|
|||||||
@@ -203,11 +203,12 @@ relink/лечения. Включается конфигом `[jellyfin]` (по
|
|||||||
## Деплой
|
## Деплой
|
||||||
|
|
||||||
Jellybit работает в **docker** — в одной среде с qBittorrent и Jellyfin
|
Jellybit работает в **docker** — в одной среде с qBittorrent и Jellyfin
|
||||||
(см. [ADR-2026-06-13-docker-deploy](../adr/ADR-2026-06-13-docker-deploy.md)).
|
(см. [ADR-2026-07-24-local-image-build](../adr/ADR-2026-07-24-local-image-build.md)).
|
||||||
Сборка: статический бинарь (`GOOS=linux GOARCH=amd64 CGO_ENABLED=0`,
|
Сборка: статический бинарь (`GOOS=linux GOARCH=amd64 CGO_ENABLED=0`,
|
||||||
сервер на Intel N150) собирается здесь; на сервер во временную build-папку
|
сервер на Intel N150) и **полный образ** собираются локально на control-хосте
|
||||||
кладутся бинарь + `Dockerfile` (копирует бинарь в `distroless/static`),
|
(`task image` упаковывает бинарь в `distroless/static`). Готовый образ едет
|
||||||
образ собирается на месте и запускается. Go-тулчейн на сервере не нужен.
|
на сервер через `docker save`/`load` (роль `app_image` в umbar), там и
|
||||||
|
запускается. Go-тулчейн и `docker build` на сервере не нужны.
|
||||||
|
|
||||||
Параметры запуска (в umbar-compose):
|
Параметры запуска (в umbar-compose):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user