Записать настройку сервера один раз и применять её к любому числу узлов
ok от changed и проверять идемпотентность повторным прогоном--check --diffРазовые команды хороши для одной операции. Настройку сервера из десяти шагов записывают в плейбук — файл 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]
hosts) и что с ними делать (tasks). В плейбуке два play: для группы web и для all. Выполняются по очереди.forks узлов сразу), и только потом начинается следующая.name у задачи — то, что вы увидите в выводе. Пишите его как нужное состояние: «nginx запущен», а не «запустить nginx» — так вывод читается как отчёт о состоянии сервера.become: true на уровне play — все его задачи через sudo.ansible.builtin.apt — полное имя модуля (FQCN): коллекция и модуль. Короткое apt тоже работает, но полное однозначно и не перепутается с модулем из другой коллекции; линтер (модуль 08) требует именно его.cache_valid_time: 3600 — обновлять список пакетов, только если он старше часа. Иначе update_cache каждый раз дёргал бы зеркала и делал задачу «изменившейся».mode: "0644" — в кавычках. Без них YAML прочитает 0644 как восьмеричное число, а 644 — как десятичное 644, и права окажутся совсем не теми.--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
$ 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
Как читать вывод:
Gathering Facts — автоматический сбор фактов в начале каждого play (модуль 02).changed — задача изменила узел; ok — узел уже был в нужном состоянии; бывают ещё skipped (условие не выполнено, модуль 04) и failed.PLAY RECAP — итог по каждому узлу. Главное в нём: failed=0 и unreachable=0. Код выхода ansible-playbook ненулевой, если хоть один узел упал, — так его проверяют в конвейерах CI.Если задача упала на одном узле, этот узел выбывает из дальнейших задач плейбука, а остальные продолжают. В итоге видно, какие узлы настроены, а какие нет, и повторный запуск доделает работу на оставшихся.
Плейбук прошёл без ошибок. Проверим, что отдают веб-узлы:
$ 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.
$ 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 без проверки). Такой плейбук нельзя спокойно запускать по расписанию, и по его выводу не понять, менялось ли что-то на самом деле.
Поменяем заголовок страницы и, прежде чем применять, посмотрим, что именно изменится. --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>
Задача «Стартовая страница» сообщила бы об изменении и показала разницу строк; файл на узле прежний. Перед изменениями на боевых узлах это обязательный шаг: вы видите не «что я хотел написать», а «что на самом деле поменяется на этих серверах».
Большой плейбук выполняется минутами. Если поменялась только страница, незачем проверять пакеты на всех узлах. 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 "имя задачи" начинает плейбук с указанной задачи — удобно, когда длинный прогон упал в середине. Но полный прогон всё равно делают регулярно: только он гарантирует, что узлы соответствуют всему описанию.
ansible.builtin.…; права файлов — строкой в кавычках--syntax-check и --list-tasks; после — PLAY RECAP с failed=0changed=0 — это проверка идемпотентности--check --diff на одном узле