В процессе написания плейбука для создания стека с модулем cloudformation (про него позже) появился пример использования тегов для ограничения выполнения задач из различных ролей, и хороший пример важности учёта приоритетов при использовании переменных.
Содержание
Ansible теги
К примеру, есть такой плейбук:
- hosts:
- all
become:
true
gather_facts:
false
roles:
- role: cloudformation
tags: infra
- role: common
tags: app
Зачем запускать CloudFormation стек каждый раз при вызове плейбука?
Что бы ограничить роли, которые будут запускаться — добавляем теги, тут это tags: infra и tags: app.
А далее, ри вызове ansible-playbook, выбираем что именно хотим запустить — роль для инфрастуктуры (tags: infra):
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-production --tags infra mobilebackend.yml
PLAY [all] ****
TASK [cloudformation : Echo] ****
ok: [mb.btrm.com] => {
"msg": "Running CloudFormation role"
}
[/simterm]
Или роль для настройки самого сервера приложения (tags: app):
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-production --tags app mobilebackend.yml
PLAY [all] ****
TASK [common : Echo] ****
ok: [mb.btrm.com] => {
"msg": "Running Common role"
}
[/simterm]
Учитывая, что ролей для настройки сервера будет намного больше, чем одна — то можно уменьшить необходимость в постоянном повторении тега app для каждой роли, и оставить тег только для роли CloudFormation, например так:
- hosts:
- all
become:
true
gather_facts:
false
roles:
- role: cloudformation
tags: infra
- role: common
Далее можно использовать ansible-playbook и передавать --skip-tags infra, что бы игнорировать CloudFomation, и запускать все остальные роли:
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-production --skip-tags infra mobilebackend.yml
PLAY [all] ****
TASK [common : Echo] ****
ok: [mb.btrm.com] => {
"msg": "Running Common role"
}
[/simterm]
ansible_connection, теги и порядок приоритетов переменных
Ещё один момент — это хост для запуска команд Ansible с указанием типа подключения.
Роль cloudformation необходимо вызывать локально, а роли common и другие — на удалённом хосте.
И тут мы сталкиваемся с одной из наиболее важных особенностей Ansible — порядок приоритетов переменных.
Рассмотрим на более наглядном примере плейбука:
- hosts:
- all
become:
true
gather_facts:
false
vars:
rnum: playbook
roles:
- role: role1
tags: tag1
- role: role2
tags: tag2
Файл инвентори — hosts:
[all] host1 [all:vars] rnum="hosts"
ansible.cfg:
[defaults] rnum = "ansible.cfg"
Все они содержат переменную rnum, каждая со своим значением.
Файл main.yml из tasks обеих ролей одинаковые:
- name: Echo
debug: msg="role number {{ rnum }}"
А main.yml в vars — разные:
[simterm]
$ cat roles/role1/vars/main.yml rnum: one
[/simterm]
И роль2:
[simterm]
$ cat roles/role2/vars/main.yml rnum: two
[/simterm]
При выполнении ролей сейчас результат ожидаем:
[simterm]
$ ansible-playbook -i hosts playbook.yml
PLAY [all] ****
TASK [role1 : Echo] ****
ok: [host1] => {
"msg": "role number one"
}
TASK [role2 : Echo] ****
ok: [host1] => {
"msg": "role number two"
}
[/simterm]
А теперь — удалим значение rnum из roles/role1/vars/main.yml, и вызовем плейбук, но без роли role2.
По идее — Ansible «скипнет» переменную из роли2, раз мы её не вызываем, и подставит rnum из файла плейбука:
...
vars:
rnum: playbook
...
Пробуем:
[simterm]
$ ansible-playbook -i hosts playbook.yml --skip-tags tag2
PLAY [all] ****
TASK [role1 : Echo] ****
ok: [host1] => {
"msg": "role number two"
}
[/simterm]
Упс…
Почему two?
Читаем описание tags:
Adding “tags:” in any part of a play (including roles) adds those tags to the contained tasks.
Т.е. получается, что --skip-tags применяется только к таскам, а roles/role2/vars/main.yml считывается в любом случае.
А так как в приоритетах переменные самой роли имеют больший вес, что ansible.cfg, hosts и vars из плейбука — то мы получаем значение rnum: two из файла vars/main.yml второй роли.
Почему это важно сейчас?
Вернёмся к началу:
Роль cloudformation нам необходимо вызывать локально, а роли common и другие — на удалённом хосте.
Указание на среду выполнения мы передаём через значение для ansible_connection, и если для ansible_connection указать значение local в файле roles/common/vars/main.yml — то этот самый local будет применяться ко всем ролям и таскам, независимо от --skip-tags.
Т.е. — модуль ping из roles/common/tasks/main.yml будет выполняться независимо от того — есть подключение к хосту из hosts, или нет. А нам этого не надо:
- name: Echo debug: msg="Running Common role" - name: Ping ping:
Тут варианта два: либо указывать ansible_connection=ssh в vars укаждой роли, а этого не хочется, ибо зачем нам повторять код?, либо — подключать файл с переменными из задачи в роли cloudformation. Тогда задача считывания переменных будет «скипаться» при --skip-tags infra, переменная ansible_connection не будет задаваться в local, и «проблема» решена.
Решаем это спомощью include_vars.
Обновляем roles/cloudformation/tasks/main.yml:
- name: Read CloudFormation vars include_vars: cf_vars.yml - name: Echo debug: msg="Running CloudFormation role"
Создаём roles/cloudformation/vars/cf_vars.yml:
... ansible_connection: local ...
Запускаем:
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-dev --skip-tags app mobilebackend.yml
PLAY [all] ****
TASK [cloudformation : Read CloudFormation vars] ****
ok: [mbdev.btrm.com]
TASK [cloudformation : Echo] ****
ok: [mbdev.btrm.com] => {
"msg": "Running CloudFormation role"
}
[/simterm]
А вот роль common будет фейлится, т.к. в ней есть вызов ping, при вызове которого Ansible попробует подключиться к хосту из hosts, но домен там пока «фейковый», и пинг не сработает:
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-dev --skip-tags infra mobilebackend.yml
PLAY [all] ****
TASK [common : Echo] ****
ok: [mbdev.btrm.com] => {
"msg": "Running Common role"
}
TASK [common : Ping] ****
fatal: [mbdev.btrm.com]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname mbdev.btrm.com: Name or service not known\r\n", "unreachable": true}
[/simterm]
Если бы common вызывала ping через ansible_connection: local — то ping сработал бы.
include_vars и отдельные файлы переменных для окружений
Что ещё хотелось бы сделать перед тем как создавать стек — это иметь возможность переопределить переменные в зависимости от значения переменной env.
Добавляем env в hosts:
[btrm-mobilebackend-dev] mbdev.btrm.com [btrm-mobilebackend-dev:vars] env=dev [btrm-mobilebackend-production] mb.btrm.com [btrm-mobilebackend-production:vars] env=production
Далее, в roles/cloudformation/tasks/main.yml используем переменную env, что бы выбрать файл с переменными:
- name: Read ENV specific variables
include_vars:
file: "{{ env }}_vars.yml"
- name: Read CloudFormation global vars
include_vars: cf_vars.yml
- name: Echo
debug: msg="Running CloudFormation role on {{ envname }}"
Создаём два файла с переменными — dev_vars.yml:
[simterm]
$ cat roles/cloudformation/vars/dev_vars.yml envname: "Dev stack"
[/simterm]
И production:
[simterm]
$ cat roles/cloudformation/vars/production_vars.yml envname: "Production stack"
[/simterm]
Запускаем cloudformation роль на Dev окружении (--limit btrm-mobilebackend-dev):
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-dev --skip-tags app mobilebackend.yml
PLAY [all] ****
TASK [cloudformation : Read ENV specific variables] ****
ok: [mbdev.btrm.com]
TASK [cloudformation : Read CloudFormation global vars] ****
ok: [mbdev.btrm.com]
TASK [cloudformation : Echo] ****
ok: [mbdev.btrm.com] => {
"msg": "Running CloudFormation role on Dev stack"
}
[/simterm]
И на Проде:
[simterm]
$ ansible-playbook -i hosts.ini --limit btrm-mobilebackend-production --skip-tags app mobilebackend.yml
PLAY [all] ****
TASK [cloudformation : Read ENV specific variables] ****
ok: [mb.btrm.com]
TASK [cloudformation : Read CloudFormation global vars] ****
ok: [mb.btrm.com]
TASK [cloudformation : Echo] ****
ok: [mb.btrm.com] => {
"msg": "Running CloudFormation role on Production stack"
}
[/simterm]
Готово.




