Image
![IT Automation ebook](/sysadmin/sites/default/files/styles/image_cta/public/2021-07/SysAdminGuidetoITAutomation-e-book-medium.jpg?itok=8_5VlQhF)
Photo by Vlada Karpovich from Pexels
Jinja2 templates are files that use variables to include static values and dynamic values. One powerful thing about a template is that you can have a basic data file but use variables to generate values dynamically based on the destination host. Ansible processes templates using Jinja2, a templating language for Python.
This article isn't an in-depth Jinja2 tutorial, but it demonstrates how to create Jinja2 templates for Ansible and use filters to manipulate the output of your data.
[ Get the free Jinja2 cheat sheet. ]
Jinja2 templates are designated with the .j2
file extension, which specifies that a file is a Jinja2 template and should be read as such.
Templates are often used to manage configuration files. They eliminate the need to manually update configuration files, which can be prone to human error. They also allow config files to be pushed to multiple hosts, making them scalable. You can find or use existing variables to generate values within files automatically.
Templates also have access to the same variables as the play calling them. These can be any variables with data that Ansible collects about a remote server using Ansible facts, any variable you define for your hosts or groups, or variables for a specific play.
The template module processes a template and pushes it out to a remote managed server.
As the Ansible docs explain, the Ansible controller performs the templating before sending the data to its target. This minimizes the amount of data transferred, keeping the process as lightweight as possible.
[ Get started with IT automation with the Ansible Automation Platform beginner's guide. ]
You can fully manage and control Apache web servers by injecting Jinja2 templates and filters into a playbook. You need some basic knowledge about using Ansible and a running lab environment.
I used these two articles to learn and build an efficient home lab with minor adjustments to suit my needs:
My lab consists of four virtual machine (VM) servers; one Ansible control node, and three Ansible managed nodes, all running CentOS Stream. Even though my lab runs four VMs, I'll keep this article brief by explaining how to manage one web server. This process can be modified to run across and manage multiple web servers.
The httpd.conf.j2
file is a template for a basic Apache config file, which you add to the control node. I have retained all comments from the file, so you can follow the code easily. This config file template has a mixture of variables that are defined in the playbook apache-template.yml
, as well as variables gathered by Ansible facts:
# This is the main Apache HTTP server configuration file
#
# {{ ansible_managed }}
ServerRoot "/etc/httpd"
Listen {{ http_port }}
Include conf.modules.d/*.conf
User apache
Group apache
ServerAdmin {{ admin }}@{{ ansible_hostname }}
<Directory />
AllowOverride none
Require all denied
</Directory>
DocumentRoot "{{ content_dir }}"
<Directory "{{ content_dir }}">
AllowOverride none
# Allow open access:
Require all granted
</Directory>
<Directory "{{ content_dir }}">
Options Indexes FollowSymLinks
AllowOverride none
Require all granted
</Directory>
<IfModule dir_module>
DirectoryIndex index.html
</IfModule>
<Files ".ht">
Require all denied
</Files>
ErrorLog "logs/error_log"
LogLevel warn
<IfModule log_config_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
CustomLog "logs/access_log" combined
</IfModule>
<IfModule />
ScriptAlias /cgi-bin/"/var/www/cgi-bin"
</IfModule>
<Directory "/var/www/cgi-bin">
AllowOverride none
Options none
Require all granted
</Directory>
<IfModule mime_module>
TypesConfig /etc/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml
</IfModule>
AddDefaultCharset UTF-8
<IfModule mime_magic_module>
MIMEMagicFile conf/magic
</IfModule>
EnableSendFile on
IncludeOptional conf.d/*.conf
The next file is the index.html
template implemented inside the playbook to print out the hostname, IPv4 address, current memory usage, block device, and partitions inside the block device. Some things to note:
Create a file called index.html.j2
and enter the following text:
Welcome to {{ ansible_hostname }}
-The ipv4 address is {{ ansible_default_ipv4['address']}}
-The current memory usage is {{ ansible_memory_mb['real']['used']}}mb out of {{ ansible_memory_mb['real']['total']}}mb
-The {{ ansible_devices | first }} block device has the following partitions:
-{{ ansible_devices['vdb']['partitions'] | join('\n -')}}
This playbook defines all the values depicted as variables in the configuration files. I could have opted to create a variables file, add the variables under the vars keyword, and then reference them in the playbook using the vars_file keyword. I chose to use the vars keyword to add the variables directly into the playbook.
Create a file called apache-template.yml
:
---
# Host to execute this playbook
- hosts: ansible01.test.lab
# Become root user
become: true
vars:
# Apache listen on Port 8080
http_port: 8080
admin: ansible-devops
# DocumentRoot set to content_dir var
# New DocumentRoot is webcontent
content_dir: /webcontent
tasks:
- name: Create Group for Webcontent
group:
name: webcontent
state: present
- name: Create Webcontent Dir
file:
path: /webcontent
state: directory
group: webcontent
owner: ansible-devops
mode: '2775'
- name: set mode to enforcing
selinux:
policy: targeted
state: enforcing
- name: enable httpd cgi boolean
seboolean:
name: httpd_enable_cgi
state: true
persistent: true
- name: Set SELinux Context on Directory
sefcontext:
target: "/webcontent(/.*)?"
setype: httpd_sys_content_t
state: present
- name: run restorecon
command: restorecon -irv /webcontent
# Push httpd Config Template
- name: push config template
template:
src: /home/vcirrus-consulting/RHCE-Ansible/templates/httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
backup: true
# Notify handler to Restart Apache
notify: "restart apache"
# Push index Config Template
- name: push index.html template
template:
src: /home/vcirrus-consulting/RHCE-Ansible/templates/index.html.j2
dest: /webcontent/index.html
# Notify this hander, and Roll Out New Changed in Config File
handlers:
- name: restart web servers
service:
name: httpd
state: restarted
listen: "restart apache"
Run the following: $ ansible-playbook apache-template.yml
PLAY [ansible01.test.lab]
************************************
TASK [Gathering Facts]
************************************
ok: [ansible01.test.lab]
TASK [Create Group for Webcontent]
************************************
ok: [ansible01.test.lab]
TASK [Create Webcontent Dir]
************************************
ok: [ansible01.test.lab]
TASK [set mode to enforcing]
************************************
ok: [ansible01.test.lab]
TASK [enable httpd cgi boolean]
************************************
ok: [ansible01.test.lab]
TASK [Set SELinux Context on Directory]
************************************
ok: [ansible01.test.lab]
TASK [run restorecon]
************************************
changed: [ansible01.test.lab]
TASK [push config template]
************************************
ok: [ansible01.test.lab]
TASK [push index.html template]
************************************
ok: [ansible01.test.lab]
PLAY RECAP
************************************
ansible01.test.lab : ok=9 changed=1 unreachable=0 failed=0 skipped=0
rescued=0 ignored=0
This command verifies that the a backup of the original httpd configuration was created together with the newly applied configuration:
$ ansible ansible01.test.lab -a "ls /etc/httpd/conf/"
ansible01.test.lab | CHANGED | rc=0 >> httpd.conf
httpd.conf.27283.2022-06-04@20:40:25\~magic
[ Download now: 6 ways to promote organization-wide IT automation. ]
This command verifies that the SELinux context file type was applied to the directory:
$ ansible ansible01.test.lab -a "ls -Zd /webcontent"
ansible01.test.lab | CHANGED | rc=0 >>
unconfined_u:object_r:httpd_sys_content_t:s0 /webcontent
This command verifies that an index.html
file was created as a result of injecting httpd.conf.j2
into the playbook and using the backup keyword:
$ ansible ansible01.test.lab -a "ls -al /webcontent"
ansible01.test.lab | CHANGED | rc=0 >>
total 8
drwxrwsr-x. 2 ansible-devops webcontent 42 Jun 4 21:05 .
dr-xr-xr-x. 19 root root 256 Jun 4 20:45 ..
-rw-r\--r\--. 1 root root 166 Jun 4 21:05 index.html
This command verifies that all the variables and Ansible facts have been supplied with values as a result of injecting index.html.j2
into the playbook:
$ ansible ansible01.test.lab -a "cat /webcontent/index.html"
ansible01.test.lab | CHANGED | rc=0 >>
Welcome to ansible01
-The ipv4 address is 192.168.124.42
-The current memory usage is 721mb out of 1812mb
-The vdb block device has the following partitions:
-vdb1
This command verifies that Ansible is managing the httpd configuration file and that all the variables have been supplied with values. For example, the server is now listening on port 8080, DocumentRoot is set to /webcontent, and the ServerAdmin is <ansible-devops@ansible01> (as listed in the templates and the playbook). The nl
command is like the cat
command but offers cleaner and easier-to-read output:
$ ansible ansible01.test.lab -a "nl /etc/httpd/conf/httpd.conf"
ansible01.test.lab | CHANGED | rc=0 >>
1 # This is the main Apache HTTP server configuration file
2 #
3 # Ansible managed
4 ServerRoot "/etc/httpd"
5 Listen 8080
6 Include conf.modules.d/*.conf
7 User apache
8 Group apache
9 ServerAdmin ansible-devops@ansible01
10 <Directory />
11 AllowOverride none
12 Require all denied
13 </Directory>
14 DocumentRoot "/webcontent"
15 <Directory "/webcontent">
16 AllowOverride none
17 # Allow open access:
18 Require all granted
19 </Directory>
20 <Directory "/webcontent">
21 Options Indexes FollowSymLinks
22 AllowOverride none
23 Require all granted
24 </Directory>
25 <IfModule dir_module>
26 DirectoryIndex index.html
27 </IfModule>
28 <Files ".ht">
29 Require all denied
30 </Files>
31 ErrorLog "logs/error_log"
32 LogLevel warn
Automated configuration is a powerful way to get infrastructure running quickly and reliably. Give this lab a try, and learn for yourself how you can leverage the power of Ansible and Jinja2 templates and filters in your IT environment.
Robert is a Linux enthusiast and an open source advocate, currently transitioning into a site reliability engineering (SRE) role. Always striving to learn more, he's pursuing Red Hat Certified Architect - Infrastructure path certification. Besides his love for Linux, he believes in helping others More about me