Ansible variables

Ansible tutorial – part 4

In this part we will focus on Ansible variables. How to use them in playbooks, tasks and templates.

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: https://github.com/blacksaildivision/ansible-tutorial
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

 

What is the purpose of variables in Ansible?

Imagine the situation where you have two servers. On each of them you need to install nginx. But each server requires a bit different configuration. Let's take simplest case - on server no. 1 we want nginx to be running on port 80, which is default port. But on the second server it should work on port 8080.

We could create two separate roles and set different tasks, but this is an overkill. Lot of copy pasting and much harder maintenance of two roles that does exactly the same. Let's use variables for that!

Defaults

We will expand the role we created in previous part - nginx. First, we need to create a directory inside nginx directory and call it defaults  (it should be on the same level as tasks directory). Inside this directory create file and name it main.yml  Ansible will automatically include that file.

Take a look at example variable:

nginx_port: 80

Variable should have a name, if you are using any other programming language, there are the same rules as everywhere, it can't start with number etc.

We always prefix our variables with role name. It's good practice and it helps avoid conflicts between roles. We could name it simply with port, but if we would create additional role that will have also port variable, prefixes becomes really handy:)

After that there is a colon following by variable value. You can use different values like numbers, strings, lists etc.

Let's add following variables to our default/main.yml  file:

nginx_service_name: nginx
nginx_yum_packages:
 - epel-release
 - nginx
nginx_port: 80

These are only example variables. Normally we would use only nginx_port variable. First two are not necessary in real playbooks. There is no need to convert everything to variables. It can lead to mess in playbook. In general we only use variables for values that can be different among hosts. For instance the port. Sometimes we run nginx on different port than 80. But service name and required yum packages are always the same. So we shouldn't use it as variable. Here we added it only for example purposes.

How to use variables in tasks?

We can use variables in tasks and in templates. We will start with simples usage in task. Take a look at the task we have in our tasks/main.yml  file:

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

Let's say that we want to replace service name with our variable nginx_service_name . So change this task like so:

- name: enable nginx
  service:
    name: "{{ nginx_service_name }}"
    state: started
  tags: [nginx, status]

In order to use variables in Ansible, we need to wrap them into double curly brackets {{}} . Spaces before and after variables are not necessary, but they add a bit of readability. However, if we want to use variable in task, we need to wrap everything in quotes "" . So here is the general rule for variables: "{{ variable_name_here }}" .

You can re-run the playbook, but you should see no difference there, as we just replace nginx name to the same value, but hidden under the variable.

You can use single variable in multiple places in your playbook.

Lists and loops in Ansible

Let's take another look at our tasks, in particular, first two tasks:

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

- name: install nginx
  yum:
    name: nginx
    state: present
  tags: [nginx]

As you can see, they are doing the same thing - installing packages to our server. We can combine them into one task to avoid code duplication. Take a look at combined tasks (and remove these two tasks from above):

- name: install required packages
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - epel-release
    - nginx
  tags: [nginx]

Let start with new statement with_items . In short words it means - execute given module like yum , template , user  etc for each value on the list. So yum module will be executed twice, because we have two items on our list - epel-release and nginx. You can specify any number of items you need. We just need two, but you can add any additional software you want to install.

Next, you need to use special variable item . The value will contains item from with_items  list. So in first iteration under item  we will have epel-release value, and in second iteration we will have nginx value there.

Lists are really useful and this is only basic usage of them. We will get into lists deeper in next parts of this tutorial.

You can also pass variable from defaults to with_items statement. We defined variable called nginx_yum_packages in defaults/main.yml . It is a list, so we can use it with with_items like so:

- name: install required packages
  yum:
    name: "{{ item }}"
    state: present
  with_items: "{{ nginx_yum_packages }}"
  tags: [nginx]

Variables in templates

We can also use variables inside template files. In our nginx role we have template named nginx.conf.j2 . Open it in editor and look for port in server block:

....
server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
....

As you can see we have port 80 there. We can replace it to our variable that we defined previously - nginx_port .

In templates we also need to use double curly brackets, but we need to omit quotes. They are not necessary in template files. If you will use ""  in template it will also appear in end result file, which in some cases is not a good thing.

Let's replace 80 with our variable:

....
server {
        listen       {{ nginx_port }} default_server;
        listen       [::]:{{ nginx_port }} default_server;
        server_name  _;
....

So now it's easy and we can just replace the value in our variable files to change the port. No need to mess with template 🙂

You can re-run the playbook, it should give you the same result as the end template after processing is not changed.

Overriding variables

You may wonder, why the directory with variables is called defaults ? It's because they are default variables if you don't specify host related value.

In most cases default value is enough to execute the playbook, but sometimes you need to change something in particular host. Imagine the situation we mentioned at the beginning - we want to change the port from 80 to 8080, but without changing anything in the role.

Open playbook.yml  file and simply set the variable value like so:

- hosts: ansible_tutorial
  become: yes
  become_user: root
  roles:
   - nginx
  vars:
    nginx_port: 8080

There is special block called vars . You can specify host(s) related values there. So when you execute the playbook, variable nginx_port  will have value 8080, not 80 like we defined in defaults. Host related variables has priority over role defaults variables.

You can override as many variables as you need. Keep in mind that you should override existing values in defaults. You shouldn't add custom variable in playbook file and use it inside role without having the default value specified. It will work fine, but if you try to use the role in another host and you won't specify missing variable, you will get an error.