Ansible: Organization using Roles, Tags, and Dependencies

July 27, 2015

My Ansible installation contains 8 servers, 18 roles, and approximately 200 tasks. It includes servers running NGINX, Passenger, Ruby on Rails, Sidekiq, Redis, PostgreSQL, and more. The following is how I use roles, tags, and dependencies to keep my sanity and stay DRY.

Let’s use Redis as an example. There are three servers, one master, two slaves, all running Redis Sentinal.

The relevant portion of my inventory file:

[redis-master]
prestwick

[redis-slave]
carnoustie
birkdale

The relevant portion of my main playbook:

- hosts: redis-master
  roles:
    - {role: 'redis-master', tags: 'redis-master'}
    - {role: 'redis-sentinel', tags: 'redis-sentinel'}

- hosts: redis-slave
  roles:
    - {role: 'redis-slave', tags: 'redis-slave'}
    - {role: 'redis-sentinel', tags: 'redis-sentinel'}

By adding the tags in this playbook all dependent tasks will automatically pick up that tag. This includes any dependencies! It also means no more needing to sprinkle tags throughout the other playbooks.

Naming the same as the role helps my sanity as I can call ansible-playbook --tags=redis-master which will run all tasks necessary for the redis-master role. Exactly as if ansible had a --role option.

Each of these roles has some common tasks, namely ensuring a particular APT repository is present and some common handlers (restart redis, restart sentinel). This is where dependencies come into play.

Each of the redis-master, redis-slave, and redis-sentenel roles has a meta/main.yml file with the following contents:

dependencies:
  - { role: 'redis-common' }

It is in this redis-common role that I place the tasks, handlers, files, and templates common to all of the other redis related roles.

The great thing about this setup is that you never have to specify redis-common directly. It will be picked up as necessary while running the other roles.

You can see how it works by comparing the task list for redis-master and redis-slave below:

$ ansible-playbook -i inventory/staging -t redis-master --list-tasks site.yml

playbook: site.yml
  ...
  play #6 (redis-master): TAGS: []
    apt_key url=http://www.dotdeb.org/dotdeb.gpg state=present  TAGS: [redis-master]
    apt_repository repo='deb http://packages.dotdeb.org wheezy all' state=present TAGS: [redis-master]
    apt_repository repo='deb-src http://packages.dotdeb.org wheezy all' state=present TAGS: [redis-master]
    apt pkg=redis-server state=present install_recommends=yes TAGS: [redis-master]
    service name=redis-server enabled=yes TAGS: [redis-master]
    template src=redis.conf dest=/etc/redis/redis.conf owner=redis group=redis mode=0644  TAGS: [redis-master]
  play #7 (redis-slave):  TAGS: []

$ ansible-playbook -i inventory/staging -t redis-slave --list-tasks site.yml

playbook: site.yml
  ...
  play #6 (redis-master): TAGS: []
  play #7 (redis-slave):  TAGS: []
    apt_key url=http://www.dotdeb.org/dotdeb.gpg state=present  TAGS: [redis-slave]
    apt_repository repo='deb http://packages.dotdeb.org wheezy all' state=present TAGS: [redis-slave]
    apt_repository repo='deb-src http://packages.dotdeb.org wheezy all' state=present TAGS: [redis-slave]
    apt pkg=redis-server state=present install_recommends=yes TAGS: [redis-slave]
    service name=redis-server enabled=yes TAGS: [redis-slave]
    template src=redis.conf dest=/etc/redis/redis.conf owner=redis group=redis mode=0644  TAGS: [redis-slave]

Here is how the files are organized:

roles/redis-client/meta/main.yml
roles/redis-client/tasks/main.yml
roles/redis-common/handlers/main.yml
roles/redis-common/tasks/main.yml
roles/redis-master/meta/main.yml
roles/redis-master/tasks/main.yml
roles/redis-master/templates/redis.conf
roles/redis-sentinel/files/redis-sentinel
roles/redis-sentinel/meta/main.yml
roles/redis-sentinel/tasks/main.yml
roles/redis-sentinel/templates/sentinel.conf
roles/redis-slave/meta/main.yml
roles/redis-slave/tasks/main.yml
roles/redis-slave/templates/redis.conf

This has helped me tremendously. The above is fairly simple, but this setup works great when your roles include: postgresql-client, postgresql-common, postgresql-master, postgresql-pgpool2, postgresql-server, and postgresql-slave.