В этом уроке разберём механику каждого, общие параметры, идемпотентность и главную ловушку новичков - режим прав mode в восьмеричном виде. На экзамене EX294 задачи на file и copy встречаются буквально на каждом варианте, так что это не теория ради галочки, а твой хлеб.
Модуль ansible file: каталоги, симлинки и права
Модуль ansible file ничего не копирует. Он управляет состоянием объекта файловой системы, который уже есть или должен появиться: обычный файл, каталог, символическая или жёсткая ссылка. Ключевой параметр - state, и именно он определяет, что делает ansible file module:
- directory - создать каталог (и родительские, как mkdir -p)
- file - не создаёт файл, только проверяет наличие и правит атрибуты; если файла нет - ошибка
- touch - создать пустой файл, если его нет, и/или обновить mtime
- link и hard - символическая и жёсткая ссылка
- absent - удалить (рекурсивно для каталогов)
Код: Выделить всё
- name: Каталог приложения
ansible.builtin.file:
path: /opt/myapp
state: directory
owner: appuser
group: appuser
mode: "0755"
- name: Симлинк на текущий релиз
ansible.builtin.file:
src: /opt/myapp/releases/2026-06-15
dest: /opt/myapp/current
state: link
Теперь про права. Когда брифы и инструкторы говорят "ansible file 754", имеется в виду режим 0754 (rwxr-xr--). И вот главная грабля: mode пиши строкой в кавычках - "0644", "0754". Если написать mode: 0644 без кавычек, YAML воспримет ведущий ноль как восьмеричный литерал и сконвертирует в десятичное число, а Ansible получит совсем не то, что ты задумал. Кавычки спасают всегда. Альтернатива - символьная запись, она тоже работает и иногда читается понятнее:
Код: Выделить всё
- name: Сделать скрипт исполняемым только владельцу
ansible.builtin.file:
path: /opt/myapp/run.sh
mode: "u+x"

Модуль ansible copy: содержимое, content и backup
Если file отвечает за состояние, то ansible copy кладёт данные. Это один из самых высокочастотных модулей вообще, поэтому ansible copy module стоит освоить до автоматизма. Он умеет два режима. Первый - копировать локальный файл с control node на узел через src. Второй - писать содержимое прямо из плейбука через content, без отдельного файла:
Код: Выделить всё
- name: Скопировать готовый конфиг
ansible.builtin.copy:
src: files/app.conf
dest: /etc/myapp/app.conf
owner: root
group: root
mode: "0644"
backup: true
- name: Создать файл из строки прямо в задаче
ansible.builtin.copy:
content: |
ENV=production
WORKERS=4
dest: /etc/myapp/env
mode: "0640"
Важно понимать границу: copy хорош для статики. Если в файле нужны переменные, циклы, условия - это уже работа модуля template с Jinja2, его разберём отдельно. И ещё: для больших бинарников copy не лучший выбор, проще скачать на узле через get_url. А вот для одиночных текстовых конфигов и небольших файлов copy - первый инструмент.
Идемпотентность тут встроена: Ansible считает контрольную сумму и, если содержимое и атрибуты совпадают, задача отрабатывает в состоянии ok, ничего не трогая. Меняешь content или права - получаешь changed. Прогон того же плейбука второй раз не должен давать изменений, и это ровно то, что проверяют на EX294.
Ansible fetch и stat: забрать файл и проверить факты
Иногда поток нужен в обратную сторону: собрать файлы с управляемых узлов на control node. Для этого есть ansible fetch. Классика - стянуть логи или конфиги для разбора. Модуль по умолчанию раскладывает добычу по подкаталогам с именем хоста, чтобы файлы с разных машин не перетёрли друг друга:
Код: Выделить всё
- name: Забрать лог приложения к себе
ansible.builtin.fetch:
src: /var/log/myapp/error.log
dest: /tmp/collected/
flat: false
Теперь ansible.builtin.stat - твой способ узнать факты о файле до того, как что-то делать. Он не меняет систему, только читает метаданные: существует ли путь, режим, владелец, контрольная сумма, это файл или каталог. Результат регистрируешь через register и принимаешь решение через when. Это и есть связка stat плюс when, которую любят давать на собеседованиях и экзамене:
Код: Выделить всё
- name: Проверить наличие конфига
ansible.builtin.stat:
path: /etc/myapp/app.conf
register: cfg
- name: Развернуть дефолт, только если конфига ещё нет
ansible.builtin.copy:
src: files/app.conf.default
dest: /etc/myapp/app.conf
mode: "0644"
when: not cfg.stat.exists
Общие параметры и типичные грабли
file, copy, fetch и большинство файловых модулей делят общий набор атрибутов: owner, group, mode, а на RHEL/Fedora ещё и setype, seuser, serole, selevel для контекста SELinux. Это важно: скопировал файл в /var/www, права верные, а Apache его не отдаёт - смотри SELinux-контекст, а не только chmod. По умолчанию copy старается выставить разумный контекст, но в нестандартных каталогах задавай setype явно или применяй restorecon.
Грабли, на которых горят чаще всего:
- mode без кавычек. Ведущий ноль ломает восьмеричную интерпретацию. Всегда "0644", не 0644.
- state: file вместо touch. state: file не создаёт файл, только правит атрибуты существующего. Чтобы создать пустой - state: touch.
- Путаница src/dest/path. У file для ссылок - src и dest, для обычного объекта - path. У copy и fetch - src и dest, но направление противоположное: copy кладёт на узел, fetch тянет на control node.
- Забыли про идемпотентность. state: touch при каждом прогоне обновляет mtime и даёт changed. Если это нежелательно, добавь параметры modification_time: preserve и access_time: preserve.
Мини-лаба: повтори руками
Собери короткий плейбук и прогони его дважды, добиваясь нуля изменений на втором прогоне:
- Создай каталог /opt/lab с владельцем root и правами "0755" через ansible.builtin.file.
- Положи туда файл config.ini из content с правами "0640" и backup: true. Поменяй одну строку в content и прогони ещё раз - убедись, что появился changed и резервная копия.
- Сделай симлинк /opt/lab/current на /opt/lab через state: link.
- Через stat проверь существование /opt/lab/config.ini, зарегистрируй результат и выведи debug-сообщение с его mode и checksum.
- Забери /opt/lab/config.ini на control node через fetch с flat: false и посмотри структуру каталогов.
- Почему mode нужно писать в кавычках и что произойдёт с mode: 0644 без них?
- Чем отличаются state: file и state: touch в модуле file?
- Как с помощью stat и when развернуть файл только при его отсутствии и почему важно сначала проверять stat.exists?
- В какую сторону работают copy и fetch и куда fetch складывает файлы при flat: false?