Advanced Ansible Playbooks Jinja2 Templates

By Raman Kumar

Updated on Nov 18, 2024

In this tutorial, we'll learn advanced Ansible Playbooks Jinja2 templates and variables.

Introduction

Ansible is a powerful tool for configuration management and automation, and a key feature that enhances its flexibility is the use of Jinja2 templates and variables. Jinja2, a templating engine for Python, allows you to create dynamic configuration files based on variables and conditions. This guide will dive into advanced topics related to Jinja2 templates and variables in Ansible. By the end, you’ll have a deeper understanding of generating dynamic configurations, handling variable precedence, and working with complex data structures.

Prerequisites

  • Basic understanding of Ansible playbooks.
  • Ansible installed on your control node.
  • Managed hosts set up and accessible via SSH.
  • Basic familiarity with YAML syntax and Jinja2 templating.

Advanced Ansible Playbooks Jinja2 Templates

1. Using Jinja2 Templates for Dynamic Configuration

What is a Jinja2 Template?

Jinja2 is a templating engine that lets you create dynamic files by embedding variables and conditional logic inside template files. In Ansible, these templates are typically used to generate configuration files dynamically. A Jinja2 template file ends with the .j2 extension.

Creating a Jinja2 Template

Let’s create a basic Jinja2 template to generate a configuration file. For example, you might want to create an Apache configuration file (apache.conf) that changes based on host-specific parameters.

Template file: apache.conf.j2

<VirtualHost *:{{ port }}>
    ServerName {{ server_name }}
    DocumentRoot {{ document_root }}
    
    {% if enable_ssl %}
    SSLEngine on
    SSLCertificateFile {{ ssl_certificate_file }}
    SSLCertificateKeyFile {{ ssl_certificate_key }}
    {% endif %}

    <Directory "{{ document_root }}">
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

Deploying the Jinja2 Template with Ansible

To deploy the above template using Ansible, create a playbook that uses the template module.

Playbook file: deploy_apache.yml

---
- name: Deploy Apache configuration
  hosts: webservers
  vars:
    port: 80
    server_name: example.com
    document_root: /var/www/html
    enable_ssl: true
    ssl_certificate_file: /etc/ssl/certs/example.com.crt
    ssl_certificate_key: /etc/ssl/private/example.com.key
  tasks:
    - name: Deploy apache.conf
      template:
        src: apache.conf.j2
        dest: /etc/apache2/sites-available/apache.conf
        mode: '0644'

Explanation

  • The template module copies the apache.conf.j2 template from the control node to the target hosts.
  • Variables like port, server_name, and enable_ssl are defined in the playbook and used inside the template.
  • Conditional blocks like {% if enable_ssl %} allow you to dynamically include sections based on conditions.

2. Understanding Variable Precedence and Scoping

Ansible variables can be defined in multiple places, leading to potential conflicts. Understanding the precedence of these variables is crucial to avoiding unexpected behavior.

Variable Precedence

The following is the order (from lowest to highest precedence) of where variables can be set:

  • Defaults in roles.
  • Vars in roles, playbooks, or included files.
  • Facts gathered from the system.
  • Inventory variables (group_vars, host_vars).
  • Playbook or role parameters.
  • Block parameters.
  • Task parameters.
  • Extra variables (--extra-vars), which always have the highest precedence.

Scoping of Variables

Variables in Ansible have different scopes:

  • Global Scope: Variables set from the command line using --extra-vars.
  • Play Scope: Variables defined in a playbook.
  • Host Scope: Variables defined in an inventory file or host_vars.
  • Role Scope: Variables specific to a role, like those in defaults/main.yml or vars/main.yml.

Example of Variable Precedence

Suppose you have a variable app_port set in different places:

  • In group_vars/webservers.yml: app_port: 8080
  • In the playbook: app_port: 9090
  • As an extra variable: --extra-vars "app_port=7070"

In this scenario, the value 7070 from --extra-vars would take precedence, overriding the other definitions.

3. Working with Complex Data Structures in Ansible

Ansible supports complex data structures like lists and dictionaries, which can help manage configurations in a more organized way.

Lists in Ansible

A list in Ansible can be used to manage multiple items or configurations. For example, let’s assume you want to manage a list of users.

Example: Managing Users with a List

Playbook file: manage_users.yml

---
- name: Manage users
  hosts: all
  vars:
    users:
      - name: alice
        uid: 1001
        shell: /bin/bash
      - name: bob
        uid: 1002
        shell: /bin/zsh
  tasks:
    - name: Create users
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        shell: "{{ item.shell }}"
      loop: "{{ users }}"

Here, a list of users is defined, and the loop directive is used to iterate over the list.

Dictionaries in Ansible

Dictionaries (hashes or objects) allow you to manage key-value pairs. They are ideal for storing configurations or settings.

Example: Using Dictionaries

Playbook file: configure_services.yml

---
- name: Configure services
  hosts: all
  vars:
    services:
      nginx:
        port: 80
        docroot: /var/www/nginx
      apache:
        port: 8080
        docroot: /var/www/apache
  tasks:
    - name: Configure each service
      debug:
        msg: "Service {{ item.key }} runs on port {{ item.value.port }} with document root {{ item.value.docroot }}"
      loop: "{{ services | dict2items }}"

In this example:

  • A dictionary services is defined with configurations for nginx and apache.
  • The dict2items filter converts a dictionary to a list of key-value pairs for iteration.

4. Best Practices for Using Jinja2 Templates and Variables

  • Use group_vars and host_vars to store environment-specific or host-specific data.
  • Prefer using dictionaries for configurations over multiple flat variables.
  • Use variable files to keep your playbooks clean. Include them using the vars_files directive.
  • Use default() filter in Jinja2 to provide fallback values.
  • Use | to_nice_yaml or | to_nice_json filters for pretty printing complex data when debugging.

5. Debugging Tips for Ansible Variables

Use the debug module to print variables for troubleshooting.

- name: Print all variables
  debug:
    var: hostvars[inventory_hostname]

Use set_fact to define variables dynamically during playbook execution.

Use ansible-playbook with the -e option to override variables for testing.

Conclusion

Jinja2 templates and Ansible variables provide the flexibility needed to manage complex infrastructure setups. Mastering them can significantly improve the efficiency of your automation tasks, allowing for dynamic configurations and fine-grained control. By understanding how to use Jinja2 templates, manage variable precedence, and handle complex data structures, you’ll be well-equipped to tackle advanced Ansible playbooks.

Further Reading

Ansible Official Documentation
Jinja2 Documentation

Feel free to ask if you need more specific examples or if you run into issues while implementing these concepts.

Checkout our instant dedicated servers and Instant KVM VPS plans.