Ansible Variables
Variables are a fundamental concept in Ansible that allow you to create more flexible, reusable, and maintainable playbooks. By using variables, you can write playbooks once and use them across different environments and scenarios without modifying the code.
Introduction to Ansible Variables
In automation, hard-coding values like server names, file paths, or configuration settings creates brittle playbooks that aren't easily reused. Variables solve this problem by allowing you to define values in one place and reference them throughout your playbooks.
Think of variables as containers that store values which can be used and reused across your Ansible project.
Variable Basics
Defining Variables
Ansible provides several ways to define variables:
- In playbooks directly
- In inventory files
- In separate variable files
- On the command line
- In roles
Let's look at some examples:
# Variables defined directly in a playbook
---
- name: Example playbook with variables
hosts: webservers
vars:
http_port: 80
max_clients: 200
app_name: "my_application"
tasks:
- name: Print variables
debug:
msg: "Setting up {{ app_name }} with port {{ http_port }}"
Variable Naming Rules
Variable names in Ansible must follow these rules:
- Must start with a letter
- Can only contain letters, numbers, and underscores
- Should be lowercase with underscores separating words (snake_case)
Valid: user_name
, http_port
, max_connections_per_host
Invalid: User-Name
, 1st_variable
, variable-name
Variable Types
Ansible supports various data types for variables:
Strings
app_name: "my_application"
server_message: 'Welcome to our server'
Numbers
http_port: 80
max_clients: 200
timeout_seconds: 30
Boolean
enable_feature: true
debug_mode: false
Lists
users:
- alice
- bob
- charlie
fruits:
- apple
- banana
- cherry
Dictionaries
server:
name: web01
ip: 192.168.1.100
ports:
- 80
- 443
employee:
name: John Smith
department: IT
skills:
- python
- ansible
- linux
Accessing Variables
Basic Variable Access
To use a variable in Ansible, wrap it in double curly braces:
- name: Create configuration file
template:
src: app.conf.j2
dest: /etc/app/app.conf
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0644'
Accessing Dictionary Values
To access values in dictionaries:
- name: Print server details
debug:
msg: "Server name is {{ server.name }} with IP {{ server.ip }}"
Accessing List Items
To access list items by index:
- name: Print first user
debug:
msg: "First user is {{ users[0] }}"
Variable Files
For better organization, you can store variables in separate YAML files:
# group_vars/webservers.yml
---
http_port: 80
https_port: 443
max_clients: 200
app_name: "web_application"
# host_vars/web01.yml
---
server_ip: 192.168.1.10
backup_enabled: true
monitoring_interval: 5
Your inventory might look like:
# inventory.ini
[webservers]
web01
web02
web03
When you run a playbook, Ansible automatically loads variables from:
group_vars/all.yml
(applies to all hosts)group_vars/webservers.yml
(applies to hosts in the webservers group)host_vars/web01.yml
(applies to the specific host web01)
Practical Examples
Example 1: Configuring a Web Server
---
- name: Configure web servers
hosts: webservers
vars:
http_port: 80
doc_root: /var/www/html
app_env: production
tasks:
- name: Install Apache
apt:
name: apache2
state: present
become: true
- name: Configure Apache port
lineinfile:
path: /etc/apache2/ports.conf
regexp: '^Listen '
line: "Listen {{ http_port }}"
notify: restart apache
become: true
- name: Create document root
file:
path: "{{ doc_root }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
become: true
- name: Deploy environment configuration
template:
src: env.conf.j2
dest: "{{ doc_root }}/.env"
owner: www-data
group: www-data
mode: '0644'
become: true
handlers:
- name: restart apache
service:
name: apache2
state: restarted
become: true
With template file env.conf.j2
:
APP_ENV={{ app_env }}
APP_PORT={{ http_port }}
APP_ROOT={{ doc_root }}
Example 2: User Management with Variable Files
Define users in a variable file:
# group_vars/all.yml
---
users:
- username: alice
groups: admin,developers
shell: /bin/bash
ssh_key: "ssh-rsa AAAA..."
- username: bob
groups: developers
shell: /bin/bash
ssh_key: "ssh-rsa BBBB..."
- username: charlie
groups: testers
shell: /bin/zsh
ssh_key: "ssh-rsa CCCC..."
Create a playbook that uses this variable file:
---
- name: Manage users
hosts: all
tasks:
- name: Create user groups
group:
name: "{{ item }}"
state: present
loop:
- admin
- developers
- testers
become: true
- name: Create users
user:
name: "{{ item.username }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
state: present
loop: "{{ users }}"
become: true
- name: Add SSH keys
authorized_key:
user: "{{ item.username }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ users }}"
become: true
Variable Precedence
Ansible follows a specific order when determining which variable value to use. Here's a simplified precedence list (from lowest to highest):
- Default variables defined in roles
- Group variables defined in inventory file
- Host variables defined in inventory file
- Group variables defined in group_vars/ directory
- Host variables defined in host_vars/ directory
- Variables defined in playbook
- Variables passed on the command line with
-e
or--extra-vars
This means command-line variables will override any other definitions, which is useful for temporary changes.
Using Jinja2 with Variables
Ansible uses Jinja2 templating, allowing for powerful variable manipulation:
Filters
- name: Convert to uppercase
debug:
msg: "{{ app_name | upper }}"
- name: Default value if undefined
debug:
msg: "{{ optional_var | default('default_value') }}"
- name: Join list into string
debug:
msg: "Users: {{ users | join(', ') }}"
Conditionals
- name: Install package based on OS
package:
name: "{{ package_name }}"
state: present
when: ansible_distribution == "Ubuntu"
Variable Registration
You can capture output from tasks into variables using register
:
- name: Check if file exists
stat:
path: /etc/myapp.conf
register: stat_result
- name: Display information about the file
debug:
msg: "File exists: {{ stat_result.stat.exists }}"
when: stat_result.stat.exists
Environment Variables
Ansible can access and set environment variables:
- name: Run command with environment variables
shell: my_app --config-check
environment:
APP_ENV: production
DEBUG: "false"
API_KEY: "{{ api_key }}"
register: command_output
Variable Encryption with Ansible Vault
For sensitive variables like passwords, use Ansible Vault:
# Creating an encrypted variable file
ansible-vault create secret_vars.yml
You might put the following in the encrypted file:
# Inside secret_vars.yml (encrypted)
db_password: SuperS3cretP@ssw0rd
api_key: 9a72h3b8c0edf5g6h7j8k9l0
Include the encrypted file in your playbook:
---
- name: Database configuration
hosts: database_servers
vars_files:
- secret_vars.yml
tasks:
- name: Configure database password
template:
src: db.conf.j2
dest: /etc/db.conf
owner: root
group: root
mode: '0600'
Run the playbook with the vault password:
ansible-playbook db_playbook.yml --ask-vault-pass
# Or with a password file
ansible-playbook db_playbook.yml --vault-password-file ~/.vault_pass
Variable Prompts
Ansible can prompt for variables at runtime:
---
- name: Interactive playbook
hosts: all
vars_prompt:
- name: username
prompt: "Enter username"
private: no
- name: password
prompt: "Enter password"
private: yes
tasks:
- name: Create user account
user:
name: "{{ username }}"
password: "{{ password | password_hash('sha512') }}"
state: present
become: true
Debugging Variables
When troubleshooting, use the debug
module:
- name: Debug all variables
debug:
var: vars
verbosity: 1
- name: Debug a specific variable
debug:
var: server
verbosity: 1
- name: Display formatted message
debug:
msg: "Connection to {{ server.name }} ({{ server.ip }}) on port {{ server.ports[0] }}"
Variable Scope
Variables in Ansible have different scopes:
- Global scope: Variables defined at the playbook level
- Host scope: Variables defined for specific hosts
- Play scope: Variables defined for a specific play
Summary
Ansible variables provide the flexibility needed to create reusable automation:
- Variables allow you to write playbooks once and use them across different environments
- You can define variables in playbooks, inventory, separate files, or the command line
- Ansible supports various data types: strings, numbers, booleans, lists, and dictionaries
- Variable precedence determines which value takes priority when variables are defined in multiple places
- Jinja2 templating provides powerful ways to manipulate and use variables
- Ansible Vault helps secure sensitive variable information
Exercises
- Create a playbook that deploys a web application to different environments (dev, staging, prod) using variables to control the configuration.
- Write a playbook that uses host variables to configure different database settings for each server in your database group.
- Create a role that installs and configures a service, using variables to make every aspect of the configuration customizable.
- Practice using Jinja2 filters by creating a template that formats a complex data structure into a configuration file.
- Set up an Ansible Vault to store sensitive information and use it in a playbook.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)