Pull to refresh

Не используйте теги в Ansible

Level of difficultyEasy
Reading time7 min
Views761

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

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

Итак, лично мое мнение на этот счет такое. Теги в Ansible – это аналог оператора GOTO в инфраструктурном коде. В программировании оператор безусловного перехода (goto) давно считается плохим стилем. Почему? Потому что он позволяет нарушить естественный порядок исполнения кода, делая программу трудно читаемой и сложно поддерживаемой, особенно при масштабировании и росте кодовой базы. В мире Ansible аналогичную роль играют теги. И, хотя инструмент сам по себе удобен и кажется полезным при быстром и разовом решении задач, частое использование тегов наносит больше вреда, чем пользы.

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

Попробуем рассмотреть чуть подробнее каждый из аспектов.

Отсутствие иерархии и дефолтных значений

У переменных Ansible есть довольно мощная многоуровневая иерархия приоритетов. Это позволяет задавать несколько различных слоев значений одних и тех же переменных с разным приоритетом. Вы можете определить значения по умолчанию в defaults роли, переопределить часть переменных на уровне инвентаря (как для групп, так и для отдельных хостов), другую часть переопределить на уровне плейбука, отдельных тасок, ролей и блоков кода. И, наконец, можно любое из нижестоящих значений переопределить с помощью extra-vars из командной строки. Все это интегрировано со стандартными слоями абстракции и объектов Ansible: инвентарь, роль, плейбук, рантайм. Можно взять чужой код, переопределить переменные на свои значения и быть уверенным, что они получат именно их в соответствии с приоритетом.

У тегов в Ansible полностью отсутствует иерархия. Вы не можете просто взять и переопределить или структурировать теги при импорте чужого кода или при попытке интеграции в более крупный проект. Простая логика применения тегов (дизъюнкция — «или») означает, что каждый новый тег просто добавляет ещё одну опцию запуска, а не уточняет логику исполнения.

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

Теги провоцируют императивный подход

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

Предположим, у вас есть набор задач для установки и настройки веб-сервера. Вместо создания сценариев в виде плейбук, где каждая отвечает за отдельную часть процесса (например, nginx_install, nginx_configure, nginx_deploy), вы помечаете разные задачи тегами типа setup, configuration, deployment и т.п. В итоге получается код, который очень сложно поддерживать и масштабировать, так как задачи теряют контекст, превращаясь просто в набор абстрактных команд, запускаемых хаотично.

Экспоненциальный рост сценариев запуска

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

Допустим, у вас есть три задачи с тегами install, configure, start. Если мы используем все три тега в нашем коде автоматизации, то появляется не просто три сценария запуска, а восемькомбинаций: install, configure, start, install&configure, install&start, configure&start, install&configure&start и запуск вообще без тегов. Если вы добавите ещё один тег, количество сценариев возрастёт уже до шестнадцати. Полноценное тестирование всех возможных комбинаций становится крайне сложным и трудозатратным, а ошибки в редко используемых сценариях в будущем неизбежно начнут проявляться и доставлять много проблем. И нет - вы не сможете это закрыть дефолтным сценарием запуска при наличии хотя бы одного тега в командной строке.

Трудности отслеживания различных сценариев запуска

Теги, в отличие от переменных, задаются не в файлах переменных и не в инвентаре, а обычно просто в командной строке (ключ вида --tags mytag1,mytag2 и т.п.) Есть вариант задавать теги непосредственно на плей в директивах плейбука. Но такое применение подразумевает по сути отдельные плейбуки на каждый такой тег. И в таком случае отдельные плейбуки как сценарии запуска можно использовать и без тегов. При запуске же в командной строке у вас сценарий запуска для одного и того же кода может быть каждый раз разные. И вы просто так не можете контролировать это с помощью одного только кода Ansible. Да, можно мониторить спосок ключей запуска, сохранять историю команд и т.п. Но тогда мы плавно движемся от подхода Infrastructure as a code к подходу вида Infrastructure as a bash history.

Проблемы с динамическими инклудами

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

Например, есть плейбук, содержащий динамический include файла с тасками, помеченными тегами update и restart. Вы захотите использовать этот файл в другом плейбуке и будете ожидать, что при запуске с тегами update или restart включённые задачи выполнятся корректно. Но на деле теги не передаются и не обрабатываются автоматически при динамическом инклуде. Вам придётся явно указывать их ещё и в самом инклуде. Любое несоответствие или забытый тег приведут к странному поведению кода, которое будет проявляться только при запуске с определённым сочетанием тегов. Это превращает отладку в мучительный процесс, не говоря уже о читаемости и удобстве.

А как же без тегов?

Спросите вы. Да довольно легко. Достаточно просто изначально при составлении кода думать над тем, какие сценарии запуска предполагаются. И соответствующим образом организовывать кодовую базу. В идеале на каждый сценарий запуска лучше делать отдельный плейбук. Например, у вас есть сценарий разворачивания приложения с нуля со всей сопутствующей обвязкой, есть сценарий только апгрейда версий пакетов, есть сценарий апгрейда конфигурации файлов. Можно выделить три условных плейбука:

  • app_full_deploy

  • app_packages_upgrade

  • app_config

В эти плейбуки вы можете выборочно включать нужные файлы тасков или задавать гибкие критерии их запуска, используя различные инструменты ветвления кода (when, with_first_found, incude_tasks, tasks_from для ролей и т.п.) При этом вы сохраняете полный контроль над кодом на уровне плейбук, можете управлять всеми вариантами запуска прозрачно через одни и те же параметры и можете полноценно интегрировать свой код в иерархию различных переменных на уровне инвентаря, других плейбук и т.п. При этом вы получаете практически такую же гибкость применения сценариев, но без минусов тегов, которые перечислены выше. Лично я использую именно такой подход в своих проектах в течение многих лет и пока ни разу об этом не пожалел. А вот когда приходится включать чью-то чужую кодовую базу, построенную на логике применения тегов, это всегда вызывает проблемы с интеграцией и лишние усилия по адаптации кода и тестированию валидности различных сценариев.

А, может, все-таки теги где-то могут быть полезны?

В принципе, могут. Лично я стараюсь избегать таких способов организации кода, но допускаю, что в каких-то случаях они могут быть нормальным выбором. Например, если у вас сценарий автоматизации не предполагает интеграции с другими командами. Или у вас весь сценарий автоматизации представляет собой линейный плейбук с простыми независимыми тасками, которые можно легко и независимо исполнять в различном сочетании. В таком случае применение тегов может быть относительно безболезненным и многих проблем, скорее всего, удастся избежать. Это не отменяет прочих вышеперечисленных минусов, но и какие-то плюсы в виде более легкой структуры кода тоже, вероятно, будут.

Также теги могут быть полезны при тестировании и отладке кода. Например, если вам нужно в текущий момент для тестов или дебага выполнить только 2-3 таска из большого плейбука или роли со множеством других тасков. Тогда действительно можно просто пометить выбранные таски нужным тегом и запускать плейбук только с ним. Это позволяет легко тестировать нужные фрагменты кода с минимальными усилиями.

Также теги позволяют помечать определенные таски в служебных целях - например, для пропуска определенных тестов в сценариях Molecule или для ansible-lint.

Во всех прочих случаях я бы рекомендовал избегать использования тегов как инструмента разделения сценариев запуска.

А что думаете на этот счет вы? Можете поделиться своим мнением в комментариях.

Tags:
Hubs:
+5
Comments9

Articles