Skip to main content

Monitor remote systems with Ansible and Jinja2 templates

Use automation and templates to gather and save information about your Linux virtual machines.

Photo by Christina Morillo from Pexels

Jinja2 templates are essential for handling tasks that involve advanced file modifications. When you use templates, target files on discovered hosts are automatically customized using variables and Ansible facts. A variable defined in a template is replaced with the correct value when the Jinja2 template is rendered to the target file on a managed host.

[ Download the Jinja2 cheat sheet. ]

This article demonstrates using Jinja2 templates and Ansible. To follow along, you need at least two virtual machines (VMs) managed by Ansible and one Ansible control node. I'm running Red Hat Enterprise Linux (RHEL) on one VM and CentOS Stream on the other.

Prepare your Ansible control node by installing Ansible, configuring ansible.cfg, and creating an inventory file for the hosts managed by Ansible.

Configure Ansible

In the ansible.cfg file, define a string called ansible_managed to prevent administrators from overwriting files managed by Ansible. It doesn't exist yet, but later an {{ ansible_managed }} variable, placed at the top of a Jinja2 template, will refer to the string defined here:

[defaults]
retry_files_enabled = False
timeout = 60
connection = smart
interpreter_python = auto
forks = 10
inventory = /home/vcirrus-consulting/RHCE-Ansible/inventory-gather-facts.yml
roles_path = /home/vcirrus-consulting/RHCE-Ansible/roles:/usr/share/ansible/roles
remote_user = ansible-devops
host_key_checking = False
command_warnings = False
deprecation_warnings = False
ansible_managed = Caution: This File is Managed By Ansible - DO NOT EDIT MANUALLY.

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

Next, create a static inventory file in a project directory with a list of hosts. I store mine in ~/RHCE-Ansible/inventory-gather-facts.yml, and here are its contents:

all:
  children:
    mombasa_one_dc:
      hosts:
        ansible01.test.lab:
    nairobi_one_dc:
      hosts:
        ansible13.test.lab:
    prod:
      hosts:
        ansible01.test.lab:
        ansible02.test.lab:
        ansible12.test.lab:
        ansible13.test.lab:
    ungrouped: {}

[ Learn more about server and configuration management by downloading Ansible for DevOps. ]

Create a Jinja2 template

Create a Jinja2 template file named rhel_managed_infra_report.j2. Jinja2 expressions in this file refer to the ansible_facts variable. The expressions also implement error handling by using the Jinja2 default filter, which ensures no unnecessary errors are returned as a result of running the playbook. In this case, the playbook displays NONE when the variable defined does not exist:

{{ ansible_managed }}

INVENTORY_HOSTNAME = {{ inventory_hostname | default('NONE',true) }}
OS_DISTRO_VERSION = {{ ansible_distribution_version | default('NONE',true) }}

Next, create a playbook called rhel_managed_infra_report.yml and use the template module to inject the Jinja2 template file into the playbook.

After executing the playbook on the Ansible-managed remote nodes, the dest element stores the results. The src element defines the Jinja2 template for Ansible to use.

Note that the src path in the playbook can be a relative path, which means the file exists in your current working directory, or it can be expressed as an absolute path.

---
- hosts: 'prod,&mombasa_one_dc'
  tasks:
     - name: Deploying Template to Gather and Save info from Remote Systems
       ansible.builtin.template:
          src: rhel_managed_infra_report.j2
          dest: /root/hwreport.txt
          mode: preserve
      
- hosts: 'prod,&nairobi_one_dc'
  tasks:
     - name: Deploying Template to Gather and Save info from Remote Systems
       ansible.builtin.template:
          src: rhel_managed_infra_report.j2
          dest: /root/hwreport.txt
          mode: preserve
[…]

Run the Ansible playbook using ansible-navigator. It consists of two tasks, the first of which must run on hosts that belong to the groups prod and mombasa_one_dc. The second task must run on hosts belonging to the groups prod and nairobi_one_dc.

$ ansible-navigator run --mode stdout rhel_managed_infra_report.yml                                                                                                
PLAY [prod,&mombasa_one_dc] *********************
TASK [Gathering Facts] *******************************
ok: [ansible01.test.lab]

TASK [Deploying Template to Gather and Save info from Remote Systems] ***
changed: [ansible01.test.lab]

PLAY [prod,&nairobi_one_dc] ************************

TASK [Gathering Facts] *********************************************************
ok: [ansible13.test.lab]

TASK [Deploying Template to Gather and Save info from Remote Systems] **********
changed: [ansible13.test.lab]

PLAY RECAP *********************************************************************
ansible01.test.lab: ok=2    changed=1    unreachable=0    failed=0...
ansible13.test.lab: ok=2    changed=1    unreachable=0    failed=0...

Notice that out of all the hosts listed in my hosts file, only ansible01 and ansible13 are affected. This is because they are the only ones that satisfied the playbook's group requirements.

[ Download now: Advanced Linux commands cheat sheet. ]

Verify success

Run an Ansible ad hoc command to verify that the information has been recorded to the specific remote directory defined in the playbook. These results display all information translated from the Jinja2 template file on ansible13.test.lab running RHEL:

$ ansible ansible13.test.lab -a "cat /root/hwreport.txt"        
ansible13.test.lab | CHANGED | rc=0 >>
Caution: This File is Managed By Ansible - DO NOT EDIT MANUALLY.

INVENTORY_HOSTNAME = ansible13.test.lab
OS_DISTRO_VERSION = Plow

The information for ansible01.test.lab, running CentOS Stream:

$ ansible ansible01.test.lab -a "cat /root/hwreport.txt"       
ansible01.test.lab | CHANGED | rc=0 >>
Caution: This File is Managed By Ansible - DO NOT EDIT MANUALLY.

INVENTORY_HOSTNAME = ansible01.test.lab
OS_DISTRO_VERSION = Stream

Notice that the values of OS_DISTRO_VERSION are unique in the rendered files, even though the same playbook generated both. That's the power of statements like OS_DISTRO_VERSION = {{ ansible_distribution_release | default('NONE',true) }}, which uses local data to generate content.

[ Get a cheat sheet of Linux utilities and commands for managing servers and networks. ]

Use Ansible facts in Jinja2

You can expand the information you gather by using other built-in ansible_facts variables for Jinja2. Modify your rhel_managed_infra_report.j2 template file:

{{ ansible_managed }}

INVENTORY_HOSTNAME = {{ inventory_hostname | default('NONE',true) }}
OS_DISTRO_VERSION = {{ ansible_distribution_version | default('NONE',true) }}

PYTHON_VERSION = {{ ansible_python_version | default('NONE',true) }}
SELINUX_STATUS = {{ ansible_selinux | default('NONE',true) }}
BIOS_VERSION = {{ ansible_bios_version | default('NONE',true) }}
BIOS_DATE = {{ ansible_bios_date | default('NONE',true) }}
TOTAL_MEMORY = {{ ansible_memtotal_mb | default('NONE',true) }}
   
CURRENT_MEMORY_USAGE = {{ ansible_memory_mb['real']['used']}} mb out of {{ ansible_memory_mb['real']['total']}} mb
OS_FAMILY = {{ ansible_os_family | default('NONE',true) }}
OS_DISTRO = {{ ansible_distribution | default('NONE',true) }}

OS_KERNEL_VERSION = {{ ansible_kernel | default('NONE',true) }}
OS_ARCHITECTURE = {{ ansible_architecture | default('NONE',true) }}
   
PROCESSOR = {{ ansible_processor | default('NONE',true) }}
   
VIRT_TYPE = {{ ansible_virtualization_type | default('NONE',true) }}
   
VDA_SIZE = {% if ansible_devices.vda is defined %}
{{ ansible_devices.vdb.size }}{% else %}
NONE
{% endif %}
   
VDB_SIZE = {% if ansible_devices.vdb is defined %}
{{ ansible_devices.vdb.size }}{% else %}
NONE
{% endif %}

Run that and look at the output. That's a lot of useful information about each system, and all it takes is one template file and a playbook!

To get a list of all the ansible_facts variables for a Jinja2 template file, use this ad hoc command on any managed node:

$ ansible <managed_host_fqdn or managed_host_ipaddress> -m setup -a "filter=ansible_*"

The power of templates

This article touches on the power of using Jinja2 templates with Ansible. Explore further and see how you can implement the lessons learned from this article to extract valuable information configured in your remote systems. For example, try tinkering with your remote storage configuration or collecting information in a multivendor hybrid environment.

Topics:   Ansible   Linux administration   Monitoring  
Author’s photo

Robert Kimani

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

AnsibleFest is coming to Red Hat Summit

With AnsibleFest now part of Red Hat Summit, you can experience two essential IT events in one location. Join us May 23-25 to shape the future of Ansible.

OUR BEST CONTENT, DELIVERED TO YOUR INBOX