Модуль 03 — Плейбуки

Записать настройку сервера один раз и применять её к любому числу узлов

Прогресс курса Модуль 3 из 9

Что вы освоите в этом модуле

01

Из чего состоит плейбук

Разовые команды хороши для одной операции. Настройку сервера из десяти шагов записывают в плейбук — файл YAML. Наш плейбук ставит на веб-узлы nginx со страницей сервиса, а на все узлы — пользователя для выкладки и утилиты:

---
- name: Веб-узлы — nginx и страница сервиса
  hosts: web
  become: true
  tasks:
    - name: Пакет nginx установлен
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
        cache_valid_time: 3600
      tags: [packages]

    - name: Каталог сайта
      ansible.builtin.file:
        path: /srv/www/price
        state: directory
        owner: www-data
        group: www-data
        mode: "0755"
      tags: [site]

    - name: Стартовая страница
      ansible.builtin.copy:
        dest: /srv/www/price/index.html
        content: |
          <h1>Сервис цен — расчёт НДС</h1>
          <p>Узел {{ inventory_hostname }}</p>
        owner: www-data
        group: www-data
        mode: "0644"
      tags: [site]

    - name: Сайт nginx смотрит в каталог сервиса
      ansible.builtin.copy:
        dest: /etc/nginx/sites-available/default
        content: |
          server {
              listen 80 default_server;
              root /srv/www/price;
              index index.html;
          }
        mode: "0644"
      tags: [site]

    - name: Служба nginx запущена и в автозапуске
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: true

- name: Все узлы — пользователь и утилиты
  hosts: all
  become: true
  tasks:
    - name: Пользователь для выкладки
      ansible.builtin.user:
        name: deploy
        shell: /bin/bash
        create_home: true

    - name: Утилиты администратора
      ansible.builtin.apt:
        name: [htop, curl, tree]
        state: present
        update_cache: true
        cache_valid_time: 3600
      tags: [packages]
02

Проверка до запуска

--syntax-check проверяет YAML, структуру плейбука и существование модулей, не подключаясь к узлам. --list-tasks показывает, какие задачи и с какими тегами будут выполнены:

$ ansible-playbook site.yml --syntax-check

playbook: site.yml
$ ansible-playbook site.yml --list-tasks

playbook: site.yml

  play #1 (web): Веб-узлы — nginx и страница сервиса	TAGS: []
    tasks:
      Пакет nginx установлен	TAGS: [packages]
      Каталог сайта	TAGS: [site]
      Стартовая страница	TAGS: [site]
      Сайт nginx смотрит в каталог сервиса	TAGS: [site]
      Служба nginx запущена и в автозапуске	TAGS: []

  play #2 (all): Все узлы — пользователь и утилиты	TAGS: []
    tasks:
      Пользователь для выкладки	TAGS: []
      Утилиты администратора	TAGS: [packages]

Опечатка в имени модуля находится сразу — с файлом, строкой и колонкой:

$ cp site.yml broken.yml && sed -i "s/ansible.builtin.apt:/ansible.builtin.aptt:/" broken.yml
$ ansible-playbook broken.yml --syntax-check
[ERROR]: couldn't resolve module/action 'ansible.builtin.aptt'. This often indicates a misspelling, missing collection, or incorrect module path.
Origin: /tmp/anspage.EvdtuF/ex/broken.yml:6:7

4   become: true
5   tasks:
6     - name: Пакет nginx установлен
        ^ column 7
03

Первый прогон

$ ansible-playbook site.yml

PLAY [Веб-узлы — nginx и страница сервиса] *************************************

TASK [Gathering Facts] *********************************************************
ok: [web1]
ok: [web2]

TASK [Пакет nginx установлен] **************************************************
changed: [web2]
changed: [web1]

TASK [Каталог сайта] ***********************************************************
changed: [web1]
changed: [web2]

TASK [Стартовая страница] ******************************************************
changed: [web2]
changed: [web1]

TASK [Сайт nginx смотрит в каталог сервиса] ************************************
changed: [web1]
changed: [web2]

TASK [Служба nginx запущена и в автозапуске] ***********************************
ok: [web1]
ok: [web2]

PLAY [Все узлы — пользователь и утилиты] ***************************************

TASK [Gathering Facts] *********************************************************
ok: [web2]
ok: [web1]
ok: [db1]

TASK [Пользователь для выкладки] ***********************************************
changed: [web2]
changed: [web1]
changed: [db1]

TASK [Утилиты администратора] **************************************************
changed: [web2]
changed: [web1]
changed: [db1]

PLAY RECAP *********************************************************************
db1                        : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web1                       : ok=9    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web2                       : ok=9    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Как читать вывод:

Если задача упала на одном узле, этот узел выбывает из дальнейших задач плейбука, а остальные продолжают. В итоге видно, какие узлы настроены, а какие нет, и повторный запуск доделает работу на оставшихся.

04

Всё зелёное, а сайт не тот

Плейбук прошёл без ошибок. Проверим, что отдают веб-узлы:

$ ansible web -m shell -a "curl -s http://127.0.0.1/ | grep -o \"<h1>.*</h1>\""
web1 | CHANGED | rc=0 >>
<h1>Welcome to nginx!</h1>
web2 | CHANGED | rc=0 >>
<h1>Welcome to nginx!</h1>
$ ansible web -b -m systemd -a "name=nginx state=reloaded" | grep -E "^\S+ \|"
web2 | CHANGED => {
web1 | CHANGED => {
$ ansible web -m shell -a "curl -s http://127.0.0.1/ | grep -o \"<h1>.*</h1>\""
web1 | CHANGED | rc=0 >>
<h1>Сервис цен</h1>
web2 | CHANGED | rc=0 >>
<h1>Сервис цен</h1>

Сначала — стандартная страница nginx. Причина в порядке событий: apt поставил nginx, и Debian сразу запустил службу со стандартной конфигурацией. Потом плейбук положил новую конфигурацию, но nginx её не перечитал, а задача «Служба nginx запущена» увидела, что он запущен, и ничего не делала. Состояние «запущен» выполнено — а работает старая конфигурация.

Здесь мы перечитали конфигурацию разовой командой. Ставить в плейбук задачу state: reloaded — плохое решение: она будет перезагружать nginx при каждом прогоне и всегда отчитываться changed, даже когда ничего не менялось. Правильное решение — обработчики: перезагрузка только тогда, когда изменилась конфигурация. Им посвящён модуль 05.

05

Повторный прогон

$ ansible-playbook site.yml

PLAY [Веб-узлы — nginx и страница сервиса] *************************************

TASK [Gathering Facts] *********************************************************
ok: [web2]
ok: [web1]

TASK [Пакет nginx установлен] **************************************************
ok: [web2]
ok: [web1]

TASK [Каталог сайта] ***********************************************************
ok: [web1]
ok: [web2]

TASK [Стартовая страница] ******************************************************
ok: [web1]
ok: [web2]

TASK [Сайт nginx смотрит в каталог сервиса] ************************************
ok: [web1]
ok: [web2]

TASK [Служба nginx запущена и в автозапуске] ***********************************
ok: [web2]
ok: [web1]

PLAY [Все узлы — пользователь и утилиты] ***************************************

TASK [Gathering Facts] *********************************************************
ok: [web2]
ok: [db1]
ok: [web1]

TASK [Пользователь для выкладки] ***********************************************
ok: [web1]
ok: [web2]
ok: [db1]

TASK [Утилиты администратора] **************************************************
ok: [db1]
ok: [web1]
ok: [web2]

PLAY RECAP *********************************************************************
db1                        : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web1                       : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web2                       : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

changed=0 на всех узлах. Это проверка идемпотентности плейбука: второй прогон без изменений в описании не должен менять ничего. Если на втором прогоне что-то changed — в плейбуке ошибка: задача не умеет определять текущее состояние (чаще всего это command или shell без проверки). Такой плейбук нельзя спокойно запускать по расписанию, и по его выводу не понять, менялось ли что-то на самом деле.

06

Что изменится: --check --diff

Поменяем заголовок страницы и, прежде чем применять, посмотрим, что именно изменится. --check ничего не меняет, --diff показывает разницу в файлах, --limit ограничивает проверку одним узлом:

$ sed -i "s#<h1>Сервис цен</h1>#<h1>Сервис цен — расчёт НДС</h1>#" site.yml
$ ansible-playbook site.yml --check --diff --limit web1

PLAY [Веб-узлы — nginx и страница сервиса] *************************************

TASK [Gathering Facts] *********************************************************
ok: [web1]

TASK [Пакет nginx установлен] **************************************************
ok: [web1]

TASK [Каталог сайта] ***********************************************************
ok: [web1]

TASK [Стартовая страница] ******************************************************
--- before: /srv/www/price/index.html
+++ after: /srv/www/price/index.html
@@ -1,2 +1,2 @@
-<h1>Сервис цен</h1>
+<h1>Сервис цен — расчёт НДС</h1>
 <p>Узел web1</p>

changed: [web1]

TASK [Сайт nginx смотрит в каталог сервиса] ************************************
ok: [web1]

TASK [Служба nginx запущена и в автозапуске] ***********************************
ok: [web1]

PLAY [Все узлы — пользователь и утилиты] ***************************************

TASK [Gathering Facts] *********************************************************
ok: [web1]

TASK [Пользователь для выкладки] ***********************************************
ok: [web1]

TASK [Утилиты администратора] **************************************************
ok: [web1]

PLAY RECAP *********************************************************************
web1                       : ok=9    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

$ ansible web1 -m command -a "cat /srv/www/price/index.html"
web1 | CHANGED | rc=0 >>
<h1>Сервис цен</h1>
<p>Узел web1</p>

Задача «Стартовая страница» сообщила бы об изменении и показала разницу строк; файл на узле прежний. Перед изменениями на боевых узлах это обязательный шаг: вы видите не «что я хотел написать», а «что на самом деле поменяется на этих серверах».

07

Теги

Большой плейбук выполняется минутами. Если поменялась только страница, незачем проверять пакеты на всех узлах. tags у задач позволяют запустить их часть:

$ ansible-playbook site.yml --list-tags

playbook: site.yml

  play #1 (web): Веб-узлы — nginx и страница сервиса	TAGS: []
      TASK TAGS: [packages, site]

  play #2 (all): Все узлы — пользователь и утилиты	TAGS: []
      TASK TAGS: [packages]
$ ansible-playbook site.yml --tags site

PLAY [Веб-узлы — nginx и страница сервиса] *************************************

TASK [Gathering Facts] *********************************************************
ok: [web1]
ok: [web2]

TASK [Каталог сайта] ***********************************************************
ok: [web1]
ok: [web2]

TASK [Стартовая страница] ******************************************************
changed: [web2]
changed: [web1]

TASK [Сайт nginx смотрит в каталог сервиса] ************************************
ok: [web1]
ok: [web2]

PLAY [Все узлы — пользователь и утилиты] ***************************************

TASK [Gathering Facts] *********************************************************
ok: [web2]
ok: [web1]
ok: [db1]

PLAY RECAP *********************************************************************
db1                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web1                       : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web2                       : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Выполнились только задачи с тегом site (и сбор фактов). Кроме --tags есть --skip-tags, а --start-at-task "имя задачи" начинает плейбук с указанной задачи — удобно, когда длинный прогон упал в середине. Но полный прогон всё равно делают регулярно: только он гарантирует, что узлы соответствуют всему описанию.

Ключевые выводы модуля

Запомните главное

  • Плейбук — список play (узлы + задачи); задача — один модуль; имена задач описывают нужное состояние
  • Полные имена модулей ansible.builtin.…; права файлов — строкой в кавычках
  • До запуска — --syntax-check и --list-tasks; после — PLAY RECAP с failed=0
  • Второй прогон должен дать changed=0 — это проверка идемпотентности
  • Перед изменениями на бою — --check --diff на одном узле
  • Зелёный прогон не гарантирует, что служба работает с новой конфигурацией — для этого обработчики (модуль 05)
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 02: Инвентарь и разовые команды Все модули Модуль 04: Переменные, факты, условия, циклы