Introduction to Variables

Variables are fundamental to making your Ansible playbooks flexible, reusable, and maintainable. Instead of hardcoding values directly into your tasks, you can define them as variables and reference them. This allows you to easily change configurations without modifying the core playbook logic.

Ansible variables can come from various sources, including:

  • Inventory variables: Defined directly in your inventory.ini file.
  • Playbook variables: Defined within the playbook itself.
  • Host/Group variables (preferred): Stored in separate files (host_vars/ and group_vars/).
  • Command-line variables: Passed using -e or --extra-vars when running ansible-playbook.
  • Facts: Information gathered by Ansible about the remote hosts.
  • Vault variables: Encrypted variables for sensitive data.

Referencing Variables

Variables are referenced in playbooks using Jinja2 templating syntax: {{ variable_name }}.

Types of Variables and Their Use Cases

  1. Inventory Variables

You’ve already seen ansible_user and ansible_private_key_file in inventory.ini. You can define custom variables too.

Example inventory.ini:

# inventory.ini

[webservers]
web1.example.com
web2.example.com nginx_port=8080

[databases]
db1.example.com

[all:vars]
ansible_user=your_ssh_user
ansible_private_key_file=~/.ssh/id_rsa
default_apache_port=80

In this example:

  • nginx_port is a host-specific variable for web2.example.com.
  • default_apache_port is a group variable that applies to all hosts under [all:vars].
  1. Playbook Variables

Defined directly within a playbook using the vars: keyword.

Example playbook_with_vars.yml:

---
- name: Deploy a simple web application
  hosts: webservers
  become: true

  vars:
    app_directory: /var/www/my_app
    app_user: webappuser
    app_group: webappgroup
    nginx_config_file: /etc/nginx/sites-available/my_app.conf

  tasks:
    - name: Create application directory
      ansible.builtin.file:
        path: "{{ app_directory }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_group }}"
        mode: '0755'

    - name: Ensure app user exists
      ansible.builtin.user:
        name: "{{ app_user }}"
        state: present
        comment: "Application User"

    - name: Ensure app group exists
      ansible.builtin.group:
        name: "{{ app_group }}"
        state: present
  1. Host and Group Variables (Recommended for Structure)

For larger projects, organizing variables in dedicated host_vars/ and group_vars/ directories is highly recommended. Ansible automatically loads variables from these directories if they match hostnames or group names in your inventory.

Directory Structure:

my_ansible_project/
├── inventory.ini
├── group_vars/
│   ├── webservers.yml
│   └── all.yml
├── host_vars/
│   ├── web1.example.com.yml
│   └── db1.example.com.yml
├── playbooks/
│   └── deploy_app.yml
└── templates/
    └── nginx.conf.j2

group_vars/webservers.yml:

# group_vars/webservers.yml
nginx_port: 80
apache_document_root: /var/www/html

host_vars/web1.example.com.yml:

# host_vars/web1.example.com.yml
nginx_port: 8080 # This will override nginx_port from group_vars for web1

group_vars/all.yml:

# group_vars/all.yml
default_timezone: "America/Los_Angeles"
ssh_port: 22

Variable Precedence

It’s important to understand variable precedence in Ansible. When the same variable is defined in multiple places, Ansible follows a specific order of precedence (higher number overrides lower):

  • Role defaults
  • Inventory variables (from inventory.ini)
  • Facts (gathered during Gathering Facts task)
  • Playbook variables (vars: in a playbook)
  • Variables from group_vars/all
  • Variables from group_vars/specific_group
  • Variables from host_vars/specific_host
  • set_fact (variables created by tasks during runtime)
  • Extra variables (-e or –extra-vars on the command line)

This means host_vars will override group_vars, and command-line variables will override almost everything else.

Introduction to Templates (Jinja2)

While the copy module is great for static files, configuration files often need dynamic content (e.g., server names, port numbers, paths) based on variables. This is where the template module and Jinja2 templating come in.

Jinja2 is a modern and designer-friendly templating language for Python. Ansible uses it extensively.

Creating a Template File

Template files typically have a .j2 extension. Let’s create an Nginx configuration template.

  1. Create a templates directory:
mkdir templates
  1. Create templates/nginx.conf.j2:
# templates/nginx.conf.j2
server {
    listen {{ nginx_port }};
    server_name {{ ansible_fqdn }}; # ansible_fqdn is a fact

    root {{ apache_document_root }};
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    # Example of a conditional block in Jinja2
    {% if enable_ssl is defined and enable_ssl %}
    listen 443 ssl;
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    {% endif %}
}

Explanation of Jinja2 syntax:

  • {{ variable_name }}: Renders the value of the variable.
  • {% if condition %} … {% endif %}: Control structures for conditional logic.
  • {% for item in list %} … {% endfor %}: Loops for iterating over lists (will cover later).
  • ansible_fqdn: An example of an Ansible fact (fully qualified domain name).

Using the template Module in a Playbook

Now, let’s use this template in a playbook and define the necessary variables.

  1. Create playbooks/deploy_nginx_with_template.yml:
---
# playbooks/deploy_nginx_with_template.yml
- name: Deploy Nginx with a templated configuration
  hosts: webservers
  become: true
  vars:
    # Playbook-level variables for this specific playbook
    nginx_conf_dest_path: /etc/nginx/conf.d/default.conf
    # This will override any nginx_port from inventory or group_vars if defined
    # unless a host_var is set higher in precedence
    nginx_port: 80

  tasks:
    - name: Install Nginx
      ansible.builtin.yum: # or apt for Debian/Ubuntu
        name: nginx
        state: present

    - name: Deploy Nginx configuration from template
      ansible.builtin.template:
        src: ../templates/nginx.conf.j2 # Path relative to the playbook location
        dest: "{{ nginx_conf_dest_path }}"
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx

    - name: Start and enable Nginx service
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers: # Handlers are special tasks that only run when notified
    - name: Restart Nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Important Notes:

  • src and dest in template module:
  • src: The path to your template file on the control node.
  • dest: The path where the rendered file will be placed on the managed node.
  • notify: Restart Nginx: This is how handlers are triggered. If the template task makes a change (i.e., the rendered file is different from the existing one), it will notify the “Restart Nginx” handler.
  • handlers: section: Handlers are special tasks that are only run once at the end of a play, even if notified multiple times. This is perfect for service restarts after configuration changes.

Setting Variables for the Playbook

To make this playbook work, we need to ensure the variables nginx_port (if you want to override the playbook default), apache_document_root, and potentially enable_ssl are defined.

Let’s use group_vars for apache_document_root and command-line variables for nginx_port and enable_ssl to demonstrate precedence.

  1. Create group_vars/webservers.yml:
# group_vars/webservers.yml
apache_document_root: /usr/share/nginx/html # Common default for Nginx
  1. Run the Playbook:
ansible-playbook -i inventory.ini playbooks/deploy_nginx_with_template.yml \
  -e "nginx_port=8080 enable_ssl=true"
  • -e “nginx_port=8080 enable_ssl=true”: This passes nginx_port as 8080 and enable_ssl as true directly to the playbook. These variables will override the nginx_port: 80 defined in the playbook’s vars section due to higher precedence.

After running, check the default.conf file on your web server (cat /etc/nginx/conf.d/default.conf) to see the rendered values.

Debugging Variables

The debug module is incredibly useful for inspecting variable values during playbook execution.

---
- name: Debugging variables
  hosts: webservers
  tasks:
    - name: Show Nginx port
      ansible.builtin.debug:
        msg: "The Nginx port is: {{ nginx_port }}"

    - name: Show OS family (a fact)
      ansible.builtin.debug:
        msg: "OS Family: {{ ansible_os_family }}"

    - name: Show the value of a variable that might not be defined
      ansible.builtin.debug:
        msg: "My custom var: {{ my_custom_var | default('Not Defined') }}"
      # Using a Jinja2 filter 'default' to provide a fallback value

Run this playbook (perhaps after setting my_custom_var in your group_vars or with -e) and observe the output.

Next Steps

In Part 4, we will delve into Control Flow with loops and conditionals, which are essential for handling lists of items and making tasks run conditionally.