Pull to refresh

Ansible это вам не bash. Сергей Печенко

Reading time 13 min
Views 24K

Предлагаю ознакомиться с расшифровкой доклада 2019 года Сергея Печенко "Ansible — это вам не bash!"



Пара слов обо мне.



Инженер. Кое-что знаю о IT и готов этими знаниями делиться. Нравятся люди, которые приходят с конкретными вопросами; не нравятся люди, которые хотят, чтобы за них кто-то всё сделал.



Краткий план рассказа:


  • Готовим почву.
  • Об императивном стиле («bashsible»).
  • Зачем писать код?
  • Модули, плагины.
  • Шаблон модуля.
  • Рабочий модуль.
  • Рабочий плагин.
  • Используй Jinja, Люк!


Когда мы начинаем работать на Ansible, надо учитывать два важных момента. Эти моменты предохранят вас от боли и унижения при дальнейшей эксплуатации проекта.



Во-первых, структура проекта. В документации Ansible приведены два варианта структур. Первый и второй. Если совсем на память – это второй. Дело в том, что Ansible умеет переменные групп <т.е. group_vars> читать из каталогов. И если мы каждому сервису выдадим свой yaml, нам будет очень удобно и комфортно каким-то образом эти конфигурации версионировать, складывать в Git, проверять их линтерами. Это первый момент. Расположение файликов по каталогам показало себя с самой лучшей стороны. Я занимаюсь Ansible уже около 5 лет, это работает.



Дальше <второй момент> вам понадобится включить конвейеризацию. Начиная с версии 2.1, нам доступна совершенно шикарная вещь – конвейеризация, когда модули летят друг за другом, и все происходит быстро. Поверьте, что вы заметите увеличение скорости работы вашего проекта, заметите увеличение скорости обработки хостов невооруженным глазом, даже без каких-то замеров производительности.


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



Сейчас несколько слов о тёмной стороне – о том, с чем приходят люди в чатик по Ansible @pro_ansible.



Сверху — пара классических императивных заданий. Они, конечно, подражают нормальным плейбукам, но их очень легко отличить от нормальных тасков Ansible. Они не говорят, что должно получится в итоге. Они говорят: «Что делать?». Императивный стиль написания тасков ведёт к хаосу, хаос ведёт к гневу, а гнев на тёмную сторону Силы ведёт.



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


Ansible помогает управлять средой, не пуская результат на самотёк. Он помогает вам контролировать всё, что происходит. Если вы пишете императивно, то это всё благополучно игнорируете. Если вы отказываетесь от сервисов Ansible, то отказываетесь от плюшек, которые он вам даёт, и имеете все минусы. И самое неприятное, когда вы отказались от сервисов – это то, что вы не можете отказаться от ответственности за своё окружение, то есть от того, что на нем происходит, и не можете отказаться от ответственности перед коллегами и командой в организации. Поэтому не используем императивный стиль.


Раз есть тёмная сторона, то есть и светлая.



Чтобы понять, нужно ли нам писать свой код для Ansible, давайте посмотрим на объектную модель Ansible. Она достаточно проста. В базе всего 4 понятия. Это группа, хост, плейбук и плей. Группа – это то, где может быть сколько-то хостов и другие группы. Плейбук – это несколько плейев. По-русски «play» — пьесы, в которые входят действующие лица. «Playbook» — сборник пьес. На слайде видно 2 play в одном playbook.


В Ansible всегда неявно присутствует группа «all». Даже если вы ее нигде не упомянули ни разу, она все равно присутствует. Это заложено в коде Ansible — так он обрабатывает ваш inventory. И в этой группе вы можете назначить переменные, которые получат все хосты. Вы можете положить в эту группу хосты, можете положить другие группы. И самое приятное, что при запуске проекта, при чтении inventory все переменные будут вычислены по иерархии, т. е. <сверху вниз по иерархии> группы all, 2, 3, и для host3 будут валидны вот эти переменные <>. И вы для этого ничего не написали, а только положили файлы в нужный каталог. Неплохо, да?


Мы посмотрели на объектную модель. Давайте разбираться дальше. Как понять – надо ли писать свой код или не надо? А, может быть, нам стандартных модулей будет достаточно? А может, нет? Давайте рассматривать «за» и «против».



Вот список. Я набросал варианты. И сюда вам нужно будет подставить специфику конкретно вашего проекта. Я не предлагаю таблетку, которая работает сразу от всего. Это повод задуматься и принять инженерное решение, которая предполагает, что мы взвешиваем «за» и взвешиваем «против».


И по правилу Парето – 80% случаев покрываются очень простыми решениями и приносят 20% денег, особенно это касается аутсорсинга. А оставшиеся 20% случаев стоят 80% денег. Думайте сами, решайте сами.



Представим, что мы поняли, что придется писать свой код. Ansible предусматривает два варианта подключения своего кода. Код на Python нам придется писать.


Знатоки Python знают, что иногда код на Python требует какие-то модули библиотечные. Ansible для этого тоже предлагает это элегантное решение. Но это небольшой забег вперед.


В итоге мы получаем, что у нас есть два варианта запуска нашего кода на Python, который мы написали. Этот код решает проблему, которая специфична только для нашего проекта. И мы предполагаем, что это в любом случае будет лучше, чем становиться Senoir YAML Developer.


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



Давайте для начала рассмотрим модули. Самый частый случай написания своего модуля, который реализует только те фишки, которые нужны на вашем конкретном проекте, это реализация существующих процессов («у нас на проекте так принято»). И часто бывает так, что по-другому это никак не сделать, либо нужно писать очень много кода с условиями и циклами, хотя достаточно упаковать в модуль.


Но, честно говоря, из тех проектов, которые я видел, это признак некого технического долга. И я видел такое, что достаточно добавить всего лишь одну дополнительную группу в inventory, в тот самый список хостов, и все получается хорошо. И не надо писать простыню на YaML, не надо становиться Senior YaML Developer, не надо получать себе лычку «Я умею писать на Ansible» — оказывается, что достаточно добавить одну группу и две переменные.


Итак, размещение модуля. Ansible предполагает шикарное место, которое работает из коробки, которое не надо нигде прописывать — то есть шикарное место для размещения модуля. Прямо внутри проекта создаем каталог «library». Кладем в него модуль. Все. Именно из этого каталога модуль будет вычитан, склеится с аргументами и дальше, как я описал – передан в Python на враждебный, удаленный хост, где ничего неизвестно и непонятно, и запущен непосредственно там. Удобно.


И если нашему беззащитному Python-модулю что-нибудь нужно «с собой» — какие-то «верные друзья», то мы их кладем в «module_utils». Они будут импортированы, и нам снова для этого не нужно ничего делать. За нас подумали, и это здорово. Это работает так из коробки.



Давайте рассмотрим минимальный модуль. Из чего он состоит? Тут уже говорили, что админы, девопсы – товарищи ленивые. Я тоже ленивый, и это нормально, потому что если бы мы не были ленивыми, то работали бы не в IT, а, например, копали землю. Поэтому я нашел модуль на просторах Интернета. Вот он GPL'ный. На этом слайде маленький кусочек. Минимальный импорт. Если ничего не нашли, то кидаем exception и обрабатываем его.


Почему так? Еще несколько слов об Ansible. В его исходниках тонны информации о том, что уже можно использовать из коробки, т. е. готовые exceptions, готовые словари, готовые типы данных. Поэтому не стесняйтесь смотреть в исходники. Исходники – это лучшая документация. Документация по модулям, о том, как из вызывать и с какими параметрами. Документация генерируется автоматически. И, читая её, вы поймете, как это выглядит в коде, как это работает, как это можно переиспользовать в своих модулях.



Вот из двух страничек модуль. Changed = False. Например, changed – ok. Это делается через флаг. Вот message, который модуль вам говорит. И вот еще приятная плюшка – ваш модуль может что-то положить в общее пространство переменных.


Про переменную тоже отдельно скажу. При старте модуля, при начальной обработке Ansible’ом удаленного хоста все переменные будут «сплющены». Они будут собраны в один словарь «host_vars». И дальше мы везде можем написать «host_vars[имя_переменной]» и получить значение. Так работает Ansible.


Чем нам интересен такой вариант? Тем, что мы можем из модуля, изнутри через ansible_facts добавить что-то в host_vars для конкретного хоста, на котором будет работать этот модуль. Controller получит назад вот эти факты, и добавит host_vars.


Конструкции типа set_fact наверняка кто-то использовал. Есть такая необходимость их использовать. Например, ты там что-то навычислял, а потом надо куда-то сложить. Вы можете это сделать прямо из Python-кода, не записав не единого таска.


Когда мы создаем объект класса AnsibleModule, мы можем ему передать несколько аргументов. Argument_spec – это как раз то самое, что в доке по модулю мы видим. Там написано: «аргумент такой-то, тип такой-то». Именно argument_spec позволяет Ansible понять, какие аргументы ваш модуль ожидает, и каких типов; корректно все это дело проверить на входе, не дав модулю «взорваться» в неподходящий момент. Но опять же за нас все написано, за нас все сделано и приготовлено. Все сделано для того, чтобы нам было хорошо. Вопрос только в том, чтобы это знать и этим пользоваться, и владеть в полной мере.



Это реальный модуль из исходников Ansible. Его история тоже занятна. Достаточно долго, где-то до версии 2.4, не было абсолютно никакого модуля, который вот так, из коробки, работал бы с дашбордами Grafana'вскими. Мы были вынуждены дергать её каким-либо модулем типа «uri», либо писать bashsible, а именно — вызывать shell, curl, и передавать ему JSON c Grafana'вским дашбордом. Нашелся человек, который написал этот модуль, и теперь все могут пользоваться модулем с Grafana'вскими дашбордами.


И, как видите, он из тех же частей состоит. То, где подчеркнуто — это вам ключевые слова для поиска по исходникам, т. е. ищете всё, что заканчивается на «_spec», и находите массу вещей. В доках вы наверняка встречали, что параметры для модуля «file», что называется, ходят вместе: например, owner, group, chmod. И они из модуля в модуль повторяются для всех модулей, которые работают c файлами. И человек его просто вставляет вот таким несложным образом, и сразу получает готовый вариант, который не надо писать. Мы его можем смело использовать.


Словарь, который мы получили — мы с ним как-то работаем и меняем его под требования нашего модуля, потому что не может добрый дядя, который пишет Ansible, знать, как и что у нас на проекте. Мы сами решаем: наш проект – наша ответственность.


Ansible умеет вашему модулю предоставлять для ключевых слов aliases (<псевдонимы>). Будет ли написано «grafana_url» или «url» — без разницы. Главное, чтобы было написано. Вот флажок «required = true». Дальше еще какие-то параметры. Это все параметры.


Здесь вырезан кусок, который относится непосредственно к посылке запросов в Grafana. Почему вырезано? Потому что дикого интереса он не представляет именно с точки зрения доклада. Обычный код.



Вот здесь мы сам объект объявляем с argument_spec, над которым весь предыдущий слайд трудились, возились и добавляли. А здесь мы говорим, что наш модуль не поддерживает check_mode.


А теперь поднимите руки, кто прогоняет свои плейбуки в check_mode? Два человека. Остальным: ребята, как минимум, задумайтесь. Это может вам очень сильно сберечь нервы при отладке, особенно, когда вы в первый раз деплоите на production.


А кто на production деплоит? Круто, молодцы. А почему без check_mode? Об этом нужно задуматься, для того чтобы деплоить на production. У вас всё равно всегда будет первый деплой, и даже если там ещё никого нет — можно что-то сломать и сделать себе неприятно. Например, насоздавать 100 серверов на Амазоне. Зачем? Потому что с параметрами ошибся, а check_mode не стал использовать.


Итак, мы говорим, что этот конкретный модуль не поддерживает check_mode, потому что Grafana его не поддерживает. Но по факту нам никто не мешает предоставлять из своего модуля полный сервис: аккуратно сходить к Grafana, вычислить текущее состояние и сравнить с тем, что мы получили на входе.


И мы видим, что некоторые аргументы должны быть вместе. Мы можем для них сказать «required_together», и Ansible проследит, чтобы в параметрах этого модуля эти конкретные аргументы были только вместе, и никак иначе. Иначе — модуль не запустится, Ansible скажет, что есть ошибка.


И mutually_exclusive, т. е. один из двух аргументов: либо тот, либо другой, но никак не оба. Это предохраняет вас от ваших же ошибок. Здорово, когда мы можем предотвратить банальные опечатки. Самые гениальные деплои рождаются где-нибудь к четырём утра, но не всегда то, что ты запускаешь после гениального деплоя, гениально работает, несмотря на то, что было написано всё красиво.


А дальше мы обрабатываем exception, и в конце сообщаем о полученном результате пользователю.


Фактически — это production в том смысле, что его можно запустить и использовать. Это из исходников Ansible. Я думаю, что вот этим рассказом какое-то непонимание развеял, там нет ничего сложного. В крайнем случае вы всегда можете посмотреть в исходный код.



Дальше у нас есть плагины. Плагинов есть несколько видов. И для того, чтобы они запустились, их надо положить в соответствующий каталог. Есть action plugins, callback plugins и т.д. Это то самое, что «*_plugins» рядом с library со слайда "Размещение модуля". Вот сюда складываются плагины, те самые, которые будут работать локально.


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



Чаще всего используют action-плагин. Он позволяет нам сделать какие-то действия <на контроллере>. Вызывается он очень легко: tasks, assert и параметры. Все, ничего сложного. Вы можете его использовать.



Пример кода плагина. Здесь уже конкретно разобран плагин, в нем нет ничего сложного, но механика чуть-чуть другая. Мы не делаем полноценный Python-код c if __name__ == '__main__', а мы объявляем конкретный производный класс. И все у нас здесь происходит в этом классе. Все сводится к простым вещам: мы делаем импорты, потом объявляем функцию «run». Именно ее Ansible вызовет при вызове данного плагина. И все, т.е. нет прямого запуска кода, мы только объявляем класс производный.


Именно вот этот подчеркнутый import сообщает Ansible, что этот файл – это action-плагин.



Опять же — о нас позаботились, о нас подумали, нам дали готовое решение. За шаблонным кодом магии почти не видно, но она здесь есть. Это — вызов шаблонизатора.



Что за шаблонизатор? Давайте о нем поговорим чуть-чуть поподробней. Называется он Jinja. А точнее Jinja2 (2.10). Именно эта версия подставится на вашу машину, на ваш контроллер, когда вы поставите Ansible.



В минимуме Jinja состоит из очень простых частей. У нас есть выражения, у нас есть операторы. И все.


И самый главный момент – это то, что написано в фигурных скобках, будет выведено, т. е. что в скобках написали, то будет и выведено.


Есть второй вариант – это операторные скобки. Они сами по себе ничего не выводят, но в них можно вызывать какие-то функции Jinja, какие-то промежуточные вычисления, создавать переменные. И самое важное, что эти переменные Ansible не видит, т. е. они существуют только на время исполнения вот этого конкретного файла, во время его шаблонизации.


Кстати, в чатиках мелькают такие штуки, что это Jinja2 Тюринг полный язык программирования. Наверное, можно сделать. Потому что цикл есть, условные выражения есть. Текст мы можем генерировать и можем заставить Jinja обрабатывать результат генерации шаблона собой же.



Типовая задача – это, например, конфигурирование систем мониторинга. Есть какая-то иерархия групп. И получается, что нам нужно с наследованием сделать сложный словарь.


Есть два одинаково неприятных варианта. Первый – это включить cache behavior = merge. В этом случае вы получите некие неожиданные эффекты. Не делайте так. А второй – это использование громоздких конструкций с фильтром «combine». Мы говорим «combine», и некоторые ключи из этого словаря будут переписаны.



Что с этим делать? Мы раскладываем файлы правильным образом с учетом той структуры каталогов, которую я показывал. group_vars/all – это общие переменные, а дальше пошли какие-то частности, т. е. group1, group2.


Затем перебираем все группы хоста, не забываем про группу «all». И погнали – hostvars. Получаем нужный вариант, т. е. какие-то значения, которые будут перечислены в списках, т. е. получаем то самое, что не решается из коробки.



Частая задача №2 – это развесистый словарь. Хочется использовать шаблонизатор, но мы помним, что это так не работает. Как минимум, в именах ключей не может быть никаких шаблонов.



Что с этим делать? Ничего. Собирать YaML ручками с помощью Jinja. Потом вставляем вот такую конструкцию — «from_yaml» — и пишем нужный нам результат.



Сергей, большое спасибо за ваш доклад! Я бы хотел попросить вас рассказать о том, когда вы в Ansible используете плагины, а когда модули. Можете привести боевые примеры?


Пример из не очень давней практики. Если не ошибаюсь, дело это было в прошлом году. В чем смысл? На агентах сборочных, там, где запускался контроллер Ansible, стоял Ansible 2.4. Я сейчас деталей не помню. Но смысл в том, что штатный модуль «uri», который позволяет обращаться к различным REST API, некорректно работал с Consul'ом. Я не мог добиться того, чтобы вариант загрузки key-value также отрабатывал из Ansible. Т. е. у меня разработчики backend сказали, что «надо вот так». Из командной строки с помощью curl — получается, а штатным модуле- не мог сделать. Тогда я просто скопировал модуль в нужное место, в каталог «library», поменял вызов модуля requests и сделал так, как надо разработчикам backend'а. Это до сих пор лежит и деплоит.


По поводу плагина. В одной компании была попытка пересадить сетевых администраторов c Zabbix на что-нибудь другое. И поставили абсолютно невыполнимое требование, чтобы валидировал, что все было правильно написано. Пришлось для этого написать такой плагин. Финал истории очень забавный. Полтора месяца сетевые администраторы не могли доехать <в другой офис>, чтобы посмотреть, как этим пользоваться. А я потом узнал, что они поставили себе Zabbix «под стол».

И еще небольшая ремарка. Вы не упомянули, в чем прелесть Ansible перед остальными системами конфигурации.


Системы управления конфигурациями есть с агентами , и есть без агентов. Агент – это просто какой-то daemon, который висит на хосте и все делает, что ему скомандуют. А «agentless» в Ansible – это когда мы подключились к хосту по SSH, все там сделали и ушли. На хосте больше ничего нет, только его состояние, которое мы прописали в своих скриптах.

Спасибо за доклад. Где лучше запускать плейбук: локально на машине, либо на коллекторе, либо вообще использовать Ansible Tower?


Ansible Tower для многих, а, может, и для всех – это либо это дорогая, либо очень дорогая история.


Там есть сейчас OpenSource версия.


OpenSource версия – это AWX.


Да.


По-честному, таких крутых сервисов, из-за которых стоило бы AWX любить и обожать, и сразу ставить, там нет. Обычный Ansible-плейбук за глаза покрывает все задачи. И либо Ansible интегрируется с существующей системой CI/CD, где мы уже запускаем jobs, применяя Jenkins, например, либо есть такая штука, которая называется Rundeck. Она позволяет очень тонко, с настройками доступа запускать в том числе и shell-команды, к которым ansible-playbook относится.


Если у вас большая команда, много DevOps и все запускают Ansible глубокие, как вы делаете так, чтобы не было конфликтов?


Запускается одна job, например, на Jenkins и устанавливается, что одновременно только одна эта job может исполняться. Одномоментно исполняется только один экземпляр job. Т. е. изменения в любом случае будут катиться последовательно.


А локально как запускать?


Лучше не надо.


Спасибо за интересный доклад! Как вы на клиентах валидируете, как вы проверяете валидацию того, что есть? Особенно, когда приходят новички, они могут что-нибудь наделать. Были такие случаи у нас, когда вся вот эта красивая структура начинает прихрамывает из-за этого.


Во-первых, есть ansible-lint, который позволяет явные глупости затормозить еще в самом начале. Но с другой стороны, он никогда не проверит точные значения параметров. А как оно должно быть на хосте? А как об этом может узнать ansible-lint? Правильный ответ – никак. Нужен код ревью и общая культура разработки.


Т. е. никаких автоматизированных средств не используете?


Есть еще инструмент для тестирования плейбуков — molecule.


Спасибо за доклад! Пару месяцев назад как раз использовал Ansible. И мне надо было убрать маршрутизацию Linux машин. Меня удивило, что нет соответствующего модуля. Я подумал, что это или я что-то делаю нет так, или просто его еще никто не написал.


Скорее, второе. Почему никто не написал, с учетом того, что Ansible, как минимум, 5 лет? Наверно, люди управляют маршрутизацией как-то по-другому. Кстати, сейчас уже даже сетевики начинают пользоваться open-source’ными инструментами для управления конфигурацией сетевого оборудования (типа napalm).


Именно под Linux не было.


Потому что есть Cisco и все остальное, но это мое предположение.


Был ли у вас опыт работы в связке с Terraform и если – да, то какую боль поймали?


У меня был опыт работы Ansible без связки с Terraform. Это был деплой, это был Амазон, это было пять регионов. Когда после двух регионов сказали, что надо еще третий, то я дописал несколько строчек и развернул.


Видео:


Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+19
Comments 22
Comments Comments 22

Articles