Learn Ansible Basics Beginners Course
Ansible Templates
Jinja2 Templates for Dynamic Configs Demo
In this lesson, we explore how Ansible extends Jinja2 by incorporating filters tailored specifically for infrastructure management. While the base Jinja2 templating engine offers many built-in filters (see the Jinja2 website for more details), Ansible enhances this functionality with additional filters for tasks such as converting between YAML and JSON, manipulating file paths across Linux and Windows systems, managing passwords, and processing regular expressions.
File-Related Filters
Let's begin by reviewing some file-related filters. For example, to extract the file name from a Linux file path such as /etc/hosts
, you can use the basename
filter. This returns hosts
. However, note that this filter will not correctly handle Windows file paths due to the use of backslashes. In that case, the win_basename
filter is required.
Similarly, if you need to split a Windows path into its drive letter and remaining path, apply the win_splitdrive
filter. This filter returns an array—where the first element is the drive letter and the second element is the rest of the path. You can chain it with the first
filter to obtain only the drive letter if needed.
{{ "/etc/hosts" | basename }}
{{ "c:\\windows\\hosts" | win_basename }}
{{ "c:\\windows\\hosts" | win_splitdrive }}
{{ "c:\\windows\\hosts" | win_splitdrive | first }}
Variable Interpolation in Playbooks
In a standard Ansible playbook, Jinja2 templates undergo variable interpolation before task execution. Consider the following examples for an inventory file and a playbook snippet that uses the nsupdate
module.
Inventory File (/etc/ansible/hosts)
web1 ansible_host=172.20.1.100 dns_server=10.5.5.4
web2 ansible_host=172.20.1.101 dns_server=10.5.5.4
web3 ansible_host=172.20.1.102 dns_server=10.5.5.4
Playbook Using the nsupdate Module
---
- name: Update DNS server
hosts: all
tasks:
- nsupdate:
server: '{{ dns_server }}'
Deploying a Web Server
Consider a scenario where you need to deploy a web server by copying a local index.html
file to each web server's default directory using Ansible's copy
module. You can start with a static inventory, playbook, and HTML file, then enhance the solution with a templated approach.
Initial Setup with Static Files
Inventory File (/etc/ansible/hosts)
[web_servers]
web1 ansible_host=172.20.1.100
web2 ansible_host=172.20.1.101
web3 ansible_host=172.20.1.102
Playbook (playbook.yml) Using the Copy Module
- hosts: web_servers
tasks:
- name: Copy index.html to remote servers
copy:
src: index.html
dest: /var/www/nginx-default/index.html
Local HTML File (index.html)
<!DOCTYPE html>
<html>
<body>
This is a Web Server
</body>
</html>
When you run this playbook, the index.html
file is copied to each web server. However, if you want each server's homepage to display its own hostname and IP address, you can leverage variable interpolation within a Jinja2 template.
Dynamic Content with Jinja2 Templates
A better approach is to convert your static HTML file into a dynamic Jinja2 template. Rename the file to index.html.j2
and introduce variables. Below is an example showing the updated inventory, playbook, and Jinja2 template that customizes each server's index page based on its name.
Inventory File (remains unchanged)
[web_servers]
web1 ansible_host=172.20.1.100
web2 ansible_host=172.20.1.101
web3 ansible_host=172.20.1.102
Updated Playbook (playbook.yml) Using the Template Module
- hosts: web_servers
tasks:
- name: Copy index.html to remote servers
template:
src: index.html.j2
dest: /var/www/nginx-default/index.html
Jinja2 Template File (index.html.j2)
<!DOCTYPE html>
<html>
<body>
This is {{ inventory_hostname }} Server
</body>
</html>
When this playbook is executed, Ansible processes the Jinja2 template for each host, replacing {{ inventory_hostname }}
with the host's actual name. For instance, if inventory_hostname
is web1
, the generated file will be:
<!DOCTYPE html>
<html>
<body>
This is web1 Server
</body>
</html>
Similarly, web2
and web3
will receive personalized HTML files.
Note: This templating approach is not limited to static web pages. You can also generate configuration files for services such as Nginx or Redis dynamically.
Dynamic Configuration Files
Nginx Configuration Template
Below is an example of an Nginx configuration template (nginx.conf.j2
) that uses variables to define the upstream server and image directory.
server {
location / {
fastcgi_pass {{ host }}:{{ port }};
fastcgi_param QUERY_STRING $query_string;
}
location ~ \.(gif|jpg|png)$ {
root {{ image_path }};
}
}
This template could render a configuration file like:
server {
location / {
fastcgi_pass localhost:9000;
fastcgi_param QUERY_STRING $query_string;
}
location ~ \.(gif|jpg|png)$ {
root /data/images;
}
}
Redis Configuration Template with Default Filters
You can also incorporate Jinja2 filters to provide fallback values within configuration files. For instance, the following Redis configuration template uses the default
filter:
bind {{ ip_address }}
protected-mode yes
port {{ redis_port | default('6379') }}
tcp-backlog 511
# Unix socket.
timeout 0
# TCP keepalive.
tcp-keepalive {{ tcp_keepalive | default('300') }}
daemonize no
supervised no
When rendered, this might produce:
bind 192.168.1.100
protected-mode yes
port 6379
tcp-backlog 511
# Unix socket.
timeout 0
# TCP keepalive.
tcp-keepalive 300
daemonize no
supervised no
Generating Configurations with Jinja2 Loops
Jinja2 loops allow you to generate configuration file entries dynamically. For example, you can generate a list of nameserver entries for the /etc/resolv.conf
file using a for loop.
Template for resolv.conf (resolv.conf.j2)
nameserver {{ name_server }},
,[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]