Ansible best practice do not repeat common role

11,034

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

http://docs.ansible.com/ansible/playbooks_best_practices.html#top-level-playbooks-are-separated-by-role

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.

Share:
11,034

Related videos on Youtube

user1087973
Author by

user1087973

Updated on June 04, 2022

Comments

  • user1087973
    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
      alex over 3 years
      What 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
    user1087973 over 8 years
    I 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