app_image: добавлена роль (сборка и доставка docker-образа)
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
# app_image
|
||||||
|
|
||||||
|
Собирает docker-образ приложения **локально** (на control-хосте) и доставляет
|
||||||
|
его на сервер через `docker save`/`load`. Тулчейн сборки (Go, node, …) на
|
||||||
|
сервере не нужен — туда едет готовый образ.
|
||||||
|
|
||||||
|
Роль — первый кирпичик деплоя: её задача, чтобы на сервере оказался образ
|
||||||
|
`<app_image_name>:<tag>`. Запуском приложения (рендер config и docker-compose,
|
||||||
|
`docker compose up`) занимается остальная часть плейбука — позже это может стать
|
||||||
|
отдельным кирпичиком.
|
||||||
|
|
||||||
|
Тег образа — случайный `BUILD_ID`, который роль генерит на каждый прогон.
|
||||||
|
Дедупликации по содержимому нет: каждый деплой везёт образ и пересоздаёт
|
||||||
|
контейнер. Для ручного нечастого деплоя это осознанный выбор — простота важнее
|
||||||
|
экономии одного рестарта.
|
||||||
|
|
||||||
|
## Контракт с приложением
|
||||||
|
|
||||||
|
Приложение реализует **команду сборки образа** в своей рабочей директории.
|
||||||
|
Роль — инициатор: она вызывает эту команду и передаёт ей случайный `BUILD_ID`
|
||||||
|
через окружение. Команда обязана собрать **полный** образ и затегать его
|
||||||
|
`<app_image_name>:$BUILD_ID`.
|
||||||
|
|
||||||
|
Пример реализации на Taskfile (тег из `$BUILD_ID`, по умолчанию `dev`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image:
|
||||||
|
desc: 'Полный docker-образ. Тег из $BUILD_ID (по умолчанию dev).'
|
||||||
|
deps: [build]
|
||||||
|
cmds:
|
||||||
|
- docker build -t {{.BINARY}}:${BUILD_ID:-dev} .
|
||||||
|
```
|
||||||
|
|
||||||
|
`${BUILD_ID:-dev}` держит команду обратно совместимой: `task image` без
|
||||||
|
переменной по-прежнему собирает `<app>:dev` для локальной работы.
|
||||||
|
|
||||||
|
## Как работает
|
||||||
|
|
||||||
|
1. Роль генерит случайный `BUILD_ID` — это же и есть `app_image_tag`.
|
||||||
|
2. Гоняет `app_image_command` в `app_image_src_dir` → приложение собирает
|
||||||
|
`<app_image_name>:$BUILD_ID`.
|
||||||
|
3. `docker save` образа → копирует tar на сервер → `docker load`.
|
||||||
|
4. Отдаёт факт `app_image_tag`, чистит локальный тег и оба tar-файла.
|
||||||
|
|
||||||
|
Локальный тег на control-хосте снимается по завершении (образ уже на сервере).
|
||||||
|
Старые образы на сервере подчищает `docker image prune -af` по крону (см.
|
||||||
|
`playbook-system.yml`).
|
||||||
|
|
||||||
|
## Переменные
|
||||||
|
|
||||||
|
| Переменная | Обязат. | По умолчанию | Назначение |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `app_image_name` | да | — | имя приложения; под ним тегается и живёт образ |
|
||||||
|
| `app_image_src_dir` | да | — | рабочая директория приложения на control-хосте |
|
||||||
|
| `app_image_command` | нет | `task image` | команда сборки образа (контракт); одна команда через `command`, без shell-синтаксиса |
|
||||||
|
|
||||||
|
## Выход
|
||||||
|
|
||||||
|
- Факт **`app_image_tag`** — тег доставленного образа (= `BUILD_ID`).
|
||||||
|
- На сервере — образ `<app_image_name>:<app_image_tag>`.
|
||||||
|
|
||||||
|
## Пример
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: 'Build and deliver application image'
|
||||||
|
ansible.builtin.import_role:
|
||||||
|
name: app_image
|
||||||
|
vars:
|
||||||
|
app_image_name: 'jellybit'
|
||||||
|
app_image_src_dir: '{{ (playbook_dir, "..", "jellybit") | path_join }}'
|
||||||
|
# далее compose-роль использует app_image_tag
|
||||||
|
```
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
# defaults file for app_image
|
||||||
|
|
||||||
|
# Имя приложения/образа (обязательно). Под этим именем приложение тегает
|
||||||
|
# собранный образ (<app_image_name>:<tag>), под ним же образ живёт на сервере.
|
||||||
|
app_image_name: ""
|
||||||
|
|
||||||
|
# Рабочая директория приложения на control-хосте — репозиторий с исходниками
|
||||||
|
# и командой сборки образа. Обязательный параметр: задаётся явно в плейбуке.
|
||||||
|
app_image_src_dir: ""
|
||||||
|
|
||||||
|
# Команда сборки образа — контракт приложения. Запускается в app_image_src_dir,
|
||||||
|
# получает случайный BUILD_ID через окружение и обязана собрать ПОЛНЫЙ образ,
|
||||||
|
# затегав его <app_image_name>:$BUILD_ID. По умолчанию — Taskfile-таск `task image`.
|
||||||
|
# Исполняется через command (без shell): одна команда, без пайпов/&&/VAR=x-префиксов.
|
||||||
|
# Подробности контракта — в README.md роли.
|
||||||
|
app_image_command: "task image"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: app_image
|
||||||
|
author: "Anton Vakhrushev"
|
||||||
|
description: "Локальная сборка образа приложения и доставка его на сервер через docker save/load" # yamllint disable-line rule:line-length
|
||||||
|
license: "MIT"
|
||||||
|
min_ansible_version: "2.14"
|
||||||
|
platforms:
|
||||||
|
- name: Ubuntu
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
- name: Debian
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- docker
|
||||||
|
- image
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
# tasks file for app_image
|
||||||
|
#
|
||||||
|
# Роль-инициатор сборки. Гоняет команду сборки приложения на control-хосте и
|
||||||
|
# доставляет получившийся образ на сервер через docker save/load. Тулчейн сборки
|
||||||
|
# (Go, node, …) на сервере не нужен — туда едет готовый образ.
|
||||||
|
#
|
||||||
|
# Контракт приложения (app_image_command): команда в app_image_src_dir получает
|
||||||
|
# BUILD_ID из окружения и собирает полный образ <app_image_name>:$BUILD_ID.
|
||||||
|
#
|
||||||
|
# Тег образа = случайный BUILD_ID (роль его генерит). Дедупликации по содержимому
|
||||||
|
# нет — каждый деплой везёт образ и пересоздаёт контейнер. Для ручного нечастого
|
||||||
|
# деплоя это осознанный выбор: простота важнее экономии рестарта. Старые образы
|
||||||
|
# на сервере чистит `docker image prune -af` по крону (см. playbook-system.yml).
|
||||||
|
#
|
||||||
|
# Рассчитана на один целевой хост за прогон: сборка делегируется на control-хост,
|
||||||
|
# при нескольких хостах в play повторится на каждый (мы деплоим последовательно).
|
||||||
|
#
|
||||||
|
# Выход: факт app_image_tag (= BUILD_ID) + образ <app_image_name>:<app_image_tag>
|
||||||
|
# на сервере. Тег забирает следующий кирпичик (рендер compose).
|
||||||
|
|
||||||
|
- name: "Validate role input"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
# Формат имени docker-образа: он же идёт в имя tar-файла (vars/main.yml),
|
||||||
|
# поэтому '/' и прочее ломало бы путь и docker-референс.
|
||||||
|
- app_image_name is string and app_image_name is match('^[a-z0-9][a-z0-9._-]*$')
|
||||||
|
- app_image_src_dir is string and app_image_src_dir | length > 0
|
||||||
|
- app_image_command is string and app_image_command | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
app_image_name — валидное имя docker-образа (^[a-z0-9][a-z0-9._-]*$);
|
||||||
|
app_image_src_dir и app_image_command — непустые строки
|
||||||
|
quiet: true
|
||||||
|
|
||||||
|
- name: "Generate build id (image tag)"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
app_image_tag: "{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_lowercase,digits length=16') }}"
|
||||||
|
|
||||||
|
- name: "Build image and deliver it to server"
|
||||||
|
# В --check command-таски пропускаются, tar не создаётся и copy упал бы «нет
|
||||||
|
# файла на контроллере». Пропускаем всю доставку — факт app_image_tag уже
|
||||||
|
# выставлен, поэтому рендер compose ниже честно покажет diff со своим тегом.
|
||||||
|
when: "not ansible_check_mode"
|
||||||
|
block:
|
||||||
|
- name: "Build image locally via app contract"
|
||||||
|
delegate_to: localhost
|
||||||
|
# Сборка идёт от локального юзера. Инвентарь включает become глобально, но
|
||||||
|
# локальная сборка под root не нужна — фиксируем инвариант явно.
|
||||||
|
become: false
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "{{ app_image_command }}"
|
||||||
|
chdir: "{{ app_image_src_dir }}"
|
||||||
|
environment:
|
||||||
|
BUILD_ID: "{{ app_image_tag }}"
|
||||||
|
# Локальная сборка — средство доставки; факт изменения системы (сервера)
|
||||||
|
# отражает загрузка образа ниже.
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: "Save image to tar locally"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "docker save --output {{ app_image_local_tar }} {{ app_image_name }}:{{ app_image_tag }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: "Copy image tar to server"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ app_image_local_tar }}"
|
||||||
|
dest: "{{ app_image_remote_tar }}"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: "Load image on server"
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "docker load --input {{ app_image_remote_tar }}"
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: "Remove local image tar"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ app_image_local_tar }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: "Remove remote image tar"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ app_image_remote_tar }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
# Локальный тег — временная ручка (на сервер образ уехал через save/load).
|
||||||
|
# Снимаем, чтобы control-хост не копил <app>:<random> от каждого прогона.
|
||||||
|
- name: "Remove transient local image tag"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "docker image rm {{ app_image_name }}:{{ app_image_tag }}"
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
# vars file for app_image — внутренние значения роли, не для переопределения.
|
||||||
|
|
||||||
|
# Временный tar с образом для переноса control-хост → сервер. Имя завязано на
|
||||||
|
# имя приложения и тег (= BUILD_ID), чтобы параллельные приложения и прогоны не
|
||||||
|
# топтали файлы друг друга. Референсит app_image_tag (выставляется в tasks до
|
||||||
|
# блока доставки) — vars роли шаблонизируются лениво, факт уже определён.
|
||||||
|
app_image_local_tar: '/tmp/app_image-{{ app_image_name }}-{{ app_image_tag }}.tar'
|
||||||
|
app_image_remote_tar: '/tmp/app_image-{{ app_image_name }}-{{ app_image_tag }}.tar'
|
||||||
Reference in New Issue
Block a user