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.