Роли#
В прошлом разделе мы разобрались с использованием include. Это был первый способ разбития playbook на части. В этом разделе мы рассмотрим второй способ — роли.
Роли это способ логического разбития файлов Ansible. По сути роли это просто автоматизация выражений include, которая основана на определенной файловой структуре. То есть, нам не нужно будет явно указывать полные пути к файлам с задачами или сценариями, а достаточно лишь соблюдать определенную структуру файлов.
Но, засчет этого, работать с Ansible намного удобней. И у нас рождается модульная структура, которая разбита на роли, например, на основе функциональности.
Для того, чтобы мы могли использовать роли, нужно соблюдать определенную структуру каталогов:
├── all_roles.yml ├── cfg_security.yml ├── cfg_ospf.yml | └── roles ├── ospf │ ├── files │ ├── templates │ ├── tasks │ ├── handlers │ ├── vars │ ├── defaults │ └── meta └── security ├── files ├── templates ├── tasks ├── handlers ├── vars ├── defaults └── meta
Первые три файла — это playbook. Они используют созданные роли.
Например, playbook all_roles.yml выглядит так:
--- - name: Roles config hosts: cisco-routers gather_facts: false connection: local roles: - security - ospf
Остальные файлы: инвентарный, конфигурационный файл Ansible и каталоги с переменными, находятся в тех же местах (в том же каталоге, что и playbook).
Все роли, по умолчанию, должны быть определены в каталоге roles:
- Каталоги следующего уровня определяют названия ролей
- В примере выше, созданы две роли: ospf и security
- Внутри каждой роли могут быть указанные каталоги.
- Как минимум, понадобится каталог tasks, чтобы описать задачи, а все остальные каталоги опциональны.
- Внутри каталогов tasks, handlers, vars, defaults, meta автоматически считывается всё, что находится в файле main.yml
- если в этих каталогах есть другие файлы, их надо добавлять через include
- Внутри роли, на файлы в каталогах files, templates, tasks можно ссылаться не указывая путь к ним (достаточно указать имя файла)
Каталоги внутри роли:
- tasks — если в этом каталоге существует файл main.yml, все задачи, которые в нем указаны, будут добавлены в сценарий
- если в каталоге tasks есть файл с задачами с другим названием, его можно добавить в роль через include, при этом не нужно указывать путь к файлу
- handlers — если в этом каталоге существует файл main.yml, все handlers, которые в нем указаны, будут добавлены в сценарий
- vars — если в этом каталоге существует файл main.yml, все переменные, которые в нем указаны, будут добавлены в сценарий
- defaults — каталог, в котором указываются значения по умолчанию для переменных. Эти значения имеют самый низкий приоритет, поэтому их легко перебить, определив переменную в другом месте. Если в этом каталоге существует файл main.yml, все переменные, которые в нем указаны, будут добавлены в сценарий
- meta — каталог, в котором указаны зависимости роли. Если в этом каталоге существует файл main.yml, все роли, которые в нем указаны, будут добавлены в список ролей
- files — каталог, в котором могут находиться различные файлы. Например, файл конфигурации
- templates — каталог для шаблонов. Если нужно указать шаблон из этого каталога, достаточно указать имя, без пути к файлу
Пример использования ролей#
Рассмотрим пример использования ролей.
Структура каталога 8_playbook_roles выглядит таким образом:
├── ansible.cfg ├── myhosts | ├── all_roles.yml ├── cfg_initial.yml ├── cfg_ospf.yml | ├── group_vars │ ├── all.yml │ ├── cisco-routers.yml │ └── cisco-switches.yml ├── host_vars │ ├── 192.168.100.1 │ ├── 192.168.100.100 │ ├── 192.168.100.2 │ └── 192.168.100.3 | └── roles ├── ospf │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ └── ospf.j2 ├── security │ └── tasks │ └── main.yml └── usability └── tasks └── main.yml
Файл конфигурации Ansible, инвентарный файл и каталоги с переменными остались без изменений.
Добавлен каталог roles, в котором находятся три роли: usability, security и ospf.
Для ролей usability и security создан только каталог tasks и в нем находится только один файл: main.yml.
Содержимое файла roles/usability/tasks/main.yml:
--- - name: Global usability config ios_config: lines: - no ip domain lookup provider: ">" - name: Configure vty usability features ios_config: parents: - line vty 0 4 lines: - exec-timeout 30 0 - logging synchronous - history size 100 provider: ">"
В нем находятся две задачи. Они достаточно простые и должны быть полностью понятны.
Обратите внимание, что в файле определяются только задачи. К каким хостам они будут применяться, будет определять playbook, который будет использовать роль.
Содержимое файла roles/security/tasks/main.yml также должно быть понятно:
--- - name: Global security config ios_config: lines: - service password-encryption - no ip http server - no ip http secure-server provider: ">" - name: Configure vty security features ios_config: parents: - line vty 0 4 lines: - transport input ssh provider: ">"
Несмотря на то, что функционал достаточно простой и общий, мы разделили его на две роли. Такое разделение позволяет более четко описать цель роли.
Теперь посмотрим как будет выглядеть playbook, который использует обе роли (файл cfg_initial.yml):
--- - name: Initial config hosts: cisco-routers gather_facts: false connection: local roles: - usability - security
Теперь запустим playbook (предварительно на маршрутизаторах сделаны изменения):
$ ansible-playbook cfg_initial.yml

Обратите внимание, что теперь, когда задачи выполняются, перед именем задачи написано имя роли:
TASK [usability : Configure vty usability features]
Теперь разберемся с ролью ospf. В этой роли используется несколько файлов.
Файл roles/ospf/tasks/main.yml описывает задачи:
--- - name: Collect facts ios_facts: gather_subset: - "!hardware" provider: ">" - name: Set fact ospf_networks set_fact: current_ospf_networks: ">" - name: Show var current_ospf_networks debug: var=current_ospf_networks - name: Config OSPF ios_config: src: ospf.j2 provider: ">" notify: save config - name: Write OSPF cfg in variable ios_command: commands: - sh run | s ^router ospf provider: ">" register: ospf_cfg - name: Show OSPF cfg debug: var=ospf_cfg.stdout_lines
Разберемся с содержимым файла:
- Сначала мы собираем все факты об устройствах, кроме hardware.
- Затем вручную устанавливаем факт current_ospf_networks
- фильтруем конфигурацию устройства и находим все строки с командами network . area 0 . Всё, что находится между указанными словами, запоминается.
- в итоге, мы получим список с командами
- Следующая задача показывает содержимое переменной current_ospf_networks
- Задача «Config OSPF» настраивает OSPF по шаблону ospf.j2
- если изменения были, выполняется handler save config
- Последующие задачи выполняют команду sh run | s ^router ospf и отображают содержимое
- name: save config ios_command: commands: - write provider: ">"
router ospf 1 router-id mgmnt_ip >> ispf auto-cost reference-bandwidth 10000 % for ip in ansible_net_all_ipv4_addresses %> network ip >> 0.0.0.0 area 0 % endfor %> % for network in current_ospf_networks %> % if network.split()[0] not in ansible_net_all_ipv4_addresses %> no network network >> area 0 % endif %> % endfor %>
В шаблоне мы используем переменные:
- mgmnt_ip — определена в соответствующем файле каталога host_vars/
- ansible_net_all_ipv4_addresses — эта переменная содержит список всех IP-адресов устройства. Это факт, который обнаруживается благодаря модулю ios_facts
- current_ospf_networks — факт, который мы создали вручную
Получается, что в шаблоне настраиваются команды network, на основе IP-адресов устройства, а затем удаляются лишние команды network.
Проверим работу роли на примере такого playbook cfg_ospf.yml:
--- - name: Configure OSPF hosts: 192.168.100.1 gather_facts: false connection: local roles: - ospf
Начальная конфигурация R1 такая (две лишних команды network):
R1#sh run | s ^router ospf router ospf 1 router-id 10.0.0.1 ispf auto-cost reference-bandwidth 10000 network 10.1.1.1 0.0.0.0 area 0 network 10.10.1.1 0.0.0.0 area 0 network 192.168.100.1 0.0.0.0 area 0 network 192.168.200.1 0.0.0.0 area 0 R1#show ip int bri | exc unass Interface IP-Address OK? Method Status Protocol Ethernet0/0 192.168.100.1 YES NVRAM up up Ethernet0/1 192.168.200.1 YES NVRAM up up
Теперь запустим playbook и посмотрим удалятся ли две лишние команды:
$ ansible-playbook cfg_ospf.yml

Обратите внимание, что до выполнения конфигурации было 4 команды network (мы их видим по содержимому переменной current_ospf_networks):
"current_ospf_networks": [ "10.1.1.1 0.0.0.0", "10.10.1.1 0.0.0.0", "192.168.100.1 0.0.0.0", "192.168.200.1 0.0.0.0" ]
А после конфигурации, осталось две команды network:
"ospf_cfg.stdout_lines": [ [ "router ospf 1", " router-id 10.0.0.1", " ispf", " auto-cost reference-bandwidth 10000", " network 192.168.100.1 0.0.0.0 area 0", " network 192.168.200.1 0.0.0.0 area 0" ] ] **Note** Этот пример не идеален. Например, подразумевается, что все интерфейсы находятся в зоне 0. Но его достаточно, чтобы понять как использовать роли.
Скорее всего, в реальной жизни вы уберете задачи, которые отображают содержимое переменных. Но, для того чтобы лучше разобраться с тем, что делает роль, они полезны.
На этом мы заканчиваем раздел. О других возможностях использования ролей вы можете почитать в документации, в разделе роли.
Ansible: изучаем и создаем Роли (Roles). Lesson 8

Ранее мы использовали Playbook для работы с серверами. Пора изучить Ansible Roles т.к. это считается более профессиональная запись Playbook. В нашем случае мы переходим в следующий каталог и создаем там роль:
Разберем подробнее, каждый элемент (пока они пустые, шаблоны):
- defaults — размещение defaults переменных для вашей роли
- files — файлы которые копируем
- handlers — разместить главный лист handlers
- meta — .
- README.md — файл с инструкцией
- tasks — разместить главный лист tasks
- templates — файлы копированные и генерированные
- tests — .
- vars — размещение других переменных для вашей роли
* многие каталоги вверху часто и не нужны, далее их будем создавать сами.
Начнем перенос в Роль:
1. Переносим файлы
sudo mv MyWebFiles/*.html roles/install_apache2/files
sudo mv MyWebFiles/*.png roles/install_apache2/files
В итоге у нас будет:
2. Переносим vars
Так как файлы мы уже перенесли, строчку «source_folder» нам больше не надо. Переносим только «destin_folder: /var/www/html» в файл «vars -> main.yml». Сам «vars:» нам ненужен.
Копируем данные в этот файл:
3. Переносим handlers
Открываем файл:
Копируем данные в этот файл:
4. Переносим tasks
Открываем файл:
Копируем данные в этот файл и немного меняем (см. в коде):
5. Исправляем основной Playbook
Возвращаемся к исходному файлу и убираем остатки:
в итоге получится чистый файл install_apache.yml:
Чтобы запустить этот файл с ролью, дополняем в него следующее:
Запускаем наш playbook с roles:
Все работает. Спасибо всем!
Источник: http://linuxsql.ru
Роли — Основы автоматизации в Ansible
Задачи, которые решаются с помощью Ansible, часто повторяются у разных людей и команд. Как и в случае программирования, где общий код выделяют в библиотеку, Ansible выделяет повторяющиеся вещи в роли. Эти роли выкладываются в общий каталог , через который любой человек может найти себе готовое решение по установке и настройке чего-либо.
Роль представляет собой набор задач или обработчик переменных, файлов и других артефактов, которые распространяются и подключаются как единое целое к плейбуку. Обычно, роли выполняют достаточно высокоуровневые задачи, например установку баз данных, веб-серверов и тому подобное. Иногда они автоматизируют работу с каким-то низкоуровневым сервисом, который не встроен в сам Ansible.
Возьмем для примера веб-сервер Nginx. Для его установки в Ansible Galaxy можно найти множество ролей от разных разработчиков, среди которых есть и официальная, разрабатываемая компанией NGINX. Вот ее страничка. Здесь мы видим описание и инструкцию для установки. Разберем их.
Первый шаг – установка роли. Она выполняется с помощью команды ansible-galaxy :
install nginxinc.nginx
Имя роли состоит из двух частей. Неймспейс, который обычно совпадает с именем пользователя на github и собственно имени роли, в данном случае nginx. Таким образом избегаются коллизии имен.
Второй шаг — подключение. Роли добавляются в плейбук через ключ roles :
- hosts: all roles: - role: nginxinc.nginx tasks: .
Ansible начинает выполнять роли до tasks независимо от порядка организации верхнеуровневых ключей в плейбуке. Но иногда требуется выполнить какие-то подготовительные действия до выполнения роли. В таком случае есть два варианта.
-
Добавить подготовительные задачи в pre_tasks :
- hosts: all pre_tasks: # Выполняются до ролей # Тут список задач - name: Какая-то задача ansible.builtin.shell: # Делаем что-нибудь roles: - role: nginxinc.nginx - role: # какая-нибудь другая роль tasks: # Выполняются после ролей # Тут список задач
- hosts: all tasks: - name: Какая-то задача ansible.builtin.shell: # Делаем что-нибудь - name: Ставим nginx через роль import_role: name: nginxinc.nginx - name: Какая-то задача ansible.builtin.shell: # Делаем что-нибудь
Конфигурация ролей
Роли стараются делать достаточно общими, чтобы их можно было использовать в разных ситуациях. Для этого внутри роли определяются переменные, с помощью которых можно управлять поведением, например, способом установки, версией пакета и так далее. Переменные, которые для этого созданы можно найти в директории defaults/ в репозитории самой роли. Пример из Nginx:
# Enable NGINX and NGINX modules. # Variables for these options can be found below. # Default is true. nginx_enable: true # Print NGINX configuration file to terminal after executing playbook. nginx_debug_output: false # Install NGINX Dynamic Modules. # You can select any of the dynamic modules listed below. Beware of NGINX Plus only dynamic modules (these are marked). # Format is list with either the dynamic module name or a dictionary (see njs for an example). # When using a dictionary, the default value for state is present, and for version it's nginx_version if specified. # Default is an empty list (no dynamic modules are installed). nginx_modules: [] # - auth-spnego # NGINX Plus # - brotli # NGINX Plus # - cookie-flag # NGINX Plus # - encrypted-session # NGINX Plus # - geoip
Переопределяются эти переменные через ключ vars :
# roles - hosts: all roles: - role: nginxinc.nginx vars: nginx_debug_output: true nginx_modules: - geoip # import_role - hosts: all tasks: - import_role: name: nginxinc.nginx vars: nginx_debug_output: true nginx_modules: - geoip
Автоматическая установка
Ansible умеет автоматически устанавливать роли. Для этого создается файл requirements.yml, например, в том месте где запускается ansible . Туда добавляется список нужных ролей:
roles: # Install a role from Ansible Galaxy. - name: geerlingguy.java version: 1.9.6
Затем выполняется установка:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
[Basics] Ansible Roles

Привет, %username% ! Я уже не первый раз вспоминаю про такой классный инструмент, как Ansible. Давай в этот раз посмотрим на такую штуку как Ansible Roles – роли в Ansible.
Что такое Ansible?#
О том, что такое Ansible написано уже множество статей в сети. Про основные понятия ansible я уже писал – там все просто. Но на всякий случай быстренько пробежимся по основным моментам.
Ansible – это система управления конфигурациями. Если перевести на простейший язык, то ansible – это такая штука, которая позволяет воспроизводить конфигурацию на множестве схожих систем.
Давай разберем на примере. Представь, что у тебя один твой любимый серверочек и ты его долго настраивал: устанавливал пакеты руками, правил конфиги, настраивал красивенький motd , чтоб при логине в систему тебе показывалось всякое разное и полезное. И вот в один прекрасный (или не очень) день, по не зависящей от тебя причине твой “огородик” сдох, а вместе с ним и его бэкапы (свежа еще история в памяти о пожаре в ДЦ). В обычной ситуации ты просто найдешь другой сервер и будешь снова все настраивать руками. Будешь делать все в точности как и было, но немного другое – некоторые вещи становятся не актуальными, а некоторые ты просто забываешь со временем. По итогу ты получаешь хоть и похожий, но немного другой “огородик”.
В альтернативной ситуации, с точно таким же пожаром, но используя Ansible, ты развернешь “идентичный огородик” – даже “сорняки” будут на тех же местах. И, что не менее важно, ты это сделаешь в разы быстрее, чем руками.
Состав роли#
И так, давай посмотрим из чего должна состоять хорошая роль. Для того чтобы посмотреть на “дефолтную роль”, достаточно выполнить инициализацию роли с помощью утилиты ansible-galaxy :
ansible-galaxy init jtprogru.packages - Role jtprogru.packages was created successfully
В данном примере создается роль под названием jtprogru.packages – директория с именем jtprogru.packages будет создана в текущей директории откуда запускалась ansible-galaxy . Вот так выглядит структура файлов и директорий:
tree -a jtprogru.packages jtprogru.packages ├── defaults ├── files ├── handlers ├── meta ├── tasks ├── templates ├── tests └── vars 8 directories, 9 files
Немного подробнее по некоторым пунктам:
- defaults – Директория с дефолтными значениями некоторых переменных;
- files – Тут положено складывать все файлики, которые необходимы (бинари, готовые конфиги, публичные сертификаты и ключи);
- handlers – Тут хранятся хендлеры, которые должны реагировать на выполнение задач;
- meta – Метаинформация о роли (автор, организация, etc);
- tasks – Тут хранятся задачи из которых состоит роль;
- templates – Тут хранятся шаблоны в формате Jinja2;
- tests – Тесты! Да, роль – это “код”, а любой код должен быть покрыт тестами;
- vars – Общие переменные для роли, например OS-specific переменные;
Почти в каждой директории создается файл main.yml – это своего рода index.html – входная точка для каждого компонента роли.
Давай рассмотрим чуть подробнее некоторые моменты.
defaults#
Тут складываются те переменные, которыми можно и нужно управлять снаружи – из групповых перменных. В этой директории размещаются те переменные, которые очень желательно переопределять на уровне инвентаря, но значения указанные для них являются валидными и роль может отработать корректно.
vars#
Сюда складываешь все OS-specific переменные и/или те переменные, которые не требуется показывать внешнему пользователю твоей роли. К примеру: у тебя есть большой шаблон, который внутри использует мешок путей из файловой системы в рамках домашней директории. Ты точно знаешь, что иногда меняется пользователь и соответственно его “хомяк”. В defaults указываешь просто my_role_base_dir: «>» и все. А в vars уже составляешь пути на основании my_role_base_dir – таким образом ты точно будешь уверен в том, что случайно не поменяются директории и они всегда будут там, где они должны быть.
handlers#
Хэндлеры — это такие же таски, но запускаются только в случае если произошли изменения и их вызвали. Простой пример: после настройки Nginx’a тебе надо бы проверить валидность нового конфига через sudo nginx -t и если все “ОК”, то корректно применить его через sudo nginx -s reload . Вот для таких ситуаций и нужны хендлеры – выполнять какие-либо задачи в случае изменения конфигураций на управляемом хосте. По сути, хендлеры — это такие же задачи, а главное преимущество хендлеров в том, что они запускаются только в случае наличия изменений. Т.е. если у тебя конфиг не изменился – твой Nginx не будет перезапускаться.
tasks#
Основное место, где описываются все задачи, из которых состоит роль. Все шаги, которые ansible должен выполнить для описываются именно тут. Описывать можно как одном файле в виде гигантской простыни, так и в десятке файлов. Главное чтобы тебе и твоим коллегам было логично и понятно.
templates#
Шаблоны любых файлов в формате Jinja2 складываешь в этой директории и встроенный модуль template будет искать эти шаблоны в этой директории если не указать иное.
files#
Самое простое место для хранения какого-либо файла. Например какая-то сложно-собираемая и трудно-доставаемая зависимость строго определенной версии в виде бинарника может спокойно лежать тут и не отсвечивать. Ты просто будешь копировать этот файлик туда, куда это требуется.
meta#
Nребуется использовать в том случае, когда ты собираешься распростарнять свою роль через Ansible Galaxy или просто начинаешь вести управление ролями через “внутренний galaxy” (заводишь отдельный репозиторий под каждую роль и подключаешь строго определенные версии ролей в инфра-репозиторий). Считается хорошим тоном заполнять данный раздел и указывать в нем зависимости от других ролей. При установке такой роли через ansible-galaxy install -r requirements.yaml ее зависимость будет так же установлена без явного указания в файле requirements.yaml .
test#
Лично я не использую данную директорию для хранения тестов, потому что привык к отдельному инструменту тестирования ролей – Molecule и его тесты хранятся рядом в директории molecule .
Практика#
Немного о том, что и за что отвечает я тебе рассказал. Теперь я тебе расскажу свой подход к разработке ролей, как я стараюсь делать и как я не делаю, а главное поясню “почему именно так, а не иначе”.
Инициализация роли#
Если речь идет о не публичной роли (которую я не буду публиковать в Ansible Galaxy или хоть как-то показывать кому-то кроме себя), то тут все просто:
cd roles mkdir jtprogru.my_new_role/defaults,handlers,tasks,templates> vim . git init/add/commit/push
Если речь идет о публичной роли (которую я буду публиковать в Ansible Galaxy), то тут можно воспользоваться утилитой ansible-galaxy :
ansible-galaxy init my_new_role cd my_new_role vim . git init/add/commit/push
Я же пользуюсь альтернативным вариантом — инициализация роли через Molecule (сразу генерируется шаблон для тестирования роли с использованием Molecule):
molecule init role jtprogru.my_new_role --driver-name docker cd my_new_role vim . git init/add/commit/push
У меня была попытка сделать шаблон роли – sample-role. Чтоб можно было форкнуть/скачать архив/скопипастить и иметь уже готовый скелет с тестами в виде Molecule и прочим, а в идеале собрать из нее нормальный шаблон для Cookiecutter – если будет желание, кидай PR.
Написание кода#
В чем писать — вообще до фонаря, главное чтобы твой редактор умел корректно работать с отступами. Я в зависимости от настроения использую VIM, VS Code, GoLand/PyCharm — все с кучей разных плагинов.
На что стоит обращать внимание при написании кода роли:
именование тасок#
- name: my_new_role | install pkg ansible.builtin.apt: name: ">" update_cache: false
Каждый раз, когда имя задачи начинается с имени роли я мысленно говорю себе спасибо за то, что я смогу отличить эту задачу в общем выхлопе плейбука. Ведь задача с именем — name: install pkg может быть где угодно, так ведь? А вот с конкретным имененем — name: my_new_role | install pkg будет явно только в моей роли.
хендлеры#
На конференции DevOpsConf 2022 я совершенно случайно узнал про “топики”. Топики это такая штука, благодаря которой тебе в своих задачах не надо перечислять нотифаи. Вот так ты делаешь обычно:
- name: "jtprogru.install_atop | generate default config for atop" ansible.builin.template: src: "atop.j2" dest: ">" owner: root group: root mode: "0644" notify: - restart atop - restart atopacct
А теперь ты можешь просто написать один нотифай, а ansible сам все поймет и сделает правильно. Например вот так будет выглядеть задача:
# tasks/main.yaml - name: "jtprogru.install_atop | generate default config for atop" ansible.builin.template: src: "atop.j2" dest: ">" owner: root group: root mode: "0644" notify: atop listner # handlers/main.yaml - name: restart atop ansible.builtin.service: name: atop.service state: restarted listen: atop listner - name: restart atopacct ansible.builtin.service: name: atopacct.service state: restarted listen: atop listner
И оба этих сервиса будут перезапущены. Более того, ты в своих ролях можешь слать нотифай в топики других своих ролей.
линтеры#
Обязательно используй линтеры! Их не так много, но их достаточно:
Каждый из них настраиваешь и прогоняешь перед коммитом. В идеале добавить их в pre-commit-hooks и тогда система тебе просто не даст закоммитить в репозиторий кривой код.
Тестирование#
Про тестирование можно говорить бесконечно и это в целом отдельная тема для холиваров, но стоит упомянуть про некоторые вещи. Например, уже упомянутая мною molecule. Однако, в работе с molecule на текущий момент есть огромный нюанс. Текущая версия поддерживает исключительно два типа драйверов (того, на базе чего будут запускаться ваши роли), это Delegated и Docker .
Как именно инициализировать роль с тестами от molecule я уже показал выше, но повторюсь – вот такой командой:
molecule init role jtprogru.my_new_role --driver-name docker
После чего, можно смело идти в файл molecule/default/molecule.yml и добавлять различные варианты Docker images для различных платформ. Например вот так:
# molecule/default/molecule.yml --- dependency: name: galaxy driver: name: docker platforms: - name: instance-cs8 image: quay.io/centos/centos:stream8 pre_build_image: true - name: instance-ub22 image: ubuntu:22.04 pre_build_image: true provisioner: name: ansible verifier: name: ansible
В тестовом инвентаре у тебя будет два хоста:
- instance-cs8 — будет запущен из образа quay.io/centos/centos:stream8 с docker registry quay.io ;
- instance-ub22 — будет запущен из образа ubuntu:22.04 с docker registry hub.docker.com ;
Важно лишь помнить, что далеко не все варианты можно протестировать в Docker’e. Например, если у тебя в роли выполняется управление сервисами systemd или настройка параметров ядра через sysctl, то вариант тестирования в Docker тебе совсем не подойдет. Можно посмотреть в сторону Vagrant’a (как именно обойти санкции использую VPN – инструкций предостаточно в сети, но если сложно, то заходи в чат и спросить) и через синхронизацию директорий (vagrant будет просто монтировать указанную директорию в нужный каталог внутри гостевой системы) катать внутри нужной гостевой ОС.
В остальном, все что касается тестирования – это в большинстве случаев краеугольный камень. Ты либо тестируешь свои роли, либо не тестируешь. Выбор как обычно за тобой.
Где и как хранить#
Начну с того, что есть два варианта хранения всех ролей (про наркоманские упоминать не буду – не та ситуация). Первый вариант – публичный – отправлять все свои роли в Ansible Galaxy. Этот вариант стоит использовать только в том случае, если написанные роли можно публиковать в интернет (тебе не стыдно их показать и компания не возбраняет это). Второй вариант – приватный – хранить свои роли у себя на приватном git-сервере. Это самый частый вариант хранения, потому что “низя показывать нашу интеллектуальную собственность” (и еще тысяча вариантов фразы “нам стыдно”).
Если публичное Ansible Galaxy не подходит по различным причинам, то просто не парься и создавай отдельный репозиторий под каждую роль. А в инфраструктурном репозитории просто подключаешь все через специальный файлик requirements.yaml – пример:
roles: # Установка роли из конкретной ветки из Github - name: jtprogru.install_atop src: https://github.com/jtprogru/ansible-role-install-atop version: origin/master # Установка роли по конкретному тегу из Github - name: jtprogru.install_atop src: https://github.com/jtprogru/ansible-role-install-atop version: 2022.9.0 # Установка роли ко конкретному коммиту из Github - name: jtprogru.install_atop src: https://github.com/jtprogru/ansible-role-install-atop version: 752b9cf7ed13261736b257317e41d1bc03261d61 # Установка роли из Ansible Galaxy - name: jtprogru.install_atop
Вообще, я категорически рекомендую использовать данный файлик для фиксации всех зависимостей – внутренних/внешних ролей и коллекций. Логика тут простейшая: ты можешь переписывать свою роль сколько угодно и совершенно не бояться чего-то сломать в инфраструктуре. Этот же подход позволит коллегам из смежных команд спокойно пользоваться твоими ролями, а когда что-то новое привносишь в свою роль – оповещаешь коллег об этом.
Зависимости ролей#
Не редки случаи, когда мне попадались странные решения в этом плане. Представь картину: есть роль install_nginx , которая ставит Nginx с дефолтными конфигами, и есть роль configure_nginx , которая настраивает Nginx – просто генерит шаблон из Jinja2 и кладет его куда надо. Далее ты добавляешь новую группу проксей в инвентарь и забываешь навесить роль install_nginx или, что чаще, путаешь порядок – сначала “настраиваешь”, а потом “устанавливаешь”. Теперь вопрос: что обычно происходит когда ты пытаешь прогнать все роли по новой группе хостов в инвентаре?
Пока ты размышляешь над ответом, я не буду напоминать про “внимательность” и “ответственность” при написании плейбуков. Я хочу напомнить про маленькую директорию meta , о которой часто забывают (что не есть хорошо). Ранее по тексту я упоминал, что в этой директории хранится мета-информация о данной роли – например такая:
--- galaxy_info: role_name: install_atop namespace: jtprogru author: Mihael Savin description: Ansible role for install atop company: Owl Legion license: WTFPL min_ansible_version: 2.9 platforms: - name: Ubuntu versions: - bionic - focal - name: Debian versions: - stretch - buster - name: EL versions: - 7 galaxy_tags: - atop - monitoring - ubuntu - install dependencies: []
Кроме информации, которая нужна для публикации такой роли в Ansible Galaxy, тут есть замечательная директива: dependencies: [] . В данной директиве ты можешь описать роли от которых эта роль зависит. Это значит, что Ansible при установке твоей роли через утилиту ansible-galaxy автоматически обнаружит эти зависимости и установит их. А главное, что при запуске плейбука Ansible прогонит сначала те роли, от которых зависит твоя, а потом уже прогонит твою роль.
Чтобы никогда не путаться в том, какую роль накатить сначала, а какую потом, очень удобно пользоваться зависимостями самих ролей. Просто указывай в виде списка названия ролей, от которых зависит твоя.
Именование ролей#
Самая страшная роль, которую можно представить – роль под названием common . В ней можно увидеть абсолютно всё – от раскладывания ssh-ключей и установки софта, до настройки sshd и установки docker. Старайся избегать слова common когда придумываешь имя для роли. Исходи из того, какую задачу решает конкретная роль. Если роль решает слишком много задач, то стоит попробовать декомпозировать ее на более атомарные объекты.
Многие кубероводы слышали про такую штуку как kubespray для разворачивания кластеров Kubernetes. Если заглянуть под капот в официальный репозиторий, то можно обнаружить, что kubespray не что иное, как набор плейбуков и ролей Ansible, которые решают одну большую задачу – развернуть Kubernete-кластер.
Заглянув в директорию roles , ты сможешь там увидеть роль adduser . В этой роли есть defaults , в которых определены универсальные дефолтные значения для того, чтобы роль гарантированно отработала. Есть vars для OS-specific переменных – в разных семействах ОСей некоторые пути могут отличаться. Есть tasks состоящий из двух задач – “создать группу” и “создать пользователя”. Все! Ничего лишнего! Даже если бы это были не системные пользователи, а обычные – роль с именем adduser должна только добавлять пользователей и группы для них. Роль с именем adduser не должна выполнять настройку окружения для этих пользователей, не должна закидывать ключи доступа от этих пользователей.
Вообще по части именования, kubespray, на мой взгляд, самый хороший пример. Ты открываешь директорию с ролями и по наименованиям понимаешь, что именно делает та или иная роль. Рекомендую делать так же – просто и понятно, не пытаясь усложнить то, что не требует усложнения.
Вредные советы#

Вредный совет 1: Чем больше в твоей роли вызовов ansible.builtin.command , ansible.builtin.raw или ansible.builtin.shell , тем веселее тебе будет дебажить. Особенно есть ты подкладываешь всякие script.sh .
Вредный совет 2: Чтобы обезопасить сервера от несанкционированного запуска твоих ролей, в первую очередь выполни ansible.builtin.raw: chmod a-x $(which python) и ansible.builtin.raw: chmod a-x $(which python3) – безопасность гарантируется.
Вредный совет 3: Именовать задачи не обязательно, это всего лишь прерогатива тех, у кого на работе много свободного времени. А когда надо срочно сделать, то не до красивостей и правильных неймингов.
Вредный совет 4: Флаг no_log: true никому не нужен, потому что кроме тебя никто не смотрит в логи, а пароли и токены ты и так хранишь на стикерах вокруг монитора.
Вредный совет 5: Запускать плейбуки со своими ролями можно прямо со своего ведроида – зря что ли настраивал какой-нибудь termux, а всякие gitlab-runner’ы и AWX’ы для слабаков.
Вредный совет 6: Если у тебя в какой-то группе есть отличающийся параметр от остальных, то не надо править group_vars , надо просто скопипастить роль полностью и назначить ее на нужную группу – будет гарантия результата.
Вопросы и ответы#
Перед написанием данной статьи я кинул клич в своем канале и просил накидать разных странных вопросов на тему ролей. А изначальная идея статьи вообще была предложена одним Senior Golang Developer у которого я учусь писать на Golang.
Теперь попробую внятно ответить только на часть вопросов, потому что некоторые из пропущенных вопросов тянут на отдельную статью (а то и целую серию).
Вопрос 1: Что лучше – универсальная роль на несколько ОСей или под каждую ОСь своя?
Ответ: Зависит от задачи которую ты хочешь решить. Если ты хочешь без головной боли управлять “одним и тем же конфигом” в разных ОСях (RHEL/Debian/BSD), то логичнее сделать универсальную роль. Универсальная роль тебе будет гарантированно выполнять одно и то же вне зависимости от целевой ОСи.
Вопрос 2: Что делать с копипастой в Ansible репозитории?
Ответ: Самый простой и одновременно сложный вопрос. Ответ тут максимально лаконичный – вычищать. Все, что повторяется более чем один раз – стопроцентно можно оформить в виде роли. Остальное по мере возможностей и необходимостей так же выделять в роль.
Вопрос 3: Где граница, за которой таски из плейбука выделяются в роли и что делать с тасками в плейбуке?
Ответ: По хорошему, эту границу каждый определяет для себя сам. Но я дам тебе пищу для размышления. В комьюнити Ansible культивируется простое правило: “в рамках одного play (и даже целого playbook) должны быть либо только роли, либо только таски”. Лично я использую более жесткую форму этого правила. В рамках основного плейбука – условный site.yaml – у меня должны быть только инклюды с другими плейбуками, в которых уже будут описаны используемые роли или только необходимые таски. Исходя из вышесказанного – мой подход: никаких тасок в плейбуках.
Вопрос 4: Как правильно жить с vars в плейбуке?
Ответ: Тут все очень просто – в идеальном мире у тебя не должно быть директивы vars_files в твоих плейбуках. Начни с того, что правильно составь инвентарь – составь список всех хостов, побей его на группы, посмотри чего не хватает и что можно объединить. После составления правильного инвентаря начни раскладывать по group_vars/ все переменные которые требуются. Если есть необходимость, то выделяй host_vars/ – но не увлекайся, а старайся осознанно подходить к этому и проверяй в первую очередь возможность создания новой группы (пусть и под один хост).
Вопрос 5: Какая разница между vars и defaults?
Ответ: Глобально – никакой. Я довольно часто вижу, что vars в роли не используются – не создается директория даже, потому что “зачем создавать переменную ради переменно если ее не надо переопределять снаружи”. Один из примеров использования я уже описал выше в соответствующем разделе. Если ты пишешь роль, которая может быть исполнена на нескольких разных ОСях, то vars обязаны присутствовать. И на основании того, что указано в group_vars (а это переопределенные переменные из defaults ) или того, где запускается роль подключаются соответствующие переменные из vars .
Вопрос 6: Откуда запускать свои плейбуки – выделенный хост или локально со своей ПеКарни?
Ответ: Если удобно запускать локально со своей рабочей станции – запускай с нее. Просто мне лично видится более логичным и правильным исполнение всех плейбуков с выделенного хоста (или группы хостов). Смотри, если ты задался таким вопросом, то у тебя явно есть своя инфраструктура, в которой наверняка есть git-сервер в лице того же Gitlab. Как известно у Gitlab есть свои раннеры – по сути бинарь, который может исполнять все что ему скажу. Использовать Gitlab-раннеры для запуска инфраструктурного кода – выглядит более чем логично. К тому же запуск можно делать “по кнопке”, а не на каждый коммит и все будет отлично. Различные вариации на тему Ansible Tower/AWX и прочее – исключительно вкусовщина. Если тебе или твоим разработчикам не нравится смотреть на выхлопы Gitlab-раннера, то можно попробовать что-то из “запускаторов для Ansible”.
Вопрос 7: Что делать, если в ИБ сидят звери и каждый пробел в плейбуке надо согласовывать?
Ответ: Простой ответ – сменить работу. Сложный ответ – найти с ИБ общий язык. В одном из рабочих кейсов прошлых лет была необходимость разворачивать “штуки” в закрытом контуре (интернета нету никакого – совсем). Новые версии “штук” передавались на DVD-R дисках, а на CD-R дисках передавались zip-архивы с Docker-образами, которые имели в себе все плейбуки. Лучше всего в таких ситуация стараться найти общий язык. Выяснять, почему те или иные вещи запрещены и какие есть альтернативные варианты. Ты ж будущий девупс – прокачивай коммуникативные навыки.
Вопрос 8: Как тестировать роли?
Ответ: Тестирование ролей это достаточно широкая тема, которую раскрыть в рамках данной статьи будет проблематично. Поэтому я сделаю отдельную статью чуть позже с различными вариантами тестирования. Но если совсем не терпится, то можешь посмотреть на живые примеры в репозитории kubespray. Там используется Molecule и Vagrant для тестирования ролей.
Итоги#
В целом роли в Ansible не так уж и сложно. Некоторые вопросы я не стал включать, потому что они тянули на отдельную статью и более глубокое разбирательство. Я их точно не потеряю, потому что вынес в отдельный issue.
Посмотреть мои кривенькие и простенькие роли можно на странице чат, а так же подписывайся на канал.
- Ansible
- basics
- ansiblerole