In this blog post, we will explore templating with Ansible to parametrize configuration files and leverage variables in templates.
What are Ansible templates? Templates are useful in cases where we would like to reuse a file but with different parameters for various use cases or environments.
If you are new to Ansible or interested in other Ansible concepts, these Ansible tutorials on Spacelift’s blog might be handy.
With Ansible templating, users can dynamically generate text-based files using templates, variables, and facts for configuration and other purposes. The main objective of using templates is to facilitate and automate the management of configuration files for different targets and requirements.
Imagine that you need to maintain multiple similar environments but with different requirements or specifications. Instead of manually creating, maintaining, and editing configuration files for each target system, we can leverage Ansible templates. We can then combine the templates with other Ansible concepts, such as facts and variables, to generate files tailored to each system’s specific needs without code duplication.
Updating configuration files becomes more manageable with this approach since we only have to perform the changes in one place and handle any inputs with variables that will be replaced with actual values during the playbook execution.
Ansible uses Jinja2 as the default templating engine to create dynamic content.
Jinja2 is a full-featured template engine for Python. The Jinja2 templating engine is quite powerful and widely used with other frameworks and applications such as Flask and Django.
Jinja2 templates combine plain text files and special syntax to define and substitute dynamic content, embed variables, expressions, loops, and even conditional statements to generate complex output. According to the documentation, expressions are enclosed in double curly braces {{ }}
, statements in curly braces with percent signs {% %}
, and comments in {# #}
.
Let’s have a look at some examples below:
- Jinja2 example with a variable named
favourite_color
My favourite color is {{ favourite_color }}
- Jinja2 if statement example
{% if age > 18 %}
You are an adult, and you can vote in the voting center: {{ voting_center }}
{% else %}
Sorry, you are minor, and you can’t vote yet.
{% endif %}
- Jinja2 loop example
Here’s a list of fruits:
{% for fruit in fruits %}
{{ fruit }}
{% endfor %}
Something worth mentioning is that the templating happens before the task is sent to the target machine. Therefore this approach doesn’t require the installation of any extra packages on the target machine and minimizes the amount of data sent.
Another helpful functionality is utilizing the standard filters and tests included in Jinja2 to perform different operations. Ansible also implements extensions to Jinja2, including extra filters for selecting and transforming data and Lookup plugins for retrieving data from external sources.
To use templates in Ansible playbooks, we can use the template module, which takes as inputs the template and the target file, and other necessary parameters to customize the final output file.
In the first example, we will create a template file test.conf.j2
with the contents of the example we saw earlier.
test.conf.j2
My favourite color is {{ favourite_color }}
{% if age > 18 %}
You are an adult, and you can vote in the voting center: {{ voting_center }}
{% else %}
Sorry, you are minor and you can’t vote yet.
{% endif %}
A list of fruits:
{% for fruit in fruits %}
- {{ fruit }}
{% endfor %}
Now let’s create a simple playbook and use Ansible’s template
module. Our playbook contains some values for the variables and only one templating task.
test_templates_playbook.yml
- name: Playbook to test templates
hosts: all
vars:
favourite_color: blue
age: 21
voting_center: ab456-g
fruits:
- banana
- apple
- mango
- pear
tasks:
- name: Template test
template:
src: templates/test.conf.j2
dest: /tmp/test.conf
If we execute this playbook the /tmp/test.conf
file will be created based on the template and the inputs. Let’s check its contents after we have executed the above playbook.
/tmp/test.conf
My favourite color is blue
You are an adult, and you can vote in the voting center: ab456-g
A list of fruits:
- banana
- apple
- mango
- pear
We have used variables, if statements, and for loops to produce a final configuration file based on our input and the base template.
Finally, let’s see an example with a real use case. In the second example, we will use a template to create a configuration file for an Nginx web server.
Here’s the template that we will use.
nginx.conf.j2
server {
listen {{ web_server_port }};
listen [::]:{{ web_server_port }};
root {{ nginx_custom_directory }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
We will use this template in the playbook below to provision an Nginx web server.
main_playbook.yml
- name: Provision nginx web server
hosts: all
gather_facts: yes
become: yes
vars:
nginx_version: 1.18.0-0ubuntu1.4
nginx_custom_directory: /home/ubuntu/nginx
web_server_port: 80
tasks:
- name: Update and upgrade apt
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
upgrade: yes
- name: "Install Nginx to version {{ nginx_version }}"
ansible.builtin.apt:
name: "nginx={{ nginx_version }}"
state: present
- name: Copy the Nginx configuration file to the host
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
- name: Create link to the new config to enable it
file:
dest: /etc/nginx/sites-enabled/default
src: /etc/nginx/sites-available/default
state: link
- name: Create Nginx directory
ansible.builtin.file:
path: "{{ nginx_custom_directory }}"
state: directory
- name: Copy index.html to the Nginx directory
copy:
src: files/index.html
dest: "{{ nginx_custom_directory }}/index.html"
- name: Restart the Nginx service
service:
name: nginx
state: restarted
We also used a simple custom index.html
file for the homepage of our web server.
index.html
<html>
<head>
<title> Hello from Nginx </title>
</head>
<body>
<h1> This is our test webserver</h1>
<p>This nginx web server was deployed by Ansible.</p>
</body>
</html>
Let’s go ahead and run this playbook. For this demo, we have created a virtual machine locally with Vagrant to serve as Ansible’s target.
Last step, let’s ssh into the local host, verify that everything has run successfully, and check the file /etc/nginx/sites-available/default
generated from the template.
The templating has worked as a charm, and our web server is up and running!
Spacelift’s vibrant ecosystem and excellent GitOps flow can greatly assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can then easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.
Another great advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their Stacks with building workflows across tools.
Our latest Ansible enhancements solve three of the biggest challenges engineers face when they are using Ansible:
- Having a centralized place in which you can run your playbooks
- Combining IaC with configuration management to create a single workflow
- Getting insights into what ran and where
Provisioning, configuring, governing, and even orchestrating your containers can be performed with a single workflow, separating the elements into smaller chunks to identify issues more easily.
Would you like to see this in action – or just want a tl;dr? Check out this video I put together showing you Spacelift’s new Ansible functionality:
If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.
This blog post deep-dived into the templating capabilities of Ansible. Ansible leverages Jinja2 to enable dynamic expressions and parametrization of files with variables, loops, conditions, and more. We discussed the characteristics and syntax of Jinja2, and we saw various templating examples. Finally, we reviewed a playbook that uses the template module to produce a configuration file for a web server.
Thank you for reading, and I hope you enjoyed this as much as I did.
Manage Ansible Better with Spacelift
Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.