← Весь справочник

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 / -Kbecome / спросить пароль sudo
-v, -vv, -vvvподробнее: результаты модулей, затем детали подключения

Частые модули

Полное имя — ansible.builtin.<модуль>; справка — ansible-doc ansible.builtin.<модуль>, краткая — ansible-doc -s ….

МодульКлючевые параметрыЧто делает
username, state, shell, groups, append, create_home, removeпользователь существует / удалён
groupname, state, gidгруппа
filepath, state (directory, touch, absent, link), owner, group, mode, srcкаталог, пустой файл, ссылка, права, удаление
copydest, src или content, owner, mode, backupфайл с заданным содержимым
templatesrc (шаблон Jinja2 .j2), dest, owner, modeфайл из шаблона с подстановкой переменных
lineinfilepath, line, regexp, state, insertafter, createодна строка в файле есть / заменена / удалена
blockinfilepath, block, marker, createблок строк между маркерами
replacepath, regexp, replaceзамена по регулярному выражению во всём файле
commandcmd, chdir, creates, removesзапустить программу (без оболочки: нет |, >, *)
shellто жезапустить через sh — с пайпами и перенаправлениями
package / aptname, state (present, absent, latest), update_cacheпакеты
servicename, state (started, stopped, restarted), enabledслужба
cronname, minute, hour, job, user, stateстрока в crontab
statpathсведения о файле (результат — через register)
get_url / unarchiveurl, dest / src, dest, remote_srcскачать файл / распаковать архив
debugmsg или varнапечатать значение
set_factимя: значениезавести переменную по ходу плейбука
assertthat, 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.

См. также

ssh · yq · cron · useradd · terraform