Сразу важная деталь, на которой спотыкаются почти все. Модуль для распаковки - это ansible.builtin.unarchive, он встроенный. А вот модуль для упаковки - community.general.archive, он живёт в коллекции community.general, а не в builtin. Если напишешь ansible.builtin.archive, получишь ошибку "couldn't resolve module". Проверь, что коллекция стоит:
Код: Выделить всё
ansible-galaxy collection list | grep community.generalКод: Выделить всё
ansible-galaxy collection install community.generalНачнём с того, что встречается чаще всего, в том числе на экзамене - деплой артефакта. Запрос "ansible распаковать архив" почти всегда приводит именно к unarchive. У модуля две принципиально разные модели работы, и путаница между ними - источник половины проблем.
Модель первая, по умолчанию: архив лежит у тебя на управляющей машине (в роли, рядом с плейбуком), Ansible сам копирует его на узел и там распаковывает.
Код: Выделить всё
- name: Выкатить дистрибутив приложения с control-ноды
ansible.builtin.unarchive:
src: files/myapp-1.4.0.tar.gz
dest: /opt/myapp
owner: appuser
group: appuser
creates: /opt/myapp/bin/myapp
Код: Выделить всё
- name: Скачать и распаковать дистрибутив прямо на узле
ansible.builtin.unarchive:
src: https://repo.example.com/releases/myapp-1.4.0.tar.gz
dest: /opt/myapp
remote_src: yes
creates: /opt/myapp/bin/myapp
Теперь про идемпотентность - это то, ради чего мы вообще в Ansible. Сам по себе unarchive не самый умный: при remote_src он по умолчанию каждый прогон сверяет содержимое архива с файлами на диске. Но самый надёжный и предсказуемый способ сделать задачу идемпотентной - параметр creates. Указываешь путь к файлу или каталогу, который появляется после распаковки; если он уже есть - модуль пропускает шаг и рапортует ok вместо changed. Без creates ты рискуешь либо лишними changed на каждом прогоне, либо тем, что распаковка затрёт уже изменённые на месте файлы. На EX294 именно creates показывает, что ты понимаешь идемпотентность, а не просто "лишь бы отработало".

Ansible archive: создать tar/gz/zip из файлов узла
Обратная операция - ansible archive - упаковывает файлы, которые лежат на самом узле, в один архив. Типичные сценарии: снять бэкап конфигов перед изменениями, собрать логи для выгрузки, заархивировать каталог приложения перед обновлением.
Код: Выделить всё
- name: Заархивировать конфиги перед правкой
community.general.archive:
path:
- /etc/myapp/myapp.conf
- /etc/myapp/conf.d/
dest: /var/backups/myapp-conf-{{ ansible_date_time.date }}.tar.gz
format: gz
owner: root
mode: '0600'
Ansible assemble: собрать один файл из фрагментов каталога
Третий модуль решает совсем другую боль. Представь конфиг, который удобно держать по кускам: основной блок, блок для каждого виртуального хоста, блок с лимитами. Многие демоны умеют читать каталог conf.d целиком, но не все - sysctl, некоторые старые сервисы, кастомные приложения хотят один монолитный файл. Вот тут и нужен ansible assemble: он берёт все фрагменты из каталога, склеивает их по алфавиту имён в один файл и кладёт в нужное место.
Код: Выделить всё
- name: Собрать sshd_config из фрагментов
ansible.builtin.assemble:
src: /etc/ssh/sshd_config.d/
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
delimiter: "\n# --- fragment ---\n"
regexp: '^[0-9]+-.*\.conf$'
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart sshd
Хочешь собрать конфиг из частей, что лежат на control-ноде, а не на узле? Добавь remote_src: no (по умолчанию yes для assemble в новых версиях - перепроверь поведение под свою версию, тут исторически были разночтения), либо просто сначала разложи фрагменты на узел через copy/template, а потом собери assemble с источником на узле. Второй путь надёжнее и понятнее.
Типичные грабли
- Пишут ansible.builtin.archive - такого нет. Упаковка только через community.general.archive.
- Путают remote_src. URL качается только при remote_src: yes. С control-ноды копируется только при remote_src: no.
- Забывают creates у unarchive - получают вечный changed или затирают данные на узле при каждом прогоне.
- На узле нет утилиты для распаковки. unarchive под капотом зовёт tar/gunzip/unzip. Для zip часто нужен пакет unzip: поставь его задачей ansible.builtin.dnf (или apt в Debian/Ubuntu, в RED OS и Astra - тоже apt/dnf по семейству).
- SELinux и права. После распаковки контекст файлов может быть неверным; не забывай про restorecon или модуль ansible.builtin.sefcontext, если демон не видит файлы.
- assemble без validate на критичном конфиге. Один битый фрагмент - и sshd не стартует. validate спасает.
Прогони руками на тестовой RHEL/Fedora-машине:
- Создай локально tar.gz с парой файлов, распакуй его на узел через unarchive с remote_src: no и creates. Запусти плейбук дважды - убедись, что второй прогон ok, а не changed.
- Скачай публичный архив по URL с remote_src: yes прямо на узел и распакуй в /opt. Проверь, что без unzip zip-архив не распакуется, и почини это задачей dnf.
- Сделай каталог /etc/myapp/conf.d с файлами 10-main.conf и 20-extra.conf, собери из них один /etc/myapp/myapp.conf через assemble с delimiter и regexp. Добавь битый фрагмент и проверь, как validate не даёт сломать файл.
- Заархивируй /etc/myapp через community.general.archive в /var/backups с датой в имени.
- В какой коллекции живёт модуль для создания архива и почему ansible.builtin.archive не сработает?
- Чем отличается поведение unarchive при remote_src: yes и remote_src: no, и когда качается URL?
- Зачем нужен параметр creates в unarchive и как он влияет на идемпотентность?
- Что делает validate в assemble и почему без него опасно собирать боевой конфиг?
Три модуля - три задачи. unarchive распаковывает (и даже качает по URL), archive из community.general упаковывает файлы на узле, assemble клеит монолитный конфиг из фрагментов. Ключ к чистым прогонам - remote_src правильно выбран, creates на месте, а validate бережёт от битых конфигов. На EX294 чаще всего попросят именно деплой артефакта через unarchive - отработай его до автоматизма.