ansible
Плейбуки Ansible — структура, модули user/file/copy/template/lineinfile, циклы, условия, идемпотентность, --check
Ansible описывает желаемое состояние системы в YAML-файле — плейбуке — и приводит машины к этому состоянию. Каждая задача вызывает модуль («пользователь существует», «строка есть в файле»), а модуль сам решает, нужно ли что-то менять. Обычно Ansible ходит на серверы по SSH, но может работать и с локальной машиной.
Синтаксис
ansible-playbook [-i инвентарь] [опции] плейбук.yml
ansible <хосты> [-i инвентарь] -m модуль -a 'аргументы' # одна задача без плейбука
Структура плейбука:
- name: Описание набора задач # play
hosts: localhost # на каких хостах выполнять
connection: local # без SSH, прямо на этой машине
gather_facts: true # собрать факты о системе (ansible_facts)
become: true # выполнять через sudo
vars:
app_dir: /opt/app
tasks:
- name: Описание задачи
ansible.builtin.file: # модуль
path: "{{ app_dir }}" # параметры модуля
state: directory
Опции ansible-playbook
| Опция | Что делает |
|---|---|
-i файл | инвентарь: список хостов и групп (INI или YAML); -i localhost, — список прямо в строке |
-l шаблон | ограничить хосты (-l web, -l 'web:!db') |
-e 'ключ=значение' / -e @vars.yml | переменные с наивысшим приоритетом |
--check | пробный прогон: показать, что изменилось бы, ничего не меняя |
--diff | показать разницу в файлах; вместе с --check — до применения |
--syntax-check | только проверить синтаксис |
--list-tasks / --list-hosts | какие задачи / хосты будут затронуты |
--tags t1,t2 / --skip-tags t | выполнить только / пропустить задачи с тегами |
--start-at-task 'имя' | начать с указанной задачи |
-b / -K | become / спросить пароль sudo |
-v, -vv, -vvv | подробнее: результаты модулей, затем детали подключения |
Частые модули
Полное имя — ansible.builtin.<модуль>; справка — ansible-doc ansible.builtin.<модуль>, краткая — ansible-doc -s ….
| Модуль | Ключевые параметры | Что делает |
|---|---|---|
user | name, state, shell, groups, append, create_home, remove | пользователь существует / удалён |
group | name, state, gid | группа |
file | path, state (directory, touch, absent, link), owner, group, mode, src | каталог, пустой файл, ссылка, права, удаление |
copy | dest, src или content, owner, mode, backup | файл с заданным содержимым |
template | src (шаблон Jinja2 .j2), dest, owner, mode | файл из шаблона с подстановкой переменных |
lineinfile | path, line, regexp, state, insertafter, create | одна строка в файле есть / заменена / удалена |
blockinfile | path, block, marker, create | блок строк между маркерами |
replace | path, regexp, replace | замена по регулярному выражению во всём файле |
command | cmd, chdir, creates, removes | запустить программу (без оболочки: нет |, >, *) |
shell | то же | запустить через sh — с пайпами и перенаправлениями |
package / apt | name, state (present, absent, latest), update_cache | пакеты |
service | name, state (started, stopped, restarted), enabled | служба |
cron | name, minute, hour, job, user, state | строка в crontab |
stat | path | сведения о файле (результат — через register) |
get_url / unarchive | url, dest / src, dest, remote_src | скачать файл / распаковать архив |
debug | msg или var | напечатать значение |
set_fact | имя: значение | завести переменную по ходу плейбука |
assert | that, fail_msg | проверить условия, иначе ошибка |
Ключи задачи
| Ключ | Что делает |
|---|---|
name | описание — видно в выводе |
loop | повторить задачу для каждого элемента; текущий — {{ item }} |
loop_control.label | что показывать в выводе вместо всего item |
when | выполнить, только если условие истинно (без {{ }}) |
register | сохранить результат модуля в переменную (.rc, .stdout, .changed) |
changed_when / failed_when | своё правило, когда считать задачу изменившей / упавшей |
ignore_errors: true | не останавливаться при ошибке задачи |
notify | после изменения запустить обработчик из раздела handlers |
become / become_user | выполнить от root / от указанного пользователя |
tags | метки для --tags / --skip-tags |
check_mode | принудительно выполнить в режиме проверки (или наоборот) |
На уровне play, кроме tasks, бывают vars, vars_files (переменные из файлов), handlers (задачи, которые запускаются по notify один раз в конце) и roles. Переменные для групп и хостов можно разложить по group_vars/<группа>.yml и host_vars/<хост>.yml рядом с инвентарём.
Примеры
# проверить синтаксис и посмотреть список задач
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml --list-tasks
# пробный прогон с разницей в файлах, затем настоящий
ansible-playbook site.yml --check --diff
ansible-playbook site.yml
# переопределить переменную из командной строки
ansible-playbook site.yml -e app_dir=/srv/app
# с инвентарём: группы хостов и способ подключения
ansible-playbook -i inventory.ini site.yml
# одна задача без плейбука (ad-hoc)
ansible localhost -m ansible.builtin.ping
ansible localhost -m ansible.builtin.setup -a 'filter=ansible_distribution*'
Инвентарь, в котором локальная машина входит в группу local:
[local]
localhost ansible_connection=local
Плейбук с циклом, шаблоном, условием и обработчиком:
- name: Настройка приложения
hosts: localhost
connection: local
vars:
app_user: demo
app_dir: /opt/demo
handlers:
- name: show reload
ansible.builtin.debug:
msg: "конфиг изменился"
tasks:
- name: Пользователь приложения
ansible.builtin.user:
name: "{{ app_user }}"
shell: /bin/bash
- name: Каталоги
ansible.builtin.file:
path: "{{ app_dir }}/{{ item }}"
state: directory
owner: "{{ app_user }}"
mode: "0755"
loop:
- conf
- logs
- name: Параметр в конфиге
ansible.builtin.lineinfile:
path: "{{ app_dir }}/conf/app.ini"
regexp: '^port='
line: "port=8080"
create: true
notify: show reload
- name: Конфиг из шаблона templates/app.conf.j2
ansible.builtin.template:
src: templates/app.conf.j2
dest: "{{ app_dir }}/conf/app.conf"
- name: Только на Debian-семействе
ansible.builtin.debug:
msg: "{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
when: ansible_facts['os_family'] == "Debian"
Идемпотентность
Повторный запуск того же плейбука не должен ничего менять: в итоговой строке PLAY RECAP будет changed=0. Модули вроде user, file, copy, lineinfile сначала проверяют текущее состояние и меняют только то, что отличается. ok — уже как надо, changed — пришлось изменить, skipped — задача пропущена (when, --check).
Подводные камни
commandиshellвсегда отчитываютсяchangedи не знают, нужно ли их запускать. Добавляйcreates:/removes:илиchanged_when: falseдля команд, которые только читают.state: touchуfile— тоже всегдаchanged: он обновляет время файла при каждом запуске.- Две задачи правят одну строку по-разному — и каждый запуск даёт
changed. Если повторный прогон что-то меняет, ищи такой конфликт. modeпиши строкой в кавычках:mode: "0644". Число644без ведущего нуля YAML прочитает как десятичное, и права станут странными (1204).- Значение, начинающееся с
{{, бери в кавычки:path: "{{ dir }}/x". Без кавычек YAML примет фигурную скобку за начало словаря и плейбук не загрузится. whenпишется без{{ }}— это уже выражение Jinja2.--checkне выполняетcommand/shell(ониskipped), поэтому зависящие от их результата задачи могут вести себя иначе, чем в настоящем прогоне.- Хост не нашёлся — не ошибка. Если
hosts:не совпал ни с одной группой инвентаря, Ansible выведет предупреждение и молча ничего не сделает. Проверяй--list-hosts. - Без
-iинвентаря нет, доступен только неявныйlocalhost; группаallего не включает. - Обработчик запускается один раз в конце play и только если уведомившая задача была
changed.