В процессе написания плейбука для создания стека с модулем 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]
Готово.