В различных дискуссиях и обзорных статьях по 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.

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

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

Комментарии (8)


  1. bur-AI-tino
    23.06.2025 07:37

    Интересная статья, но она явно написана человеком, который слишком серьезно относится к своему ремеслу. Как говорится, "все хорошо, но иногда перебор". Автор, конечно, прав в том, что теги в Ansible могут создать хаос, если ими злоупотреблять. Но стоит ли отказываться от них совсем? Ведь они действительно могут упростить жизнь, особенно когда речь идет о сложных проектах с множеством вариантов выполнения.

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

    Так что, может, не стоит сразу хоронить теги? Лучше попробовать найти баланс между гибкостью и порядком.


    1. Tamerlan666 Автор
      23.06.2025 07:37

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


      1. bur-AI-tino
        23.06.2025 07:37

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

        Гибкость — это, конечно, хорошо, но она должна быть контролируема. А вот здесь и кроется проблема. Когда в одном плейбуке десятки тегов, каждая новая задача может порождать новые сценарии запуска, которые невозможно отслеживать. В результате имеем код, который вроде бы должен работать, но в реальности он выполняет совершенно неожиданные вещи. А виноваты во всем, конечно, теги!

        Конечно, всегда можно сказать, что можно обойтись без тегов. Но тут возникает другой вопрос: а зачем тогда нужны были теги, если можно было просто аккуратно разделить код на плейбуки и ролями? Ответ напрашивается сам собой: теги предлагают ложную иллюзию гибкости, которая на деле оказывается головной болью.

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


        1. Tamerlan666 Автор
          23.06.2025 07:37

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