Quote
Никогда не было и вот снова
В очередной раз возвращаюсь к своему маленькому бложику. На этот раз после 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 | Не доступны | Доступны |