Pull to refresh

Comments 66

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

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

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


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


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

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


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

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

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

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


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

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

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

Если вы написали хотя бы одну плейбуку в своей жизни и первый раз слышите слово "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 делают все. Я тоже делал, и мне потребовалось несколько лет, чтобы почувствовать дзен простого Ансибла.

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


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

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

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

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


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

image

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

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


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

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

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


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


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

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

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

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

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


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


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

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


Сейчас я сходу ткнулся — 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 я это считать не могу.


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

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

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

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

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


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


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

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

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). Это было давно, возможно, там у меня грязный Ансибл внутри.


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

У меня есть пример, где использование flush кажется уместным. Роль (допустим из galaxy) настраивает некий сервис, в ней предусмотрен хэндлер reload при изменении конфига. Хочу прогонять play по одному хосту, делая паузу и ожидания от оператора, что сервис с новым конфигом стартовал и работает ОК. Получается такой плей:


serial: 1

roles:
  - setup_service

post_tasks:
  - name: Flush handlers (e.g. reload service when needed)
    meta: flush_handlers

  # FIXME: don't ask for confirmation after last host in batch
  - pause:
      prompt: "Check that SERVICE is properly running on {{ inventory_hostname }} before we proceed to next host. Type YES when done."
    register: prompt_result
    failed_when: 'prompt_result.user_input != "YES"'

без flush после прогона первого хоста prompt возникает без reload сервиса.

У вас будет flush после выполнения роли и так и так. Перечитайте: flush делается после выполнения каждой секции — pre_tasks, roles и post_tasks.


Т.е. в вашем случае первая post_tasks 'meta: flush_handlers' всегда ничего не делает. А flush делается по факту завершения списка ролей.


(хотя, возможно, с serial: 1 это не так, но я бы счёл это багой).

Ну вот может бага serial:1, но пока я не добавил flush, для сервиса не вызывался reload перед pause.

Аж перепроверил. Неправда, всё выполняется как я написал. (2.9.16)


├── play.yaml
└── role1
    ├── handlers
    │   └── main.yaml
    └── tasks
        └── main.yaml

find -type f -name "*.yaml"|xargs -n1 --verbose cat
cat ./play.yaml
---
- hosts: localhost,127.0.0.1
  gather_facts: false
  serial: 1
  roles:
    - role1
  post_tasks:
    - pause:
       prompt: "Check that SERVICE is properly running on {{ inventory_hostname }} before we proceed to next host. Type YES when done."
      register: prompt_result
cat ./role1/tasks/main.yaml
---
- debug:
  changed_when: true
  notify: h1
cat ./role1/handlers/main.yaml
---
- name: h1
  debug: msg=handler

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

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

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


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

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

ага, defaults роли — это то что никогда не использовал, но оно точно имеет место, спасибо
UFO just landed and posted this here
Плейбуки вроде бы должны декларативно описывать желаемое конечное состояние системы, а в 90% там «bashsible» (почему бы и нет, раз и так работает), который хорошо если два раза подряд можно запустить без проблем.

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


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


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

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

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

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

Большая ошибка тут в том что вы считаете Ансибл декларативным — декларативна каждая таска в отдельности, плейбук — императивен.

Это зависит от того, как вы его написали. Если у вас нет неявных императивных зависимостей между тасками, то плей будет декларативной. Вот если у вас постоянные when с отсылкой на register от предыдущей таски — вот это да, замаскированное написание модулей для ансибла на ансибле. Такого надо избегать.

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


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

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

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

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

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

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

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

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

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

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

Статья очень грамотная, но так и не понял одного — почему в одной play не может быть и roles и tasks секций.
В официальной документации есть четкий порядок исполнения секций: сначала roles, потом tasks.

По формату — может. Если же вы хотите писать хорошо читаемые play, то если у вас есть roles — не используйте tasks, а только pre_tasks и post_tasks. Причина — каждый читающий play с комбинацией tasks и roles должен будет вспоминать, что исполняется первым.

Sign up to leave a comment.

Articles