Никогда не было и вот снова
В очередной раз возвращаюсь к своему маленькому бложику. На этот раз после 4-х летнего перерыва. Будем считать на это повлияли смена работы и самоизоляция. Продолжить я решил с небольшой заметки про нежно любимый мной Ansible. А точнее про его волшебные особенности работы с переменными и ролями. Мне всегда хотелось видеть в ролях Ansible жесткую изоляцию. Но это конечно совсем не так. Разберу на примере, то что я имею в виду, основываясь на поведении Ansible 2.9.7.
Ansible я очень активно использовал на работе в Qligent для автоматизации ручных инструкций по установке продукта. В результате за 3-4 года сформировалась достаточно большая база кастомных ролей и плейбуков. Ну а сейчас я использую Ansible исключительно для настройки домашнего HP MicroServer, на котором крутится парочка локальных сервисов в docker контейнерах. Каждый сервис фактически представляет собой docker-compose.yml
, специфичные конфиги и директории, с которыми он работает.
Итак, во время написания ролей для локальных сервисов я решил вынести повторяющиеся действия необходимые для инициализации в отдельную роль. Назовем ее puzan_service
. Туда попали:
- создание отдельного пользователя
assert
'ы- создание директории с именем сервиса
- определение необходимых фактов
Ну и роль сама получила как минимум один параметр, который определяет ее поведение - puzan_service_name
. В упрощенном варианте это имя директории, в которую в последующем копируется compose
файл. Теперь для инициализации сервисов достаточно в meta
добавлять нечто вроде:
dependencies:
- role: puzan_service
vars:
puzan_service_name: usefull_service
Примерно тут мои ожидания несколько расходятся с реальностью. Указанный подход отлично работает. Более того на текущий момент похоже это официальный подход для подключения ролей с параметрами в качестве зависимостей. Но есть нюанс — после выполнения роли в контексте останется переменная puzan_service_name
. В целом может показаться, что это не страшно. Но меня это не устраивает: puzan_service
предназначена для множественного использования, а это значит каждый ее запуск не должен быть связан с предыдущим. В текущем примере puzan_service_name
обязательный параметр и его каждый раз необходимо определять. Но что, если у роли будет еще необязательный параметр или кто-то допустит опечатку в имени переменной? Это приведет к неожиданному поведению и вероятно сложному поиску причины ошибки, так как роль может выполнится со старыми значениями переменных.
Я начал работать с Ansible где-то около версии 1.9. И как ни странно там был немного другой метод определения зависимостей с переменными:
dependencies:
- role: puzan_service
puzan_service_name: usefull_service
Данный метод также до сих пор работает. Но более удивительно то, что он работает, как я хочу: puzan_service_name
не попадает в глобальный контекст. Я подготовил небольшой репозиторий с тестами данного поведения. Кроме использования meta dependencies
там также продемонстрирована работа использования ролей в playbook'ах, а также разница поведения import_role
и include_role
.
Следует отменить, что из всех конструкций только include_role
на текущий момент имеет явный механизм контроля доступа к переменным снаружи. Это реализовано через параметр public
.
Уже давненько имеются issues на github'е на эту тему:
Исправляться они не спешат. Один из моих PR'ов в ansible, был замержен всего лишь за 3 года. Поэтому я не думаю, что тут что-то быстро изменится.
В целом мои рекомендации свозятся к следующему:
- Используйте только вариант передачи параметров в роль без
vars
- Не используйте
import_role
иinclude_role
сpublic: yes
- Есть конечно исключения: В любой непонятной ситуации — думай!
В итоге. Будьте внимательны и пишите больше тестов. Даже на роли в Anisble. Переменные и факты в Ansible здоровенная перемешенная куча. Ansible к сожалению сам не дает никаких явных инструментов по ограничению доступов к ним. Поэтому тут надежда только на Вас самих и на практики, которые вы используете. Ниже небольшая сводка по описанному поведению для версии Ansible 2.9.7
-
Использование в playbook'ах и meta
dependencies
Поведение одинаковое при подключении ролей в playbook'ах и через
dependencies
.-
Без
vars
Playbook пример:
- name: Playbook hosts: localhost roles: - role: some_role some_role_var: some
Meta role пример:
dependencies: - role: some_role some_role_var: some
some_role_var
не доступна во внешнем контексте до и после запуска роли -
C
vars
Playbook пример:
- name: Playbook hosts: localhost roles: - role: some_role vars: some_role_var: some
Meta role пример:
dependencies: - role: some_role vars: some_role_var: some
some_role_var
доступна во внешнем контексте до и после запуска роли
-
-
Использование
import_role
- import_role: name: some_role vars: some_role_var: some
some_role_var
доступна во внешнем контексте до и после запуска роли -
Использование
include_role
сpublic: no
- include_role: name: some_role public: no vars: some_role_var: some
some_role_var
не доступна во внешнем контексте до и после запуска роли -
Использование
include_role
сpublic: yes
- include_role: name: some_role public: yes vars: some_role_var: some
some_role_var
не доступна во внешнем контексте до запуска роли, но доступна после.
И в виде таблички:
Кейс | Переменные до | Переменные после |
---|---|---|
roles без vars |
Не доступны | Не доступны |
roles с vars |
Доступны | Доступны |
import_role |
Доступны | Доступны |
include_role , public: no |
Не доступны | Не доступны |
include_role , public: yes |
Не доступны | Доступны |
Comments
comments powered by Disqus