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
Examples of invalid variable names:
1st_user # Starts with a number
user-name # Contains a hyphen
backup@time # Contains an invalid character '@'
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 }}"
Explanation:
- The vars section defines ‘app_name’ and ‘app_port’
- The variables are accessed using Jinja2 syntax [.code]({{ variable_name }})[.code] 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
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
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 }}"
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"
Host variables (host_vars/web1.yml):
app_port: 8080
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 [.code]-e[.code] or [.code]--extra-vars[.code] 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'"
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
}
YAML example (vars.yml):
app_name: "DynamicApp"
app_port: 9090
Passing the file:
ansible-playbook playbook.yml --extra-vars @vars.json
or
ansible-playbook playbook.yml --extra-vars @vars.yml
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
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
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"
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
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 }}"
Explanation:
- [.code]servers[0].name[.code] retrieves the name of the first server in the list (web1)
- [.code]servers[0].ip[.code] 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 }}"
Output:
PLAY [Echo playbook] *********************************************************
TASK [Echo inventory_hostname] ***********************************************
ok: [localhost] => {
"msg": [
"Hello from Ansible playbook!",
"This is running on localhost"
]
}
Other essential magic variables
- hostvars: Provides information about other hosts in the inventory, including their associated variables
- Example: [.code]hostvars['web1'].ansible_host[.code] retrieves the 'ansible_host' variable for the web1 host
- group_names: Contains a list of group names to which the current host belongs
- Example: [.code]group_names[.code] helps identify the roles or purposes assigned to the current host
- groups: Group names to the list of hosts in each group
- Example: [.code]groups['webservers'][.code] 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 [.code]ansible_connection[.code] 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 }}"
Explanation:
- The connection: local directive specifies that the playbook should run locally rather than connecting to a remote host
- The [.code]ansible_connection[.code] 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"
]
}
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 [.code]ansible_facts[.code] 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 [.code]ansible_facts[.code] dictionary and can also be accessed as top-level variables with the [.code]ansible_ prefix[.code]
- 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
Alternatively, you can gather raw fact data from the command line:
ansible <hostname> -m ansible.builtin.setup
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'] }}.
Explanation:
- [.code]ansible_facts['nodename'][.code]: Retrieves the system hostname
- [.code]ansible_facts['default_ipv4']['address'][.code]: 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."
}
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'] }}
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"}}'
Access the fact in a playbook:
- name: Display dynamic fact
ansible.builtin.debug:
msg: "User Count: {{ ansible_local['dynamic_fact']['user_count'] }}"
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):
- command line values (for example, -u my_user, these are not variables)
- role defaults
- inventory file or script group vars
- inventory group_vars/all
- playbook group_vars/all
- inventory group_vars/*
- playbook group_vars/*
- inventory file or script host vars
- inventory host_vars/*
- playbook host_vars/*
- host facts / cached set_facts
- play vars
- play vars_prompt
- play vars_files
- role vars
- block vars (only for tasks in block)
- task vars (only for the task)
- include_vars
- set_facts / registered vars
- role (and include_role) params
- include params
- 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:
- The Essential Ansible Tutorial: A Step by Step Guide
- Mastering Ansible Playbooks: Examples and Step by Step Guide
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 [.code]ANSIBLE_CLI_inventory[.code] 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?
[.code]{{ item }}[.code] 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 ([.code]-e[.code]) 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 [.code]set_fact[.code] during playbook execution.
Q. How do I use environment variables in an Ansible playbook?
Access environment variables using the [.code]ansible_env[.code] dictionary or pass them explicitly when running the playbook.
Q. How do I pass variables to Ansible playbook?
Variables can be passed using the [.code]-e[.code] option, included in inventory files, or defined in external files and referenced in the playbook.