Organizing into Roles
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeUsing include
isn't the only way to organize your playbook. In fact, the best way is with roles... which are a really important concept in Ansible.
If you think of a package of functionality - like bootstrapping Symfony, or getting Nginx set up - it involves a number of things. In the case of Nginx, sure, we definitely need to run some tasks. But we also need to set a variable that's used by those tasks and register the "Restart Nginx" handler!
- hosts: vb | |
// ... lines 3 - 26 | |
tasks: | |
// ... lines 28 - 58 | |
- name: Install Nginx web server | |
become: true | |
apt: | |
name: nginx | |
state: latest | |
notify: Restart Nginx | |
- name: Add Symfony config template to the Nginx available sites | |
become: true | |
template: | |
src: templates/symfony.conf | |
dest: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
notify: Restart Nginx | |
- name: Enable Symfony config template from Nginx available sites | |
become: true | |
file: | |
src: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
dest: "/etc/nginx/sites-enabled/{{ server_name }}.conf" | |
state: link | |
notify: Restart Nginx | |
- name: Add enabled Nginx site to /etc/hosts | |
become: true | |
lineinfile: | |
dest: /etc/hosts | |
regexp: "{{ server_name }}" | |
line: "127.0.0.1 {{ server_name }}" | |
// ... lines 87 - 191 | |
handlers: | |
- name: Restart Nginx | |
become: true | |
service: | |
name: nginx | |
state: restarted | |
// ... lines 198 - 204 |
So a collection of functionality is more than just tasks: it's tasks, variables, and sometimes other things like handlers. A role is a way to organize all of that into a pre-defined directory structure so that Ansible can automatically discover everything.
Creating the Role
Let's turn all of our Nginx setup into an Nginx role. In the ansible/
directory, create a new directory called roles
... and inside that, another directory called nginx
.
In a moment, we're going to point our playbook at this directory. When we do that, Ansible will automatically discover the tasks, variables, handlers and other things that live inside of it. This will work because roles must have a very specific structure.
Tasks in the Role
First, we know that a few tasks need to live in the role. So, create a directory called tasks
and inside, a new file called main.yml
. Start with the ---
:
// ... lines 2 - 30 |
Below - just like with symfony-bootstrap.yml
- we add tasks. In the playbook, search for "Install Nginx web server". We need that! Move it into main.yml
:
- name: Install Nginx web server | |
become: true | |
apt: | |
name: nginx | |
state: latest | |
notify: Restart Nginx | |
// ... lines 8 - 30 |
Let's copy a few others: like "Add Symfony config template to the Nginx available sites" and the two tasks below it. Move those to the role. Then, select everything and un-indent them:
- name: Install Nginx web server | |
become: true | |
apt: | |
name: nginx | |
state: latest | |
notify: Restart Nginx | |
- name: Add Symfony config template to the Nginx available sites | |
become: true | |
template: | |
src: templates/symfony.conf | |
dest: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
notify: Restart Nginx | |
- name: Enable Symfony config template from Nginx available sites | |
become: true | |
file: | |
src: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
dest: "/etc/nginx/sites-enabled/{{ server_name }}.conf" | |
state: link | |
notify: Restart Nginx | |
- name: Add enabled Nginx site to /etc/hosts | |
become: true | |
lineinfile: | |
dest: /etc/hosts | |
regexp: "{{ server_name }}" | |
line: "127.0.0.1 {{ server_name }}" |
Beautiful!
Role templates
Okay, what else does this role need? Well, this task refers to templates/symfony.conf
... which lives at the root of the ansible/
directory:
// ... lines 2 - 8 | |
- name: Add Symfony config template to the Nginx available sites | |
// ... line 10 | |
template: | |
src: templates/symfony.conf | |
// ... lines 13 - 30 |
Drag the templates/
directory into the role.
Role Variables
The tasks also use a variable - server_name
:
// ... lines 2 - 8 | |
- name: Add Symfony config template to the Nginx available sites | |
// ... line 10 | |
template: | |
// ... line 12 | |
dest: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
// ... lines 14 - 15 | |
- name: Enable Symfony config template from Nginx available sites | |
// ... line 17 | |
file: | |
src: "/etc/nginx/sites-available/{{ server_name }}.conf" | |
dest: "/etc/nginx/sites-enabled/{{ server_name }}.conf" | |
// ... lines 21 - 23 | |
- name: Add enabled Nginx site to /etc/hosts | |
// ... line 25 | |
lineinfile: | |
// ... line 27 | |
regexp: "{{ server_name }}" | |
line: "127.0.0.1 {{ server_name }}" |
This is set at the top of our playbook, but it's only used by the Nginx tasks:
- hosts: vb | |
vars: | |
server_name: mootube.l | |
// ... lines 6 - 204 |
Let's move it into the role.
This time, create a vars/
directory and - once again - a main.yml
file.
Remove the variable from playbook.yml
and paste it here:
server_name: mootube.l |
Notice that it's not under the vars
keyword anymore: Ansible knows its a variable because it's in the vars/
directory.
Role Handlers
Finally, if you look back at the tasks, the last thing they reference is the "Restart Nginx" handler. Go find that at the bottom of our playbook:
- hosts: vb | |
// ... lines 3 - 191 | |
handlers: | |
- name: Restart Nginx | |
become: true | |
service: | |
name: nginx | |
state: restarted | |
// ... lines 198 - 204 |
Copy it, remove it, then create - surprise! - a handlers
directory with a main.yml
file inside. Put the 3 dashes, paste it and un-indent!
- name: Restart Nginx | |
become: true | |
service: | |
name: nginx | |
state: restarted |
Using the Role
Phew! This is the file structure for a role, and as long as you follow it, Ansible will take care of including and processing everything. All we need to do is activate the role in our playbook. At the top, add roles
, then - nginx
:
- hosts: vb | |
// ... lines 3 - 18 | |
pre_tasks: | |
// ... lines 20 - 25 | |
roles: | |
- nginx | |
tasks: | |
// ... lines 30 - 171 |
That's it! Time to try it out. Run the entire playbook:
ansible-playbook ansible/playbook.yml -i ansible/hosts.ini
Deploy to the dev
environment this time - I'll show you why in a minute. Hey, it looks good! The Nginx stuff happens immediately.
Looking good... looking good... woh! The Nginx stuff was happy, but we have a huge error at the end. Go back to your browser and load the site with http://mootube.l/app_dev.php
. This is that same error!
Don't be too Smart
What's going on? Well, I made our playbook too smart and it became self-aware. Ok, not quite - but we do have a mistake... and it's unrelated to roles. Inside symfony-bootstrap.yml
, we only install composer dependencies when code_changed
:
- name: Install Composer's dependencies | |
// ... lines 3 - 7 | |
when: code_changed | |
// ... lines 9 - 50 |
Well, remember that the composer dependencies are a little different for the dev
environment versus the prod
environment. The last time I ran Ansible I used the prod
environment. This time I used dev
... but the task that should have installed the dependencies for the dev
environment was skipped!
Shame on me! Find your balance between speed and being too clever. I'll comment out the when
. Run the playbook again in the dev
environment:
ansible-playbook ansible/playbook.yml -i ansible/hosts.ini
This time "Install Composer dependencies" is marked as "changed" because it did download the dev
dependencies. And the page works!
Role and Task Ordering
Go back to your terminal and scroll up a little. Ah, there's one slight problem: Nginx was installed before updating the apt repositories cache. That means that if there's a new version of Nginx, it might install the old one first. We didn't intend for them to run in this order!
In fact, only one thing ran before Nginx - other than the setup
task - our pre-task! In the playbook, I've put pre_tasks
first, then roles
and then tasks
:
- hosts: vb | |
// ... lines 3 - 18 | |
pre_tasks: | |
// ... lines 20 - 25 | |
roles: | |
// ... lines 27 - 28 | |
tasks: | |
// ... lines 30 - 171 |
And that's the order they run it. But it's not because of how I ordered them in my YAML file: Ansible always executes pre_tasks
, then roles
then tasks
.
So how can we update the apt repository cache first? Just move those two tasks into pre_tasks
:
- hosts: vb | |
// ... lines 3 - 18 | |
pre_tasks: | |
// ... lines 20 - 25 | |
- name: Update APT package manager repositories cache | |
become: true | |
apt: | |
update_cache: yes | |
- name: Upgrade installed packages | |
become: true | |
apt: | |
upgrade: safe | |
// ... lines 35 - 171 |
Done! Next, let's download a third-party role for free playbook functionality!