Ansible: идемпотентность на практике

Плейбук, который можно запускать сколько угодно раз, — и почему module beats shell.

Минимум устройства

Ansible приходит на машины по SSH — агентов ставить не нужно. Два файла запускают всё:

Inventory — список машин:

[web]
web1.example.com
web2.example.com

Playbook — желаемое состояние:

- hosts: web
  become: true
  tasks:
    - name: nginx установлен
      apt:
        name: nginx
        state: present

    - name: конфиг сайта на месте
      template:
        src: site.conf.j2
        dest: /etc/nginx/conf.d/site.conf
      notify: reload nginx

    - name: nginx запущен и в автозагрузке
      service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: reload nginx
      service:
        name: nginx
        state: reloaded

Идемпотентность — в модулях

Задачи описаны не глаголами («установи»), а состояниями (state: present). Модуль сам проверяет текущее состояние: nginx уже стоит — задача вернёт ok и ничего не тронет; нет — поставит и вернёт changed. Поэтому плейбук можно запускать после каждого изменения и по расписанию — второй запуск по уже настроенной машине пройдёт весь в ok.

Это же ломается, как только вместо модуля берут shell:

# плохо: не идемпотентно, каждый запуск — changed
- shell: echo "net.core.somaxconn=1024" >> /etc/sysctl.conf

# хорошо: строка либо есть, либо будет добавлена один раз
- sysctl:
    name: net.core.somaxconn
    value: "1024"

shell — последнее средство, когда модуля действительно нет. Если он всё же нужен — делайте его идемпотентным сами через creates: или changed_when.

Handlers: перезапуск только при изменении

notify срабатывает только если задача вернула changed. Конфиг не менялся — nginx не перезагружается. Это и есть причина, по которой плейбук безопасно гонять хоть каждый час.

Сухой прогон

Перед применением к проду:

ansible-playbook site.yml --check --diff

--check ничего не меняет, --diff показывает, что изменилось бы. Привычка смотреть diff до запуска ловит бо́льшую часть сюрпризов.