Ansible tutorial – part 2

This is part 2 of Ansible tutorial. In this part you will learn how to create playbooks, what are roles and tasks and how to use privileges escalation in Ansible.

A short word about Ansible tutorial

This is multipart series about Ansible, great tool for provisioning and configuration management. You will learn how to create useful roles and use Ansible in efficient way. You will also pick up some good practices:)

If you like tl;dr take a look at shorter veresion of this tutorial on GitHub:
There are also some examples!

Part 1: Intro to Ansible and Inventory files
Part 2: Playbooks and tasks
Part 3: Templates and handlers 
Part 4: Variables



Playbooks are essential feature of Ansible. In Playbooks we specify what and how our server will be configured and provisioned.

Go to our directory that we created in first part -  vagrant_ansible_tutorial . Create a file inside and name it playbook.yml . Name is irrelevant here, you can use anything you want. Open this file in editor and paste following content:

- hosts: ansible_tutorial
   - debug:
      msg: Test of Ansible Playbook!

First line contains host(s) that we want to provision. You must specify group or single host from Inventory file  hosts  that we created in part 1. Good practice is to specify group instead of single host. In our case it is  ansible_tutorial , so we will provision our Vagrant box.

You can also specify multiple hosts here and set different tasks or roles:

- hosts: webservers
   - debug:
      msg: Installing nginx
   - debug:
      msg: Installing PHP

- hosts: databases
   - debug:
      msg: Installing MongoDb


Next, we need to specify tasks . Task in Ansible is like single command in bash on steroids. With tasks you can install software, add users, remove files etc. Our task will just print out the message in command line, so no harm will be done if it won't work as expected.

We will get more into the tasks later on. For now just use the playbook above.

If you are familiar with YAML or Ansible you might notice that there are three dashes ---  missing. If you will browser through the example playbooks on GitHub, you might note that all files starts with --- . This is coming from YAML and it is optional in Ansible. You might or might now use it, it doesn't make any difference. We never use it on our side and we won't use it in this tutorial as well. But if you prefer, you can add ---  to start of each file like this:

- hosts: ansible_tutorial

Go to your terminal and execute following command:

ansible-playbook -i hosts playbook.yml

 We need to pass two arguments. First one is an inventory file. Second argument is playbook file that we want to run.

Your output should look similar to:

PLAY [ansible_tutorial] ********************************************************

TASK [setup] *******************************************************************
ok: []

TASK [debug] *******************************************************************
ok: [] => {
    "msg": "Test of Ansible Playbook!"

PLAY RECAP *********************************************************************              : ok=2    changed=0    unreachable=0    failed=0

You might note that there is also task named setup . This is Ansible internal task for gathering information about the server. We will use them later on. For now, just know, that such additional task exists.

Next lines contains our debug task. It got status ok  and it was executed correctly.

Probably the most important part of this output is recap . It contains information about current run of playbook. There are 4 metrics that are worth checking:

  • ok  - tasks that were executed correctly or didn't change anything on provisioned server.
  • changed  - things that were modified, like you just add new user. If you run the same playbook twice it will have ok status.
  • unreachable - usually should not happen, you will get it when host becomes unreachable, connection was dropped or you are out of internet etc
  • failed  - when tasks were unable to execute correctly

In the end, perfect playbook should not contain any failed and unreachable errors. On first run you should get only ok and changed tasks. Second execution of the same playbook should result only in ok statuses.

Roles in Ansible

Role, apart from Playbook, is another essential feature of Ansible. In previous example we created tasks directly in playbook file. You must know that this is wrong approach. 99% of the time you will use roles instead of using tasks directly in playbooks. We used it only to show you how to execute working playbook.

Think of how do you use a server. For sure you need to install some kind of web server like apache or nginx. There will be couple of things to do (tasks) in order to install nginx. We can group them into single role. Roles will help you organise your structure.

Role should have single responsibility like installing and configuring nginx. It should also be reusable. So you can for instance use it in different group of servers or even use it in separate project.

Let's create our first test role that will install and configure nginx. In the project directory create directory and name it roles . In this directory you should keep all roles you create.

Inside there you should create another directory and name it nginx . Name of the directory is the same as name of your role. Each role directory, like nginx, usually contains couple of directories inside like tasks, defaults, handlers etc. For now we will just create directory named tasks . Inside tasks directory, create file and name it main.yml  In the end your directory structure should look like this:

Screen Shot 2016-06-22 at 13.02.08

At the very beginning it might look messy, but after a while you will see that it's quite clever way of organisation.

Just for the start, we will use our debug message as simple start. Your main.yml file should look like this:

- debug:
    msg: Test of Ansible Playbook!

This is just copy+paste from playbook.yml  Now we need to include our role into the playbook itself. Edit playbook.yml  so it will look like that:

- hosts: ansible_tutorial
   - nginx

As you can see, we just removed tasks  from there and added roles  instead. Each hosts group can contain multiple roles. You can also reuse given role in different hosts.

Now let's run our playbook again:

ansible-playbook -i hosts playbook.yml

Output will be very similar as before and the end result should be the same.

Tasks in Ansible

Let's write some some real tasks that will help us install nginx. Our OS is CentOS 7 as we specified it in Vagrantfile . In order to get nginx up we need to install epel repostitory and nginx itself. Start by removing everything from main.yml  in tasks directory. We don't need debug message there, it was only for test purposes. Instead create following task:

- name: install epel
    name: epel-release
    state: present
  tags: [nginx]

Each task should start with name. It's not necessary but it's very informative and easier to read in ansible command output.

Next line contains module name that we will use. In CentOS we are using yum for installing files. If the base images was Ubuntu for instance, we would use apt module. Yum/Apt module needs to know two things. First is the name of the package we would like to install. In our case it's epel-release . Second thing is state - whether application should be installed or removed. We want to install it, so we should use present  state.

Last thing are tags. They are optional in each task but usually it's good to use them. When you use ansible-playbook command, you can run tasks that contains only given tag. We basically add tag(s) for each task we create. Sometimes it's not necessary, but it's just convenient to have them there.

Run your playbook with ansible-playbook command like before. If you are using the same image as we in our tutorial, you might wonder that task resulted in ok  status, instead of changed . It should be changed when we install new packages, right? Yes, it should, but the vagrant image has epel-release already installed. So Ansible is checking if it's installed, and if so, it won't do anything. Clever, isn't it?

Once we have epel repository we can install nginx. Let's add another task to our role:

- name: install epel
    name: epel-release
    state: present
  tags: [nginx]
- name: install nginx
    name: nginx
    state: present
  tags: [nginx]

As you can see, we are just installing nginx. Let's run the playbook. Whooops! You should get a failure - You need to be root to perform this command.


In hosts file we specified that the user that should connect to the server is vagrant. But it does not have enough permissions to install things. We need to use privileges escalation.

Let's edit our playbook file and add become  lines:

- hosts: ansible_tutorial
  become: yes
  become_user: root
   - nginx

What we just did is we enabled become so now we can allow Ansible to execute command as different user. As we need to install some packages we need root user. Ansible will login as vagrant user, but each command will be executed as it would be root account.

You might ask - why not to edit hosts file and use root as a user. Answer is simple - the secure way is to disable ssh for root account to prevent harm to your server. root should not have the option to direct login via ssh.

You can also specify these values per task. But usually most of the tasks we do require root power, so we are defining it for entire connection to given host.

Try to run the command again. It should succeed this time. You should get changed=1 in output. It means that nginx has been installed. Try to run the playbook again. It will get ok  status, so nothing was changed.

Finish installing nginx with Ansible

Last missing task is to start nginx. Add following task inside main.yml

- name: enable nginx
    name: nginx
    state: started
  tags: [nginx, status]

We are using Ansible module called service. It is responsible for managing services. In general you can find all possible modules for Ansible on this page. It's super useful and it contains lot of examples for each command. It's also well written. If you don't know something, just take a look at documentation of given module and you should get an answer to your question:)

Service module requires two things - name of the service, which in our case is nginx, and state of the service. We want to start it, so state should be set to started .

Run your playbook. After thatm open your web browser and type the IP of your server (you can find it in Vagrant file). If you are following the tutorial, simply visit this URL:

You should see nginx welcome page!


Now it's the time to use some tags power. Note that we added another tag named status  to our task. Imagine the situation when you just want to check if nginx is working. You don't want to execute other tasks that are installing things. We should only execute tasks that have given tag:

ansible-playbook -i hosts playbook.yml --tags="status"

It should execute only two tasks. One is default setup task, and second one is the task with status tag. If you have multiple roles, like nginx, php, mysql and you will add status tag to all tasks connected to service module, command above will look through all roles. In one command you are able to check if all services are working fine:)

You can also specify multiple tags using comma separated list:

ansible-playbook -i hosts playbook.yml --tags="status,nginx"