Ansible best practice do not repeat common role
Solution 1
I determine role dependencies using meta
files in my role directory. Role dependencies allow you to automatically pull in other roles when using a role. Role dependencies are stored in the meta/main.yml file contained within the role directory.
Roles dependencies are always executed before the role that includes them, and are recursive. By default, roles can also only be added as a dependency once - if another role also lists it as a dependency it will not be run again. This behavior can be overridden by adding allow_duplicates: yes to the meta/main.yml file.
See an example in the Ansible documentation.
Solution 2
As others I had this problem, and even if the role is idempotent, it take time to execute, so we might want to execute it only once.
I used a similar approach than @Vor but I choose facts instead of creating files.
Reminder:
Facts survive between plays during an Ansible run, but will not be saved across executions even if you use a fact cache.
Example
site.yml
---
- hosts: all
roles: [common]
- include: db.yml
- include: web.yml
db.yml
---
- hosts: db
roles:
- { role: common, when: isdef_common_role is not defined }
- db
web.yml
---
- hosts: web
roles:
- { role: common, when: isdef_common_role is not defined }
- web
roles/common/tasks/main.yml
---
- debug: 'msg="{{ inventory_hostname }} common"'
- set_fact: isdef_common_role=1
This is, indeed, a bit redundant (as you have to make a conditional include every time), but have the following advantages:
- works on most situations I can think (
ansible-playbook site.yml
,ansible-playbook site.yml -l common
,ansible-playbook site.yml -l db
,ansible-playbook db.yml
, ...) - let you decide if you want to repeat the execution of the common
If you do not want to repeat the { role: common, when: isdef_common_role is not defined }
, you could put condition into the common role using the following:
site.yml
: no changes
db.yml
, web.yml
: remove the conditional from the roles
roles/common/tasks/main.yml
---
- include: tasks.yml
when: isdef_common_role is not defined
- set_fact: isdef_common_role=1
roles/common/tasks/tasks.yml
---
- debug: 'msg="{{ inventory_hostname }} common"'
Solution 3
@user1087973 Are you using tags ? If you are using tags, a common role with different tags is consider as different :
For example :
role_common
role1 : host SRV tag_role_1 depends on role_common
role 2 : host SRV tag_role_2 depends on role_common
Your common role will be executed twice, because it is considered as different because of the tags
Take a look with --list-tasks
and you will see that
A proper solution instead of using tags is to use different yml files and include it in your site.yml
Hope this can help
Solution 4
My approach is to not include playbooks in playbooks. At least not when doing so would result in a role executing multiple times in a single job.
Anything I need to include in more than 1 playbook gets converted into a role, and that role can be included in many playbooks. If I end up with several playbooks that include a duplicated series of roles, I can combine those roles into a single role that just depends on the other roles to avoid that duplication.
Solution 5
My approach is create a lock
file on the server for each role. This works quite nicely.
For example I have a role called common
that's how my tasks/main.yml
looks like:
- name: check lock file exist
stat: path=/tmp/ansible-common-role
ignore_errors: true
register: lock_file
- name: install apt packages
apt: name={{ item }} state=present
with_items:
- python-setuptools
when: lock_file.stat.exists == false
.....
# other tasks with
# when: lock_file.stat.exists == false
.....
- name: Create lock file
file: path=/tmp/ansible-common-role state=touch
when: lock_file.stat.exists == false
As you can see on the example above, the playbook will skip all of the tasks if it is already ran.
Related videos on Youtube
user1087973
Updated on June 04, 2022Comments
-
user1087973 almost 2 years
On the Ansible best practices page: http://docs.ansible.com/ansible/playbooks_best_practices.html#top-level-playbooks-are-separated-by-role it shows an example where the master playbook site.yml includes a couple of other top-level playbooks webservers.yml and dbservers.yml. Within those playbooks they each include the common role. Some inventory files I have all my groups run on one single host. Another inventory file I have a host per group. For the case where ever group is on one host, if I run site.yml you can see that the common role is getting played twice, one for webservers.yml and one for dbservers.yml.
What is a solution to avoid this? I guess you can take out the common role from webservers.yml and dbservers.yml and instead within site.yml have a task that targets both with the common role. But then I can not individually provision a webserver or dbserver with common.
-
alex over 3 yearsWhat do you mean by "Some inventory files I have all my groups run on one single host. Another inventory file I have a host per group."?
-
-
user1087973 over 8 yearsI like this approach and seems to make the most sense to me. However, when I actually try to implement this it does not work. My common role still runs multiple times. I explicitly tried to set allow_duplicates: no with no effect. Seems like there are other people with this same problem? github.com/ansible/ansible/issues/5971