Forem

Cover image for Mastering Ansible Variables: Practical Guide with Examples
env0 Team for env0

Posted on • Originally published at evnzeeero-1c3bda8a-6e07-41f7-93a4-1dc2c.webflow.io

Mastering Ansible Variables: Practical Guide with Examples

Ansible variables enable you to manage differences between systems, environments, and configurations, making your automation tasks more streamlined and adaptable. In this blog post, we dive into how these variables can be best utilized, through a series of step-by-step guides and practical examples.

What are  Ansible Variables

Ansible variables are dynamic components that allow for reusability in playbooks and roles, enhancing the efficiency of configurations. 

By using variables, you can make your Ansible projects adaptable to different environments and scenarios, allowing for more robust automation and efficient configurations.

Why Use Variables?

  • Dynamic configurations: Variables allow you to define values that can change based on the environment or context, such as different server IPs, usernames, or package versions
  • Simplified management: By using variables, you can manage complex configurations more easily, as changes need to be made in only one place
  • Enhanced readability: Meaningful variable names make playbooks more understandable and maintainable

Variable Naming Rules

Ansible enforces specific rules for variable names to ensure consistency and prevent conflicts:

  • Start with a letter or underscore: Variable names must begin with a letter (a-z, A-Z) or an underscore (_)
  • Allowed characters: Subsequent characters can include letters, numbers (0-9), and underscores
  • Avoid reserved words: Do not use reserved words from Python or Ansible's playbook keywords as variable names

Examples of valid variable names:

server_port
_user_name
backup_interval_7
Enter fullscreen mode Exit fullscreen mode

Examples of invalid variable names:

1st_user   # Starts with a number
user-name  # Contains a hyphen
backup@time # Contains an invalid character '@'
Enter fullscreen mode Exit fullscreen mode

Adhering to these naming conventions is a good start, but you also need to give your variables meaningful names so anyone reading your Ansible playbooks will understand what they are for.

Where Can You Use Ansible Variables?

Ansible provides multiple ways to define and use variables, each tailored to specific use cases. Below is a detailed explanation of the various methods, with examples to illustrate their practical applications.

1. Defining Variables in Playbooks

Defining variables directly in a playbook is the simplest and most accessible method. This approach is useful when you want to tightly couple variables with a specific playbook or perform quick tests. Variables declared in this manner are scoped to the playbook and cannot be reused elsewhere.

Playbook example:

---
- name: Example Playbook
  hosts: all
  vars:
    app_name: "MyApp"
    app_port: 8080
  tasks:
    - debug:
        msg: "Deploying {{ app_name }} on port {{ app_port }}"
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The vars section defines ‘app_name’ and ‘app_port’
  • The variables are accessed using Jinja2 syntax ({{ variable_name }}) in tasks. 

This method keeps the playbook self-contained but can become unwieldy if the number of variables increases.

2. Defining Variables in Inventory Files

Inventory files allow you to associate variables with specific hosts or groups of hosts. This method is ideal for defining system-specific or environment-specific values without altering playbooks.

Example inventory file (hosts):

[webservers]
web1 ansible_host=192.168.1.10 app_port=8080
web2 ansible_host=192.168.1.11 app_port=8081

[databases]
db1 ansible_host=192.168.1.20 db_name=prod_db
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Host-specific variables like ‘app_por’t and ‘db_name’ are defined alongside each host
  • Groupings such as [webservers] and [databases] help categorize hosts by role
  • These variables can be directly accessed in playbooks targeting these hosts or groups 

This method ensures configuration flexibility across different environments or roles.

3. Defining Variables in Separate Variable Files

Storing variables in separate files is a best practice for larger projects. This keeps the playbooks clean and allows variables to be reused across multiple playbooks.

Variable file (vars/main.yml):

app_name: "MyApp"
app_port: 8080
Enter fullscreen mode Exit fullscreen mode

Playbook example:

---
- name: Include Variables from File
  hosts: all
  vars_files:
    - vars/main.yml
  tasks:
    - debug:
        msg: "The app name is {{ app_name }} running on port {{ app_port }}"
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The vars_files directive includes external variable files, making the playbook easier to read and maintain
  • Variable files can follow specific naming conventions for better organization, such as vars/dev.yml for development or vars/prod.yml for production

This approach supports modularity and scalability in managing configurations.

4. Group and Host Variables

Group and host variables are stored in designated directories (group_vars and host_vars) and automatically applied to their respective groups or hosts. This structure is highly scalable and ensures consistent configurations across environments.

Group variables (group_vars/webservers.yml):

app_name: "WebApp"
Enter fullscreen mode Exit fullscreen mode

Host variables (host_vars/web1.yml):

app_port: 8080
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Variables in group_vars apply to all hosts in the group, such as webservers
  • Variables in host_vars override group variables for a specific host, such as web1 

This approach makes managing variables for complex inventories simpler and enforces a clear separation of concerns.

5. Defining Variables at Runtime

The -e or --extra-vars option lets you define variables dynamically during playbook execution. This is especially useful for ad-hoc configurations or testing.

Example command:

ansible-playbook playbook.yml -e "app_port=9090 app_name='DynamicApp'"
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Variables passed at runtime override other variable definitions in the hierarchy

This method is convenient for temporarily changing settings without editing files. It is, however, less suitable for configurations that need to be reused consistently.

6. JSON and YAML Files for Runtime Variables

When working with a large number of variables, JSON or YAML files provide a structured way to pass variables at runtime.

JSON example (vars.json):

{
  "app_name": "DynamicApp",
  "app_port": 9090
}
Enter fullscreen mode Exit fullscreen mode

YAML example (vars.yml):

app_name: "DynamicApp"
app_port: 9090
Enter fullscreen mode Exit fullscreen mode

Passing the file:

ansible-playbook playbook.yml --extra-vars @vars.json
Enter fullscreen mode Exit fullscreen mode

or

ansible-playbook playbook.yml --extra-vars @vars.yml
Enter fullscreen mode Exit fullscreen mode

Explanation:

The structured format improves readability and minimizes errors when passing multiple variables. This method is excellent for managing complex variable sets with special characters.

Types of Ansible Variables

Ansible supports a wide range of variable types to handle diverse data structures and use cases. Here's an in-depth look at the variable types, along with detailed examples to clarify their applications.

1. Simple Variables

Simple variables store a single value, such as a string, number, or boolean. These are ideal for straightforward configurations where each variable represents a single property or setting.

Examples:

app_name: "SimpleApp"   # A string value
max_retries: 5          # An integer value
debug_mode: true        # A boolean value
Enter fullscreen mode Exit fullscreen mode

Use case:

  • Simple variables work well for settings like application names (‘app_name’), numeric parameters (‘max_retries’), or flags (‘debug_mode’)
  • These variables are easy to define and reference, making them suitable for straightforward configurations

2. Complex Variables

These variables allow for more sophisticated configurations by supporting lists, dictionaries, and nested data structures. These are essential for managing interconnected or hierarchical data.

a. Lists

Lists are used to define an ordered collection of items. Each item in the list can represent a related piece of information, such as versions, IP addresses, or tasks.

Example:

supported_versions:
  - 1.0
  - 1.1
  - 2.0
Enter fullscreen mode Exit fullscreen mode

Use case:

  • Lists are perfect for scenarios where you need to iterate over multiple values, such as supported application versions or a list of servers

b. Dictionaries

Dictionaries, also called maps or hashes, define key-value pairs. They are ideal for grouping related configurations into a single structure.

Example:

database_config:
  host: "db.example.com"
  port: 5432
  username: "db_user"
  password: "secure_password"
Enter fullscreen mode Exit fullscreen mode

Use case:

  • Dictionaries work well for organizing structured data like database configurations (database_config), where each key-value pair represents a specific setting
  • You can easily reference individual elements, such as database_config.host which uses the dot notation

c. Nested variables

These variables combine lists and dictionaries to model more complex relationships, such as defining multiple servers with unique attributes.

Example:

servers:
  - name: web1
    ip: 192.168.1.10
  - name: web2
    ip: 192.168.1.11
Enter fullscreen mode Exit fullscreen mode

Use case:

  • Nested variables are invaluable for scenarios involving groups of objects, such as multiple servers, where each object has its own properties (name, ip)

Referencing nested variables: Nested variables can be accessed using dot notation or bracket notation.

Example in a task:

- debug:
    msg: "{{ servers[0].name }} has IP {{ servers[0].ip }}"
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • servers[0].name retrieves the name of the first server in the list (web1)
  • servers[0].ip retrieves the ip of the first server (192.168.1.10)

This allows you to dynamically access and use specific elements of nested data structures.

Special Variables in Ansible

Ansible's special variables are predefined and offer insights into the system data, inventory, or execution context of a playbook or role. These variables are categorized into magic variables, connection variables, and facts, each serving a specific purpose. 

It’s crucial to note that these variable names are reserved by Ansible and cannot be redefined. Below, we explore these categories in detail.

Magic Variables

Ansible automatically creates magic variables to reflect its internal state. These variables cannot be altered by users but can be accessed directly to retrieve useful information about the playbook’s execution and environment.

Example: Using inventory_hostname

This magic variable represents the name of the current host in the inventory.

Playbook example:

- name: Echo playbook
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Echo inventory_hostname
      ansible.builtin.debug:
        msg:
          - "Hello from Ansible playbook!"
          - "This is running on {{ inventory_hostname }}"
Enter fullscreen mode Exit fullscreen mode

Output:

PLAY [Echo playbook] *********************************************************
TASK [Echo inventory_hostname] ***********************************************
ok: [localhost] => {
    "msg": [
        "Hello from Ansible playbook!",
        "This is running on localhost"
    ]
}
Enter fullscreen mode Exit fullscreen mode

Other essential magic variables

  • hostvars: Provides information about other hosts in the inventory, including their associated variables
    • Example: hostvars['web1'].ansible_host retrieves the 'ansible_host' variable for the web1 host
  • group_names: Contains a list of group names to which the current host belongs
    • Example: group_names helps identify the roles or purposes assigned to the current host
  • groups: Group names to the list of hosts in each group
    • Example: groups['webservers'] returns all hosts in the webservers group

These variables are indispensable for dynamic inventory management and playbook flexibility. You can reference the list in the documentation here.

Connection Variables

Connection variables control how Ansible connects to remote hosts during playbook execution. They define the connection type, user, and other related settings.

Example: Using ansible_connection

The ansible_connection variable indicates the connection type (e.g., SSH, local, or winrm).

Playbook example:

- name: Echo message on localhost
  hosts: localhost
  connection: local
  gather_facts: no
  vars:
    message: "Hello from Ansible playbook on localhost!"
  tasks:
    - name: Echo message and connection type
      ansible.builtin.shell: "echo '{{ message }}' ; echo 'Connection type: {{ ansible_connection }}'"
      register: echo_output
    - name: Display output
      ansible.builtin.debug:
        msg: "{{ echo_output.stdout_lines }}"
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The connection: local directive specifies that the playbook should run locally rather than connecting to a remote host
  • The ansible_connection variable dynamically identifies the connection type (local in this case)

Sample output:

TASK [Echo message and connection type] ***************************************
ok: [localhost] => {
    "msg": [
        "Hello from Ansible playbook on localhost!",
        "Connection type: local"
    ]
}
Enter fullscreen mode Exit fullscreen mode

Ansible Facts

Ansible facts are a collection of data automatically gathered about remote systems during playbook execution. 

They provide detailed information about the system, such as operating system details, network interfaces, disk configurations, and more. Facts are stored in the ansible_facts variable, which can be used in tasks, conditionals, and templates.

Key features of Ansible facts:

  • Automatic collection: Facts are gathered at the start of each play by default
  • Access and scope: Facts are available in the ansible_facts dictionary and can also be accessed as top-level variables with the ansible_ prefix
  • Customization: Users can add custom facts or disable fact gathering if not needed

Example: Viewing facts

To see all available facts for a host, add this task to a playbook:

- name: Print all available facts
  ansible.builtin.debug:
    var: ansible_facts
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can gather raw fact data from the command line:

ansible <hostname> -m ansible.builtin.setup
Enter fullscreen mode Exit fullscreen mode

Using facts in playbooks

Facts allow you to dynamically configure tasks based on system attributes. For example, you can retrieve the hostname and default IPv4 address of a system:

Playbook example:

- name: Display system facts
  hosts: all
  tasks:
    - name: Show hostname and default IPv4
      ansible.builtin.debug:
        msg: >
          The system {{ ansible_facts['nodename'] }} has IP {{ ansible_facts['default_ipv4']['address'] }}.
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ansible_facts['nodename']: Retrieves the system hostname
  • ansible_facts['default_ipv4']['address']: Retrieves the default IPv4 address

Sample output:

TASK [Show hostname and default IPv4] *****************************************
ok: [host1] => {
    "msg": "The system host1 has IP 192.168.1.10."
}
Enter fullscreen mode Exit fullscreen mode

Common use cases for facts

  • Dynamic Configurations

    • Use facts to configure tasks dynamically, such as installing packages based on the OS family:
    • name: Install package based on OS ansible.builtin.package: name: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}" state: present
  • Conditionals and filters: Use facts to apply conditional logic, such as running tasks only on systems with specific attributes

  • Custom facts: Custom facts allow you to extend Ansible's default capabilities by defining your own facts specific to your needs. These can be either static facts (defined in files) or dynamic facts (generated by scripts). Custom facts are stored in the ansible_local namespace to avoid conflicts with system facts.

Example: Adding a static fact

Create a file for the fact:

  • On the remote host, create a directory /etc/ansible/facts.d
  • Add a file named custom.fact with content in INI format:

    [general]
    app_version = 1.2.3
    environment = production

Access the fact in a playbook:

- name: Show custom facts
  ansible.builtin.debug:
    msg: >
      App Version: {{ ansible_local['custom']['general']['app_version'] }},
      Environment: {{ ansible_local['custom']['general']['environment'] }}
Enter fullscreen mode Exit fullscreen mode

Example: Adding a dynamic fact

Create a script for the fact:

  • Place an executable script (e.g., generate fact.sh) in /etc/ansible/facts.d
  • The script should output JSON:
    #!/bin/bash
    echo '{"dynamic_fact": {"user_count": 10, "status": "active"}}'
Enter fullscreen mode Exit fullscreen mode

Access the fact in a playbook:

- name: Display dynamic fact
ansible.builtin.debug:
msg: "User Count: {{ ansible_local['dynamic_fact']['user_count'] }}"
Enter fullscreen mode Exit fullscreen mode




Best Practices with Facts

  • Caching Facts: Use fact caching to improve performance in large environments or repetitive tasks.
  • Disabling Facts: Turn off fact gathering for better scalability if you don’t need system details as in the playbook below:

    • hosts: all gather_facts: false

Variable Precedence

Because variables can be defined in various locations, Ansible uses a precedence hierarchy to decide which value takes effect. Below is the entire list as defined in the documentation, with the least precedence at the top (the last listed variables override all other variables):

  1. command line values (for example, -u my_user, these are not variables)
  2. role defaults
  3. inventory file or script group vars
  4. inventory group_vars/all
  5. playbook group_vars/all
  6. inventory group_vars/*
  7. playbook group_vars/*
  8. inventory file or script host vars
  9. inventory host_vars/*
  10. playbook host_vars/*
  11. host facts / cached set_facts
  12. play vars
  13. play vars_prompt
  14. play vars_files
  15. role vars
  16. block vars (only for tasks in block)
  17. task vars (only for the task)
  18. include_vars
  19. set_facts / registered vars
  20. role (and include_role) params
  21. include params
  22. extra vars (for example, -e "user=my_user")(always win precedence)

Conclusion and Key Takeaways

Ansible variables enable scalable, flexible, and reusable automation. By mastering their usage and following best practices, you can enhance your Ansible projects' efficiency and maintainability.

Key takeaways include:

  • Flexibility: Variables adapt your playbooks to different contexts with minimal changes.
  • Organization: Properly organizing and centralizing variables reduces redundancy and simplifies management.
  • Efficiency: Leveraging advanced techniques like combining variables ensures scalability in larger projects.

By applying the best practices outlined here, you can make your Ansible projects more robust, maintainable, and easier to collaborate on.

If you're interested in learning more about Ansible, I recommend these two blog posts:

Why Ansible is Better with env0

Integrating Ansible with env0 revolutionizes infrastructure management by combining Ansible’s powerful automation capabilities with env0’s advanced orchestration and collaboration features. This integration simplifies workflows, reduces manual effort, and enhances governance.

Key Advantages of env0 Integration

1. Effortless automation: Instead of manually running Ansible commands through the CLI, env0 allows you to directly define and manage environments. This streamlines the deployment process, reducing errors and improving consistency.

2. Seamless template management: Use env0 to create and manage environments based on Ansible templates. These templates can specify the Ansible version, SSH keys, and other configurations, ensuring that your deployments adhere to organizational standards.

3. Enhanced GitHub integration: Link your env0 environments to your GitHub repository. By specifying the folder where your Ansible playbooks reside, env0 ensures that your scripts and configurations are always accessible and up-to-date.

4. Simplified variable handling: With env0, defining and managing environment variables like ANSIBLE_CLI_inventory becomes straightforward. This enables Ansible to dynamically locate and utilize the correct inventory files for deployments.

5. Automated execution: Once an environment is initiated, env0 handles the entire process: cloning the repository, setting up the working directory, loading variables, and executing the playbooks. This eliminates repetitive tasks and accelerates deployment cycles.

6. Governance and collaboration: env0’s built-in RBAC and OPA policy support ensures that deployments remain secure and compliant. Team members can collaborate efficiently with clear access controls and activity tracking.

7. Comprehensive logs and insights: Review deployment logs in env0 to verify configurations and monitor playbook execution. This transparency aids troubleshooting and ensures accountability.

8. Multi-framework support: Combine Ansible with other IaC tools like Terraform, OpenTofu, Pulumi, or CloudFormation within env0. This flexibility allows teams to use the best tools for specific tasks while maintaining a cohesive workflow.

By integrating Ansible with env0, teams can achieve greater efficiency, improve collaboration, and maintain strict governance over their infrastructure. 

Whether managing simple configurations or complex deployments, env0 ensures that Ansible users can focus on innovation rather than operational overhead.

Frequently Asked Questions 

Q. How do I specify a variable in Ansible?

Variables can be specified using the vars keyword in a playbook, inventory files, or through external variable files. They can also be passed at runtime using the -e flag.

Q. What is {{ item }} in Ansible?

{{ item }} is a placeholder used in loops to reference the current item being iterated over.

Q. What is the difference between vars_files and include_vars?

  • vars_files: Used to include external variable files in a playbook
  • include_vars: A task that dynamically includes variable files during playbook execution

Q. How do I use environment variables in Ansible?

Environment variables can be accessed using the ansible_env dictionary. Learn more in the documentation.

Q. How do I pass variables to an Ansible playbook?

Variables can be passed using the -e flag, through inventory files, or by including them in variable files referenced in the playbook.

Q. What is the order of precedence for Ansible variables?

The order of precedence determines which variable value is used when multiple variables with the same name exist. Extra variables (-e) have the highest precedence.

Q. What is the order of execution in Ansible?

Ansible executes tasks in the order they are listed in the playbook, applying variables and configurations as it progresses.

Q. What is the Ansible naming convention for variables?

Variables should start with a letter or underscore, and subsequent characters can include letters, numbers, and underscores. Avoid using reserved words or special characters.

Q. How do I set the env variable using Ansible?

Use the environment keyword in tasks to set environment variables for that task.

Q. How do I assign variables in Ansible?

Assign variables using the vars keyword, in inventory files, or through set_fact during playbook execution.

Q. How do I use environment variables in an Ansible playbook?

Access environment variables using the ansible_env dictionary or pass them explicitly when running the playbook.

Q. How do I pass variables to Ansible playbook?

Variables can be passed using the -e option, included in inventory files, or defined in external files and referenced in the playbook.

Top comments (0)