Основы Ansible, без которых ваши плейбуки — комок слипшихся макарон

  • Tutorial

Я делаю много ревью для чужого кода на Ансибл и много пишу сам. В ходе анализа ошибок (как чужих, так и своих), а так же некоторого количества собеседований, я понял основную ошибку, которую допускают пользователи Ансибла — они лезут в сложное, не освоив базового.


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


Ожидаемый уровень читателя — уже написано несколько тысяч строк ямла, уже что-то в продакшене, но "как-то всё криво".


Названия


Главная ошибка пользователя Ansible — это не знать как что называется. Если вы не знаете названий, вы не можете понимать то, что написано в документации. Живой пример: на собеседовании, человек, вроде бы заявлявший, что он много писал на Ансибле, не смог ответить на вопрос "из каких элементов состоит playbook'а?". А когда я подсказал, что "ожидался ответ, что playbook состоит из play", то последовал убийственный комментарий "мы этого не используем". Люди пишут на Ансибле за деньги и не используют play. На самом деле используют, но не знают, что это такое.


Так что начнём с простого: как что называется. Может быть вы это знаете, а может и нет, потому что не обратили внимания, когда читали документацию.


ansible-playbook исполняет playbook. Playbook — это файл с расширением yml/yaml, внутри которого что-то такое:


---
- hosts: group1
  roles:
    - role1

- hosts: group2,group3
  tasks:
    - debug:

Мы уже поняли, что весь этот файл — плейбука. Мы можем показать где тут роли (roles), где таски (tasks). Но где тут play? И чем отличается play от role или playbook?


В документации это всё есть. И это пропускают. Начинающие — потому что там слишком много и всё сразу не запомнишь. Опытные — потому что "тривиальные вещи". Если вы опытный — перечитывайте эти страницы хотя бы раз в пол-года, и ваш код станет классом лучше.


Итак, запоминайте: Playbook — это список, состоящий из play и import_playbook.
Вот это — одна play:


- hosts: group1
  roles:
    - role1

и вот это тоже ещё одна play:


- hosts: group2,group3
  tasks:
    - debug:

Что же такое play? Зачем она?


Play — это ключевой элемент для playbook, потому что play и только play связывает список ролей и/или тасок с списком хостов, на которых их надо выполнять. В глубоких недрах документации можно найти упоминание про delegate_to, локальные lookup-плагины, network-cli-специфичные настройки, jump-хосты и т.д. Они позволяют слегка поменять место исполнения тасок. Но, забудьте про это. У каждой из этих хитрых опций есть очень специальные применения, и они точно не являются универсальными. А мы говорим про базовые вещи, которые должны знать и использовать все.


Если вы хотите "что-то" исполнить "где-то" — вы пишете play. Не роль. Не роль с модулями и делегейтами. Вы берёте и пишете play. В которой, в поле hosts вы перечисляете где исполнять, а в roles/tasks — что исполнять.


Просто же, да? А как может быть иначе?


Одним из характерных моментов, когда у людей возникает желание сделать это не через play, это "роль, которая всё настраивает". Хочется иметь роль, которая настраивает и сервера первого типа, и сервера второго типа.


Архетипичным примером является мониторинг. Хочется иметь роль monitoring, которая настроит мониторинг. Роль monitoring назначается на хосты мониторинга (в соотв. play). Но, выясняется, что для мониторинга нам надо поставить пакеты на хосты, которые мы мониторим. Почему бы не использовать delegate? А ещё надо настроить iptables. delegate? А ещё надо написать/поправить конфиг для СУБД, чтобы мониторинг пускала. delegate! А если креатив попёр, то можно сделать делегацию include_role во вложенном цикле по хитрому фильтру на список групп, а внутри include_role можно ещё делать delegate_to снова. И понеслось...


Благое пожелание — иметь одну-единственную роль monitoring, которая "всё делает" — ведёт нас кромешный ад из которого чаще всего один выход: всё переписать с нуля.


Где тут случилась ошибка? В тот момент, когда вы обнаружили, что для выполнения задачи "x" на хосте X вам надо пойти на хост Y и сделать там "y", вы должны были выполнить простое упражнение: пойти и написать play, которая на хосте Y делает y. Не дописывать что-то в "x", а написать с нуля. Пусть даже с захардкоженными переменными.


Вроде бы, в абзацах выше всё сказано правильно. Но это же не ваш случай! Потому что вы хотите написать переиспользуемый код, который DRY и похож на библиотеку, и нужно искать метод как это сделать.


Вот тут вот притаилась ещё одна грубая ошибка. Ошибка, которая превратила множество проектов из терпимо написанных (можно лучше, но всё работает и легко дописать) в совершенный ужас, в котором даже автор не может разобраться. Оно работает, но упаси боже что-то поменять.


Эта ошибка звучит так: роль — это библиотечная функция. Эта аналогия сгубила столько хороших начинаний, что просто грустно смотреть. Роль — не библиотечная функция. Она не может делать вычисления и она не может принимать решения уровня play. Напомните мне, какие решения принимает play?


Спасибо, вы правы. Play принимает решение (точнее, содержит в себе информацию) о том, какие таски и роли на каких хостах выполнять.


Если вы делегируете это решение на роль, да ещё и с вычислениями, вы обрекаете себя (и того, кто ваш код будет пытаться разобрать) на жалкое существование. Роль не решает где ей выполняться. Это решение принимает play. Роль делает то, что ей сказали, там, где ей сказали.


Почему заниматься программированием на Ансибле опасно и чем COBOL лучше Ансибла мы поговорим в главе про переменные и jinja. Пока что скажем одно — каждое ваше вычисление оставляет за собой нестираемый след из изменения глобальных переменных, и вы ничего с этим не можете сделать. Как только два "следа" пересеклись — всё пропало.


Замечание для въедливых: роль, безусловно, может влиять на control flow. Есть delegate_to и у него есть разумные применения. Есть meta: end host/play. Но! Помните, мы учим основы? Забыли про delegate_to. Мы говорим про самый простой и самый красивый код на Ансибл. Который легко читать, легко писать, легко отлаживать, легко тестировать и легко дописывать. Так что, ещё раз:


play и только play решает на каких хостах что исполняется.


В этом разделе мы разобрались с противостоянием play и role. Теперь поговорим про отношения tasks vs role.


Таски и Роли


Рассмотрим play:


- hosts: somegroup
  pre_tasks:
    - some_tasks1:
  roles:
     - role1
     - role2
  post_tasks:
     - some_task2:
     - some_task3:

Допустим, вам надо сделать foo. И выглядит это как foo: name=foobar state=present. Куда это писать? в pre? post? Создавать role?


… И куда делись tasks?


Мы снова начинаем с азов — устройство play. Если вы плаваете в этом вопросе, вы не можете использовать play как основу для всего остального, и ваш результат получается "шатким".


Устройство play: директива hosts, настройки самой play и секции pre_tasks, tasks, roles, post_tasks. Остальные параметры для play нам сейчас не важны.


Порядок их секций с тасками и ролями: pre_tasks, roles, tasks, post_tasks. Поскольку семантически порядок исполнения между tasks и roles не понятен, то best practices говорит, что мы добавляем секцию tasks, только если нет roles. Если есть roles, то все прилагающиеся таски помещаются в секции pre_tasks/post_tasks.


Остаётся только то, что семантически всё понятно: сначала pre_tasks, потом roles, потом post_tasks.


Но мы всё ещё не ответили на вопрос: а куда вызов модуля foo писать? Надо ли нам под каждый модуль писать целую роль? Или лучше иметь толстую роль подо всё? А если не роль, то куда писать — в pre или в post?


Если на на эти вопросы нет аргументированного ответа, то это признак отсутствия интуиции, то есть те самые "шаткие основы". Давайте разбираться. Сначала контрольный вопрос: Если у play есть pre_tasks и post_tasks (и нет ни tasks, ни roles), то может ли что-то сломаться, если я первую таску из post_tasks перенесу в конец pre_tasks?


Разумеется, формулировка вопроса намекает, что сломается. Но что именно?


… Хэндлеры. Чтение основ открывает важный факт: все хэндлеры flush'атся автоматом после каждой секции. Т.е. выполняются все таски из pre_tasks, потом все хэндлеры, которые были notify. Потом выполняются все роли и все хэндлеры, которые были notify в ролях. Потом post_tasks и их хэндлеры.


Таким образом, если вы таску перетащите из post_tasks в pre_tasks, то, потенциально, вы выполните её до выполнения handler'а. например, если в pre_tasks устанавливается и конфигурируется веб-сервер, а в post_tasks в него что-то засылается, то перенос этой таски в секцию pre_tasks приведёт к тому, что в момент "засылания" сервер будет ещё не запущен и всё сломается.


А теперь давайте ещё раз подумаем, а зачем нам pre_tasks и post_tasks? Например, для того, чтобы выполнить всё нужное (включая хэндлеры) до выполнения роли. А post_tasks позволит нам работать с результатами выполнения ролей (включая хэндлеры).


Въедливый знаток Ansible скажет нам, что есть meta: flush_handlers, но зачем нам flush_handlers, если мы можем положиться на порядок исполнения секций в play? Более того, использование meta: flush_handlers может нам доставить неожиданного с повторяющимися хэндлерами, сделать нам странные варнинги в случае использования when у block и т.д. Чем лучше вы знаете ансибл, тем больше нюансов вы сможете назвать для "хитрого" решения. А простое решение — использование натурального разделения между pre/roles/post — не вызывает нюансов.


И, возвращаемся, к нашему 'foo'. Куда его поместить? В pre, post или в roles? Очевидно, это зависит от того, нужны ли нам результаты работы хэндлера для foo. Если их нет, то foo не нужно класть ни в pre, ни в post — эти секции имеют специальный смысл — выполнение тасок до и после основного массива кода.


Теперь ответ на вопрос "роль или таска" сводится к тому, что уже есть в play — если там есть tasks, то надо дописать в tasks. Если есть roles — надо делать роль (пусть и из одной task). Напоминаю, tasks и roles одновременно не используются.


Понимание основ Ансибла даёт обоснованные ответы на, казалось бы, вопросы вкусовщины.


Таски и роли (часть вторая)


Теперь обсудим ситуацию, когда вы только начинаете писать плейбуку. Вам надо сделать foo, bar и baz. Это три таски, одна роль или три роли? Обобщая вопрос: в какой момент надо начинать писать роли? В чём смысл писать роли, когда можно писать таски?… А что такое роль?


Одна из грубейших ошибок (я про это уже говорил) — считать, что роль — это как функция в библиотеке у программы. Как выглядит обобщённое описание функции? Она принимает аргументы на вход, взаимодействует с side causes, делает side effects, возвращает значение.


Теперь, внимание. Что из этого можно сделать в роли? Вызвать side effects — всегда пожалуйста, это и есть суть всего Ансибла — делать сайд-эффекты. Иметь side causes? Элементарно. А вот с "передать значение и вернуть его" — вот тут-то и нет. Во-первых, вы не можете передать значение в роль. Вы можете выставить глобальную переменную со сроком жизни размером в play в секции vars для роли. Вы можете выставить глобальную переменную со сроком жизни в play внутри роли. Или даже со сроком жизни плейбуки (set_fact/register). Но вы не можете иметь "локальные переменные". Вы не можете "принимать значение" и "возвращать его".


Из этого вытекает главное: нельзя на ansible написать что-то и не вызвать сайд-эффекты. Изменение глобальных переменных — это всегда side effect для функции. В Rust, например, изменение глобальной переменной — это unsafe. А в Ансибл — единственный метод повлиять на значения для роли. Обратите внимание на используемые слова: не "передать значение в роль", а "изменить значения, которые использует роль". Между ролями нет изоляции. Между тасками и ролями нет изоляции.


Итого: роль — это не функция.


Что же хорошего есть в роли? Во-первых, у роли есть default values (/default/main.yaml), во-вторых у роли есть дополнительные каталоги для складывания файлов.


Чем же хороши default values? Тем, что в пирамиде Маслоу довольно извращённой таблице приоритетов переменных у Ансибла, role defaults — самые неприоритетные (за вычетом параметров командной строки ансибла). Это означает, что если вам надо предоставить значения по-умолчанию и не переживать что они перебъют значения из инвентори или групповых переменных, то дефолты роли — это единственное правильное место для вас. (Я немного вру — есть ещё |d(your_default_here), но если говорить про стационарные места — то только дефолты ролей).


Что ещё хорошего в ролях? Тем, что у них есть свои каталоги. Это каталоги для переменных, как постоянных (т.е. вычисляемых для роли), так и для динамических (есть такой то ли паттерн, то ли анти-паттерн — include_vars вместе с {{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml.). Это каталоги для files/, templates/. Ещё, оно позволяет иметь роли свои модули и плагины (library/). Но, в сравнении с тасками у playbook'и (у которой тоже всё это может быть), польза тут только в том, что файлы свалены не в одну кучу, а несколько раздельных кучек.


Ещё одна деталь: можно пытаться делать роли, которые будут доступны для переиспользования (через galaxy). После появления коллекций распространение ролей можно считать почти забытым.


Таким образом, роли обладают двумя важными особенностями: у них есть дефолты (уникальная особенность) и они позволяют структурировать код.


Возвращаясь к исходному вопросу: когда делать таски а когда роли? Таски в плейбуке чаще всего используются либо как "клей" до/после ролей, либо как самостоятельный строительный элемент (тогда в коде не должно быть ролей). Груда нормальных тасок в перемешку с ролями — это однозначная неряшливость. Следует придерживаться конкретного стиля — либо таски, либо роли. Роли дают разделение сущностей и дефолты, таски позволяют прочитать код быстрее. Обычно в роли выносят более "стационарный" (важный и сложный) код, а в стиле тасок пишут вспомогательные скрипты.


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


Въедливый читатель может сказать, что роли могут импортировать роли, у ролей может быть зависимость через galaxy.yml, а ещё есть страшный и ужасный include_role — напоминаю, мы повышаем навыки в базовом Ансибле, а не в фигурной гимнастике.


Хэндлеры и таски


Давайте обсудим ещё одну очевидную вещь: хэндлеры. Умение их правильно использовать — это почти искусство. В чём разница между хэндлером и таской?


Так как мы вспоминаем основы, то вот пример:


- hosts: group1
  tasks:
    - foo:
      notify: handler1
  handlers:
     - name: handler1
       bar:

У роли handler'ы лежат в rolename/handlers/main.yaml. Handler'ы шарятся между всеми участниками play: pre/post_tasks могут дёргать handler'ы роли, а роль может дёргать handler'ы из плей. Однако, "кросс-ролевые" вызовы handler'ов вызывают куда больший wtf, чем повтор тривиального handler'а. (Ещё один элемент best practices — стараться не делать повторов имён handler'ов).


Основное различие в том, что таска выполняется (идемпотентно) всегда (плюс/минус теги и when), а хэндлер — по изменению состояния (notify срабатывает только если был changed). Чем это чревато? Например, тем, что при повторном запуске, если не было changed, то не будет и handler. А почему может быть так, что нам нужно выполнить handler когда не было changed у порождающей таски? Например, потому что что-то сломалось и changed был, а до хэндлера выполнение не дошло. Например, потому что сеть временно лежала. Конфиг поменялся, сервис не перезапущен. При следующем запуске конфиг уже не меняется, и сервис остаётся со старой версией конфига.


Ситуация с конфигом не решаемая (точнее, можно самим себе изобрести специальный протокол перезапуска с файловыми флагами и т.д., но это уже не 'basic ansible' ни в каком виде). Зато есть другая частая история: мы поставили приложение, записали его .service-файл, и теперь хотим его daemon_reload и state=started. И натуральное место для этого, кажется, хэндлер. Но если сделать его не хэндлером а таской в конце тасклиста или роли, то он будет идемпотентно выполняться каждый раз. Даже если плейбука сломалась на середине. Это совершенно не решает проблемы restarted (нельзя делать таску с атрибутом restarted, т.к. теряется идемпотентность), но однозначно стоит делать state=started, общая стабильность плейбуки возрастает, т.к. уменьшается количество связей и динамического состояния.


Ещё одно положительное свойство handler'а состоит в том, что он не засоряет вывод. Не было изменений — нет лишних skipped или ok в выводе — легче читать. Оно же является и отрицательным свойством — если опечатку в линейно исполняемой task'е вы найдёте на первый же прогон, то handler'ы будут выполнены только при changed, т.е. при некоторых условиях — очень редко. Например, первый раз в жизни спустя пять лет. И, разумеется, там будет опечатка в имени и всё сломается. А второй раз их не запустить — changed-то нет.


Отдельно надо говорить про доступность переменных. Например, если вы notify для таски с циклом, то что будет в переменных? Можно аналитическим путём догадаться, но не всегда это тривиально, особенно, если переменные приходят из разных мест.


… Так что handler'ы куда менее полезны и куда более проблемны, чем кажется. Если можно что-то красиво (без выкрутас) написать без хэндлеров лучше делать без них. Если красиво не получается — лучше с ними.


Въедливый читатель справедливо отмечает, что мы не обсудили listen, что handler может вызывать notify для другого handler'а, что handler может включать в себя import_tasks (который может делать include_role c with_items), что система хэндлеров в Ансибле тьюринг-полная, что хэндлеры из include_role прелюбопытнейшим образом пересекаются с хэндлерами из плей и т.д. — всё это явно не "основы").


Хотя есть один определённый WTF, который на самом деле фича, и о котором надо помнить. Если у вас таска выполняется с delegate_to и у неё есть notify, то соответствующий хэндлер выполняется без delegate_to, т.е. на хосте, на котором назначена play. (Хотя у хэндлера, разумеется, может быть delegate_to тоже).


Отдельно я хочу сказать пару слов про reusable roles. До появления коллекций была идея, что можно сделать универсальные роли, которые можно ansible-galaxy install и поехал. Работает на всех ОС всех вариантов во всех ситуациях. Так вот, моё мнение: это не работает. Любая роль с массовым include_vars, поддержкой 100500 случаев обречена на бездны corner case багов. Их можно затыкать массированным тестированием, но как с любым тестированием, либо у вас декартово произведение входных значений и тотальная функция, либо у вас "покрыты отдельные сценарии". Моё мнение — куда лучше, если роль линейная (цикломатическая сложность 1).


Чем меньше if'ов (явных или декларативных — в форме when или форме include_vars по набору переменных), тем лучше роль. Иногда приходится делать ветвления, но, повторю, чем их меньше, тем лучше. Так что вроде бы хорошая роль с galaxy (работает же!) с кучей when может быть менее предпочтительна, чем "своя" роль из пяти тасок. Момент, когда роль с galaxy лучше — когда вы что-то начинаете писать. Момент, когда она становится хуже — когда что-то ломается, и у вас есть подозрение, что это из-за "роли с galaxy". Вы её открываете, а там пять инклюдов, восемь таск-листов и стопка when'ов… И в этом надо разобраться. Вместо 5 тасок линейным списком, в котором и ломаться-то нечему.


В следующих частях


  • Немного про инвентори
  • групповые переменные, host_group_vars plugin, hostvars. Как из спагетти связать Гордиев узел. Scope и precedence переменных, модель памяти Ansible. "Так где же всё-таки хранить имя пользователя для базы данных?".
  • jinja: {{ jinja }} — nosql notype nosense мягкий пластилин. Оно всюду, даже там, где вы его не ожидаете. Немного про !!unsafe и вкусный yaml.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 58

    +2
    Спасибо! Супер! Жаль, что раньше такого разбора не попалось.
      0

      Да, а теперь уже поздно?

      +6

      Очень толковая статья философско-архитектурного плана, надо будет ещё многое осмыслить, тут одним чтением явно не ограничиться. Пишу на ансибл много, видел и много статей прикладного уровня, но вот такое структурное осмысление пожалуй впервые на хабре вижу.


      Очень жаль, что тематика узкая и статья выйдет недооценённой, это явно одна из лучших закладок по теме будет, к которой ещё не раз будут возвращаться желающие осмыслить свой опыт написания плейбуков. (Кстати, да: а почему в женском роде? "плейбука" как "базука", мне вот интуитивно хочется сказать в мужском: "этот плейбук", а не "эта плейбука".)


      Жду продолжения, пока буду вертеть в голове и обдумывать уже написанное.

        0

        Бук — книга — она.


        Интересно, склонения по родам в рунглише — дело вкуса или есть каноничный вариант?

          0

          "плейбука" приятнее произносить и склонять, чем "плейбук". Как native speaker, я настаиваю.

            0
            Если вы термин используете «плейбук», то раз написание на русском, тогда и правила русского языка применяйте. Если вы хотите можете перевести playbook как «пьеса» и склонять уже это слово, но это не не верно потому, что это технический термин.
              +2

              В русском языке нет слова "плейбук" или "плейбука", это калька с английского, которая сейчас имеет статус профессионального жаргонизма. Какой при этом у слова образуется род решается носителями языка (к которым я отношусь) в процессе словоупотребления. Вы вольны предлагать свою версию, и лишь практика и время покажет, какой вариант приживётся.


              Я использую такие формы, с которыми удобно работать. Это причина, почему я не транслитерирую play, потому что мне его склонения не нравятся.

        0
        Не могу голосовать, но огромное Вам спасибо за четкое и доходчивое разъяснение! Жду продолжения.
          +1
          Ошибки, которые вы высмеиваете, растут из того, что программисты, не имеющие реального опыта администрирования (домашний роутер не в счет) пытаются выстраивать инфраструктуру. Почему-то товарищи программисты забывают, что для качественной работы надо погрузиться в предметную область. А не тяп-ляп и в продакшн ;).
          Если честно, то про сущность play я читаю в первый раз =). Однако то что я в первую очередь админ, а уже во вторую программист, позволило не совершить описанных ошибок. Потому что я давно погружен в эту предметную область =).

          Интересно будет почитать остальные части.
            +5

            Если вы написали хотя бы одну плейбуку в своей жизни и первый раз слышите слово "play", это означает, что документацию вы пролистнули не фокусируясь на написанном. https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#about-playbooks:


            Each playbook is composed of one or more ‘plays’ in a list.

            The goal of a play is to map a group of hosts to some well defined roles, represented by things ansible calls tasks. At a basic level, a task is nothing more than a call to an ansible module.

            By composing a playbook of multiple ‘plays’, it is possible to orchestrate multi-machine deployments, running certain steps on all machines in the webservers group, then certain steps on the database server group, then more commands back on the webservers group, etc.

            Насчёт "программистов" вы неправы. Я основываюсь на большом опыте ревью и работы с очень опытными администраторами в команде — ошибки с control flow делают все. Я тоже делал, и мне потребовалось несколько лет, чтобы почувствовать дзен простого Ансибла.

              0
              Если вы написали хотя бы одну плейбуку в своей жизни и первый раз слышите слово «play», это означает, что документацию вы пролистнули не фокусируясь на написанном.


              Все почти так + плохое знание английского. Я и сейчас не воспринял кавычки как обозначение термина =).
                –4
                Всегда завидовал людям у которых хватает воли читать эти тонны документации, которые на 90% не пригодятся.
                  0

                  Это не тонны документации, это 101 ansible'а.

                    0

                    что такое 101?

                    –1
                    Ну вот вы пишите что человек на собеседовании не знал что такое «play». Это значит что он не умеет пользоваться Ansible и он плохой специалист? Я считаю что это задротсво. По-мимо Ansible, DevOps-инженеру нужно знать еще очень много технологий и чтобы изучать каждую так досконально, нужно иметь феноменальную память.
                      +1

                      Он говорил, что пишет на Ансибле и это его основная деятельность на текущем месте работы. Если бы он сказал, что Ансибл знает поверхностно, то я бы просто перешёл к другим вопросам. Доучить Ансибл — не проблема, проблема в том, что человек использует инструмент как основной и не знает его.


                      Это как если бы программист, который говорил, что пишет на питоне, не мог бы сказать, что такое "модуль", или верстальщик на html не знал, что такое "тег".

                –3
                image

                Вот вам типичный лог Ансибла который вы наверное миллион раз видели. Видите там сущность «плей»?
                  +6
                  Английским по чёрному:
                  PLAY [GATHER INFORMATION FROM ROUTERS] **************
                +2
                В целом согласен. Ещё добавлю, что when и delegate_to — это попытки написать части Ansible на Ansible :-)
                  +4

                  delegate_to имеет несколько очень хороших случаев применения (обычно в отношении гипервизор/vm, хост и "его управляющая машина"). When имеет определённый смысл. Во-первых это двухходовочка для имитации идемпотентности (query, do/when), во-вторых, один из потрясающе полезных трюков, которые я часто использую, это meta: end_host when: nothing else to do. — позволяет исключить тонны skip'ов в выводе.


                  В целом, и я буду писать отдельный лонгрид про это, Ансибл не всеобъемлющий и надо знать когда остановиться и переходить на модули/скрипты и т.д. Неумение остановиться вовремя (точнее, не знание где остановиться) — это третья критическая ошибка при использовании Ансибла, когда каждется, что "вот сейчас я в этом словаре найду ключ и по нему найду в другом списке словарь и там будет имя по которому я смогу найти IP, и тогда оно заработает". А надо не пытаться дальше, а остановиться и использовать другие инструменты.

                    +1
                    meta: end_host when: nothing else to do

                    Вот это хорошо, полезно. Жаль, что нету meta: end_role

                  0
                  Посоветуйте, что почитать для погружения в тем. Чую, скоро заставят и меня писать плейбуки))
                    +4

                    Удивительно, но обычная документация ансибла достаточно хороша. Есть такая проблема онбординга, что люди не хотят фокусироваться на простом и сразу прыгают в самое сложное (отношения хостов/переменных, обычно видно в паттерне "настроить СУБД и клиента"). На самом деле, просто начинайте писать простые вещи. Плейбука из одной плей, внутри которой 4 таски — отличное начало.


                    Например, вы всегда ставите на сервер комплект нужного вам софта и (например) копируете свой bashrc. Вот и напишите плейбуку, которая это делает.


                    По чуть-чуть, по чуть-чуть. Меньше ролей, больше простых tasks. Дзен Ансибла в том, чтобы сохранять простоту.

                    0
                    Спасибо огромное, очень толково. Жду продолжения

                    Я вообще пишу single-play playbooks в основном и только на ролях, никаких тасков, но у меня и задачи соответствующие…

                      +1
                      А ансибл бывает не комком слипшихся макарон? :)
                      Когда смотришь даже в корпоративных плейбуки — волосы шевелятся. Или груда кода, или все предельно переусложнено и для того, чтобы понять простую роль надо обежать кучу файлов.
                        +1

                        Вот как раз корпоративные плейбуки чаще всего и есть самый большой ужас. Как и любой другой код с малым уровнем peer pressure.


                        Мы в отделе сейчас нарабатываем практики, и у нас есть пара пет-проектов в которых мы стараемся делать так хорошо, как можем. И там всё очень, очень просто. Линейный код, минимум переменных, две с половиной выкрутасы (одна из которых решает проблему группировок, вторая уменьшает скипы).


                        Когда мы одну из них доведём до совершенства, можно будет поговорить про публикацию. Там нет никаких бизнес-секретов, а вот пример хорошего ансибла — есть.

                          0
                          А что можете сказать по поводу DebOps?
                            0

                            Я их трогал до появления коллекций и у меня они не взлетели. Но это было очень давно, я не могу оценить своё впечатление тогда как правильную оценку сегодня.


                            Сейчас я сходу ткнулся — https://github.com/debops/debops/blob/master/ansible/roles/apache/tasks/main.yml


                            У них могут быть причины, но в целом я не одобряю такой стиль:


                            - name: Enable/disable configuration snippets
                              file:
                                path: '{{ apache__config_path + "/conf-enabled/" + item.key + ".conf" }}'
                                src: '../conf-available/{{ item.key }}.conf'
                                force: '{{ ansible_check_mode|d() | bool }}'
                                state: '{{ (((item.value.enabled|d(True)
                                              if (item.value is mapping)
                                              else item.value|d(True)))
                                             if (item.value.state|d("present") != "absent")
                                             else False) | bool | ternary("link", "absent") }}'
                              when: (item.value.type|d("default") not in ["divert"])
                              with_dict: '{{ apache__combined_snippets }}'
                              notify: [ 'Test apache and reload' ]

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


                            (А ещё они не пройдут линтер).

                              0
                              С линтером там порядок, скипают только одно правило в конфиге.

                              Да условия выглядят жутко если уровень вложенности больше 1-го, но это же проблема шаблонизатора.

                              Хотелось бы авторитетного мнения по поводу структуры, подходов и прочей архитектуры.

                              По поводу таких сборных переменных тоже хотелось бы услышать
                              apache__combined_snippets: '{{ apache__dependent_snippets
                                                             | combine(apache__role_snippets)
                                                             | combine(apache__snippets)
                                                             | combine(apache__group_snippets)
                                                             | combine(apache__host_snippets) }}'


                                0

                                Я увидел 'True' с большой буквы. На такое ругаются (в yaml true пишется с маленькой, а True — это синоним on, yes и т.д.).


                                Что касается сборки переменных вместе, паттерн со снипеттами у меня вызывает наименьшие вопросы, если нет нормального conf.d, это один из методов написать разумного размера роли.
                                А вот математику в 'state' (в примере выше) я бы на ревью не принял.

                                  0
                                  Я бы её принял в дефолтах роли, но src я бы не принял из-за хардкода. У меня вообще всё завязано на метод «счёт до 3х», если за 3 шага не можешь реализовать, разбиваешь на меньшие куски. А чтобы кроличья нора не получилась, там тоже «счёт до 3х». В итоге получается максимум 81 ветвление, но вот конечных вариантов получается 6561.
                          0
                          даже в корпоративных плейбуки
                          почему «даже»? Их обычно мало человек пишет и еще меньше в них смотрит, так что там «обычно» всё ужасно, «лишь бы работало». А вот когда от коммьюнити что-то ансибловое — там есть шанс наткнутся на нормально сделанное.
                          0
                          Большое спасибо за статью, она действительно очень интересна и прямо-таки источает боль и страдания многолетнего использования ансибла:)

                          Вы пишете про `meta: flush_handlers` так, будто это что-то плохое. Но ведь если у нас в одном плее десяток ролей, девять из которых внесут изменения на хосте, а десятая сфейлится — хендлеры не отработают, о чём вы и пишете дальше. В общем, понятно, что хендлеры по сути компромисс, но это явно меньшее зло и избежать их использования не получится, поэтому мне кажется, что нужно использовать `meta: flush_handlers` всегда и во всех ролях, которые используют хендлеры.

                          Или я не так понял ваш посыл?
                            0

                            meta: flush_handlers не плохое, но это "затычка". Идеальный вариант, если плейбука структурирована так, что handler'ы отрабатывают сами собой. Например, если роль2 зависит от хэндлера роль1, то можно плей порезать на две, в первой роль1, во второй роль2, и всё отработает само, без flush'ей.


                            … А handler'ы и ошибки — это вообще тёмные углы Ansible, в которые лучше не ходить. Во-первых хэндлеры — это попытка "спрятать" неидемпотентные рестарты. Что, довольно успешно делается, если всё хорошо. Но если у нас сбой, то возникает проблема: у нас неидемпотентная операция, которая зависит от стейта в памяти у программы. Программа закончилась ошибкой, стейт потерян.


                            Иногда это ок. Иногда — остро не "ок", потому что процесс (в продакшене) остался со старой версией, а новые прогоны ансибла говорят "ok" с 0 changed.


                            … И тут начинается проблема распределённых систем, когда нельзя ничего сделать "once". Либо at least once, либо at most once. Хэндлеры работают в режиме at most once.


                            Если нужно at least once (гарантированный рестарт, но, может быть, больше одного раза), то я такое изобретал (давным-давно). Вот как-то так: https://medium.com/@george.shuklin/handling-handlers-for-ansible-88b0c91515a4 (глава surviving failures). Это было давно, возможно, там у меня грязный Ансибл внутри.


                            Но тотальной функции для рестартов быть не может, увы.

                            0

                            Утащил в закладки. Спасибо большое

                              0
                              Я всё таки считаю, что кросс ОС роли для простых вещей вроде настройки ссш или судо это удобно. Но только в том случае если вы используете immutable servers (неизменяемые сервера?), когда вы создали тераформом виртуалки из заранее известного шаблона ОС (а шаблон создан вами пакером который бутстрапит ансиблом), а потом универсальной ролью нтп меняете пару параметров и релоадите сервис. Если что-то пошло не так, удалить сервер и уведомить человека об ошибке. В подавляющем большинстве случаев, разницы в действиях не будет, разница будет только в именах пакетов, путей или именах юнитов системд.
                                0
                                Если уже даже пакер применять — то какие-то простые вещи как раз можно уже сразу заранее настроить, еще до ансибла.
                                  0
                                  Пакер ансиблом бутсрапит шаблон. Ну и всё равно может быть какой то кастом после поднятия вм. Обычно какая нибудь мелочь типа таймзоны, серверов нтп, адреса реджистри, управление ключами (техические учётки самого ансибла могут иметь политику обновления)
                                0

                                Спасибо за статью. Все мои плейбуки односложные


                                • hosts: nginx
                                  Roles: — nginx
                                  В роли собственно происходит раскатка конфига и поднятие докер контейнера с nginx. Все вроде бы просто. Но так как разные окружения требуют разных портов, разных конфигов того же nginx, то с переменными явно перебор, как красивее организовать хранение переменных? Сейчас это отдельная папка на уровне инвентори. Кстати как проще конфигурить разные окружения? Сейчас это фактически разные инвентори со своими переменными в папке на том же уровне. А про инвентори это yaml или все же ini? Надеюсь в след.статье коснетесь этого. Имхо ini по-приятнее и не надо писать отдельно vars, что придает более табличный вид нежели списочный, сами переменные хостов справа от его объявления.
                                  0

                                  Переменные должны отражать связь между объектами в плейбуке или с внешним миром (если инвентори вам кто-то другой генерирует). Частично вы можете задавать переменные в play (например, связка nginx+grafana использует одни порты, nginx+apache2 — другие). Частично — выносить в group_vars для плейбук (но надо понимать зачем). Если что-то очень специальное для инсталляции — в инвентори. Если "может быть другим, но кому менять не понятно" — в defaults роли, или даже константами.

                                    0
                                    ага, defaults роли — это то что никогда не использовал, но оно точно имеет место, спасибо
                                  0

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


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

                                    0
                                    Плейбуки вроде бы должны декларативно описывать желаемое конечное состояние системы, а в 90% там «bashsible» (почему бы и нет, раз и так работает), который хорошо если два раза подряд можно запустить без проблем.
                                      0

                                      Тут есть большое враньё. Ансибл приложил много усилий, чтобы обмануть нас, но враньё всё равно иногда сквозит.


                                      Любой хэндлер — враньё. Если плейбука сломается между выполнением changed и соответствущим handler, то второй прогон идемпотентно не сделает этот handler.


                                      А причина — в том, что restart не может быть идемпотентный.

                                        0
                                        враньё всё равно иногда сквозит

                                        Для этого есть технический термин: абстракция протекает. :)

                                          0
                                          Этого недостаточно для понимания. К примеру у нас есть роль А с версией 1, которая выполняет задачу Б, тогда когда мы обновим роль до версии 2 и снова её прогоним, мы не приведём состояние к описанному в ансибл, т.к. не учитываем прошлое состояние. Идемпотентность хороша только для immutable servers, тогда ты не сломаешь повторными запусками. А в остальных случаях эксплуатации ты за стейтом не уследишь, не потому что кто-то будет там вручную изменять данные, а потому, что при изменении кода у тебя уже есть предыдущее состояние на сервере.
                                        0

                                        -

                                          0

                                          Спасибо за статью.


                                          А где ещё можно почитать про «вот это лучше делать именно так, потому что …»? Best practices на официальном сайте, увы, не совсем про это.

                                            +1

                                            В блогах у более опытных людей, на конференциях.

                                              0

                                              это слишком общий ответ, хотелось бы конкретики.

                                                +1
                                                В статье «Основы Ansible, без которых ваши плейбуки — комок слипшихся макарон» на хабре
                                                  0
                                                  а где ещё
                                                  +1

                                                  Тогда "нигде". Учитесь сами.

                                                    0
                                                    Учитесь сами.

                                                    это понятно. но иногда стоит и чужой опыт осмысливать

                                                      +2

                                                      Осмысливайте, кто ж вам мешает. Просто "хотелось бы конкретики" звучит как ультиматум "выдать мне качественные учебные материалы по Ансибл".

                                                        0
                                                        звучит как ультиматум

                                                        даже и мысли не было чего-то требовать, только спросил нет ли годных ссылок.
                                                        жаль, что был неправильно понят.

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое