Ansible Fundamentals
What is Ansible?
Ansible is a free, open-source configuration management tool that is designed to install and manage the software on existing servers or systems.
It is an agentless tool i.e. we just need to install the Ansible on master node or control machine and it takes care of all the configuration on worker nodes or target machines without needing to install the Ansible on target machines.
Ansible Configuration Files
When we install the Ansible on the system, it creates a default configuration file at the location
/etc/ansible/ansible.cfg
(can change it as well).It is a file that tell the Ansible about the environment. We can use this file to modify the Ansible’s setting to meet our needs.
Ansible consider the configuration defined in this file when we run the playbooks anywhere from the control machine.
This file includes various default configurations for tasks such as inventory management, logging, privilege escalation, connection setting, etc.
Default Location of ansible.cfg
Ansible looks for the configuration file in the following order of precedence:
ANSIBLE_CONFIG environment variable (if set) (we can also set ANSIBLE_<PARAMAETER> to ovverride any one parameter in our config file with new value).
# /etc/ansible/ansible.cfg gathering = implicit # can override this while running the playbook as ANSIBLE_GATHERING=explicit ansible-playbook -i inventory playbook.yml
ansible.cfg
in the current working directory.~/.ansible.cfg
in the home directory./etc/ansible/ansible.cfg
(global configuration).
The first file found in this order is the one Ansible will use.
View Configuration
Lists all configurations
ansible-config list
Show the current configuration file
ansible-config view
Shows the current settings
ansible-config dump
Ansible Inventory
Ansible Inventory is a file in INI or YAML format that defines the hosts or group of hosts that Ansible will manage.
Inventory file consists of information or details of target servers such as IP addresses, hostnames, connection details, etc.
The default location of an inventory file is
/etc/ansible/hosts
.
Common Inventory Attributes
ansible_host
: The IP or hostname for the target machineansible_connection
: ssh/winrm/localhostansible_user
: The SSH user to connect asansible_port
: The SSH port to use (default is 22)ansible_ssh_pass
: The SSH password (not recommended)ansible_ssh_private_key_file
: Path to the SSH private key for authenticationansible_python_interpreter
: The path to the Python interpreter on the host
Inventory Formats
Ansible supports 2 inventory formats: INI and YAML
INI format:
The most basic and widely-used format.
Hosts and groups are defined in a simple key-value or plain-text style.
# Groups of hosts [webservers] web1.example.com web2.example.com [databases] db1.example.com ansible_user=dbadmin ansible_port=5432 db2.example.com ansible_host=192.168.1.10 ansible_user=root # Ungrouped hosts with variables standalone.example.com ansible_user=admin ansible_port=2222
YAML Format:
A modern, structured format that is more readable and flexible.
Typically used for more complex inventories, especially with nested groups or hierarchies.
all: children: webservers: hosts: web1.example.com: web2.example.com: databases: hosts: db1.example.com: ansible_user: dbadmin ansible_port: 5432 db2.example.com: ansible_host: 192.168.1.10 ansible_user: root vars: ansible_ssh_private_key_file: /path/to/key.pem
Grouping and Parent-Child Relationships
In Ansible Inventory, a parent-child relationship refers to a hierarchy where groups (the parent groups) contain other groups (the child groups).
[datacenters] us europe [us] web1.us.example.com db1.us.example.com [europe] web1.europe.example.com db1.europe.example.com
all: children: datacenters: children: us: hosts: web1.us.example.com: db1.us.example.com: europe: hosts: web1.europe.example.com: db1.europe.example.com
Variable Inheritance
Child groups or hosts can inherit variables from their parent groups, while also overriding them if necessary.
iniCopy code[all:vars] ansible_user=admin ansible_port=22 [datacenters:vars] ansible_port=2222 [us] web1.us.example.com db1.us.example.com ansible_port=2022
Variable Precedence:
Default
ansible_user
isadmin
datacenters
setsansible_port
to2222
db1.us.example.com
overrides it to2022
Ansible Variables
- Ansible variables are key-value pairs that allow us to store and use the data dynamically in playbooks.
Syntax
Variables are typically written as
key: value
pairs in YAML files.To reference variables, use the Jinja2 template syntax:
{{ variable_name }}
.--- - hosts: localhost vars: car_model: 'BMW M3' country_name: USA title: 'Systems Engineer' tasks: - command: 'echo "My car is {{ car_model }}"' - command: 'echo "I live in the {{ country_name }}"' - command: 'echo "I work as a {{ title }}"'
Variable Types
String Variables
Sequences of characters.
username: "admin"
Number Variables
Can hold integer or floating-point values.
max_connections: 50
Boolean Variables
Can hold either
true
offalse
.debug_mode: true
List Variables
Can hold an ordered collection of values.
packages: - nginx - postgresql - git
Dictionary Variables
Can hold a collection of key-value pairs.
user: name: admin password: secret
Types of Variables
Host and Group Variables:
Defined in the inventory or
host_vars/
andgroup_vars/
directories.# group_vars/webservers.yaml web_port: 80
Facts:
Automatically gathered system information (e.g.,
ansible_facts
).codeansible_distribution: "Ubuntu" ansible_hostname: "web1"
Registered Variables:
Store the output of a task to use later in the playbook.
Structure of a Registered Variable:
stdout
: The standard output of the command.stderr
: The standard error, if any.rc
: The return code (0 indicates success).stdout_lines
: The standard output split into lines for easier processing.- name: Get uptime command: uptime register: uptime_output - debug: msg: "Uptime is {{ uptime_output.stdout }}"
Role Variables:
- Variables specific to a role, defined in
defaults
orvars
.
- Variables specific to a role, defined in
CLI Variables:
Passed using the
--extra-vars
(-e
) flag.ansible-playbook site.yml -e "web_port=8080 db_host=db.example.com"
Environment Variables:
- System environment variables can be accessed using
lookup('env', 'ENV_VAR_NAME')
.
- System environment variables can be accessed using
Variable Precedence
Ansible resolves variables based on their precedence. The hierarchy (from lowest to highest) is:
Role defaults (
defaults/main.yml
)Inventory group variables
Inventory host variables
Playbook-level variables
Extra variables (
-e
on the command line)
Variable Scoping
A scope defines the accessibility or the visibility of a variable.
Scope of Variables:
Global scope:
Variables that apply across the entire Ansible execution.
Includes variables set in:
Configuration files (e.g.,
ansible.cfg
).CLI options (e.g.,
--extra-vars
).
ansible-playbook site.yml -e "variable=value"
Play scope:
Variables defined within a specific playbook or role.
Limited to the play or the roles/tasks executed as part of it.
- name: Example Play hosts: all vars: play_var: "This is a play variable" tasks: - debug: msg: "{{ play_var }}"
Host scope:
Variables specific to a particular host.
Defined in inventory files,
host_vars/
, or dynamically through facts.# host_vars/web1.yaml host_specific_var: "This is a host variable"
Group scope:
- Defined per group in inventory files or group variable files.
Task Scope:
Variables defined or registered within a task.
Limited to the task or subsequent tasks in the same play.
- name: Get system uptime command: uptime register: uptime_output
Magic Variables
Ansible magic variables are special variables that Ansible automatically defines and makes available during the execution of a playbook.
This variables provide essential information about the current play, host, tasks etc.
(*) ones are important in given below tables.
Common Magic Variables
Play Context Variables
- Provide the details about the playbook and current execution context.
Variable | Description |
inventory_hostname | The name of the current host as defined in the inventory. (*) |
inventory_hostname_short | The short name of the current host (first part before the dot). |
play_hosts | List of all hosts in the current play. |
ansible_playbook_dir | The directory containing the playbook being executed. |
ansible_user | The user Ansible is using to connect to the host. |
Host and Group Variables
- Provide details about the current host and groups in the inventory.
Variable | Description |
group_names | List of groups the current host belongs to. (*) |
groups | Dictionary of all groups and their members. (*) |
hostvars | Dictionary of all variables for all hosts. (*) |
Connection Variables
- Provide details about the connection to the target host.
Variable | Description |
ansible_host | The IP or hostname of the current host. |
ansible_port | The SSH port used to connect to the host. |
ansible_user | The user used for the connection. |
Task Context Variables
- Provide information about the currently executing task.
Variable | Description |
ansible_check_mode | Boolean indicating if Ansible is running in check mode. |
ansible_failed_task | Details of the last failed task (if any). |
ansible_facts | Dictionary of system facts gathered by Ansible. |
Loop Variables
- Used to access details about the current item in a loop.
Variable | Description |
item | Current item in a loop. |
ansible_loop_var | The loop variable name (to avoid conflicts). |
item.key , item.value | Key-value pairs when looping over dictionaries. |
Playbook Execution Variables
- Provide details about the playbook execution environment.
Variable | Description |
playbook_dir | Directory where the current playbook resides. |
role_path | Directory where the current role resides. |
Ansible Facts
Ansible facts are pieces of information collected about a targeted machine during the execution of a playbook.
These facts includes system details such as operating system, configuration, hardware specifications, etc.
Ansible gathers these facts using the
setup
modules at the beginning of a play automatically.By default, Ansible collects facts when a playbook is run. You can disable this by setting
gather_facts: no
in your playbook.- name: Example Playbook hosts: all gather_facts: no tasks: - name: Print a message debug: msg: "Facts are disabled."
We can also set this setting in ansible configuration file with a flag named as
gathering
gathering: smart
- gather by default, but don’t regather if already gatheredgathering: implicit
- gather by default, turn off withgather_facts: False
gathering: explicit
- do not gather by default, must saygather_facts: True
If specified in both playbook and configuration file, the setting in playbook always takes the precedence over the configuration file.
Ansible Playbooks
Ansible playbooks are Ansible’s orchestration language.
It’s a YAML file that defines a series of tasks that Ansible will perform on the target machines.
Basic structure of Ansible Playbook
Playbook: A single YAML file
Plays: Defines a set of action (tasks) to be performed of the hosts
Task: An action to be performed on the hosts
Modules: The different actions run by tasks are modules (predefined tools to execute specific action e.g. file, command, service)
# a single play consisting of multiple tasks - name: Play 1 hosts: localhost tasks: - name: Execute command ‘date’ command: date - name: Execute script on server script: test_script.sh - name: Install httpd service yum: name: httpd state: present - name: Start web server service: name: httpd state: started # multiple (2) plays consisting of multiple tasks - name: Play 1 hosts: localhost tasks: - name: Execute command ‘date’ command: date - name: Execute script on server script: test_script.sh - name: Play 2 hosts: localhost tasks: - name: Install web service yum: name: httpd state: present - name: Start web server service: name: httpd state: started
Note: The host (target machine) we want to run the actions is defined at the play level. The host can be anything defined in the inventory file.
Run a playbook
To execute a playbook:
# run a playbook ansible-playbook <playbook-name>.yaml # playbook help ansible-playbook --help
Verifying Playbooks
Verifying ansible playbooks ensures that they are syntactically correct and logically valid before running them in production.
Syntax Check:
The
--syntax-check
option validates the syntax of a playbook without executing it.ansible-playbook playbook.yml --syntax-check
Dry Run (Check Mode)
The
--check
option runs the playbook without making changes to the target systems.ansible-playbook playbook.yml --check
Diff Mode
The
--diff
option along with--check
provides a before-and-after comparison of playbook changes.ansible-playbook playbook.yml --check --diff
Ansible Lint
Ansible Lint is a command-line tool that checks the playbooks against best practices and coding standards.
# installation pip install ansible-lint #command to run ansible-lint playbook.yml
It provides warnings or errors for deprecated modules, misuse of variables, inefficient practices, etc.
Ansible Conditionals
Ansible Conditionals allows to control the execution of tasks based on specific conditions.
The
when
keyword to specify when a task should execute.Multiple conditions can be combined using
or
,and
andnot
keywords.Note: Conditional statements should not include jinja2 templating delimiters such a {{ }} or {% %}.
# Based on Variables - name: Play 1 hosts: localhost tasks: - name: Install nginx if required apt: name: nginx state: present when: install_nginx == true # install_nginx is a variable # Based on facts and Combining Multiple Conditions - name: Play 2 hosts: localhost tasks: - name: Install nginx only if OS is Ubuntu and install_nginx is true apt: name: nginx state: present when: ansible_distribution == "Ubuntu" and install_nginx == true # Conditions in loops - name: Play 3 hosts: localhost vars: packages: - name: nginx required: True - name: mysql required : True - name: apache required : False tasks: - name: Install '{{ item.name }}' on Debian apt: name: '{{ item.name }}' state: present when: item.required == True loop: '{{ packages }}'
Ansible Loops
Ansible loops are basically a series of iterations that gets performed on a set of data.
It provides a variable named as
item
that can be use t iterate and access a specific data.Simple List Loop:
name: Play 1 hosts: localhost tasks: - name: Install multiple packages apt: name: "{{ item }}" state: present loop: - vim - git - curl
Dictionary Loop:
name: Play 1 hosts: localhost tasks: - name: Create users with specific details user: name: "{{ item.name }}" state: present uid: "{{ item.uid }}" loop: - { name: alice, uid: 1001 } # can also be written as below - { name: bob, uid: 1002 } # - name: alice - { name: charlie, uid: 1003 } # uid: 1001
Range Loop:
name: Play 1 hosts: localhost tasks: - name: Create multiple files file: path: "/tmp/file{{ item }}" state: touch loop: "{{ range(1, 6) | list }}" # This creates /tmp/file1 to /tmp/file5
With_*
with_
Loop (Legacy Syntax) still supported,with_
loops are being replaced byloop
(lookup directive).name: Play 1 hosts: localhost tasks: - name: Install multiple packages yum: name: "{{ item }}" state: present with_items: - httpd - nginx
Some of the example of
with_
:with_items with_file with_url with_mongodb with_dict with_etcd with_env with_filetree With_ini With_inventory_hostnames With_k8s With_manifold With_nested With_nios With_openshift With_password With_pipe With_rabbitmq With_redis With_sequence With_skydive With_subelements With_template With_together With_varnames
Ansible Modules
Ansible Modules are small, reusable scripts that Ansible uses to perform specific tasks on target systems (defined inside the tasks).
Idempotency: Modules ensure tasks are only executed when necessary, avoiding redundant changes.
How to use Ansible Modules
In Ad-Hoc Commands: Modules can be used directly in ad-hoc commands without writing a playbook.
ansible all -m ping ansible webservers -m service -a "name=nginx state=started"
In Playbooks: Modules are specified within tasks in a playbook.
- name: Configure web servers hosts: webservers tasks: - name: Install Apache apt: name: apache2 state: present
Commonly Used Ansible Modules
File and Directory Management:
copy
: Copies files to remote hosts.template
: Renders Jinja2 templates and transfers them to remote hosts.file
: Sets file attributes like permissions, ownership, and state.fetch
: Retrieves files from remote hosts.
tasks:
- name: Copy file to remote server
copy:
src: /local/path/to/file
dest: /remote/path/to/file
Package Management:
apt
: Manages packages on Debian-based systems.yum
: Manages packages on Red Hat-based systems.
tasks:
- name: Install nginx
apt:
name: nginx
state: present
Service Management:
service
: Manages services (start, stop, restart).
tasks:
- name: Ensure nginx is running
service:
name: nginx
state: started
User and Group Management:
user
: Manages users on remote systems.group
: Manages groups.
tasks:
- name: Create a user
user:
name: johndoe
state: present
Command Execution:
command
: Executes commands on the remote host (don’t supports idempotency).shell
: Executes shell commands (includes environment variables and pipes) (don’t supports idempotency).lineinfile
: Search for a line in a file and replace it or add it if it doesn’t exist (supports idempotency).
tasks:
- name: Run a command
command: ls /etc
- name: Display resolve.conf contents
command: cat resolv.conf chdir=/etc
Cloud Resource Management:
ec2
: Manages AWS EC2 instances.gcp_compute_instance
: Manages Google Cloud Compute Engine instances.
tasks:
- name: Launch an AWS EC2 instance
ec2:
key_name: my_key
instance_type: t2.micro
image: ami-12345678
region: us-east-1
Ansible Plugins
Ansible Plugin is a piece of code that enhance or extends the functionality of Ansible.
They allows to customize or change the default behavior of how Ansible works to process tasks, inventory or data.
Types of Ansible Plugins (some of many)
Inventory Plugins:
Manage dynamic inventories from external sources.
Examples:
aws_ec2
,gcp_compute
,azure_rm
Use Case: Fetch host information dynamically from cloud providers or other databases.
Action Plugins:
Modify or enhance the behavior of modules in playbooks.
Examples:
normal
,accelerate
Use Case: Add logic to change how a task executes.
Callback Plugins:
Customize the output or logging behavior during playbook execution. Also provides hooks into Ansible’s execution lifecycle, allowing to capture events and perform custom actions during playbook execution.
Examples:
default
,json
,yaml
Use Case: Change the playbook output format or send notifications.
Connection Plugins:
Define how Ansible connects to target hosts.
Examples:
ssh
,paramiko
,local
Use Case: Support different connection methods, such as SSH, winRM, Docker, or API-based.
Lookup Plugins
Retrieve data from external sources.
Examples:
file
,url
,password
Use Case: Fetch secrets, data files, or content dynamically during playbook execution.
Filter Plugins
Manipulate data in templates or tasks.
Examples:
default
,to_json
,split
Use Case: Transform data in Jinja2 templates.
Ansible Handlers
Ansible Handlers are special tasks in Ansible playbooks that are triggered only when notified by other tasks.
Handlers are typically used to perform operations that need to occur only when a change is made by another task, such as restarting a service after a configuration file is updated.
Handlers are defined under the
handlers
section in a playbook. They work in conjunction with thenotify
directive in tasks.- name: Example of Ansible Handlers hosts: webservers tasks: - name: Update configuration file template: src: /path/to/template.j2 dest: /path/to/destination.conf notify: Restart Web Service handlers: - name: Restart Web Service service: name: apache2 state: restarted
Task:
The
notify
directive triggers a handler when the task makes a change.In the example, if the
template
task updates the configuration file, it notifies theRestart Web Service
handler.
Handler:
Defined under
handlers
with a unique name.Contains the task to be executed, such as restarting the
apache2
service.
Key Points:
Handlers Are Executed After Tasks: Handlers are not executed immediately when notified but are queued until all tasks in the play are completed.
Handlers Run Once: A handler runs only once, even if it is notified multiple times during a play.
Notification Without Change: If a task does not change anything, its
notify
directive does not trigger the handler.Multiple Handlers: A single task can notify multiple handlers, and multiple tasks can notify the same handler.
Force Handlers to Run Earlier:
Use
meta: flush_handlers
to execute handlers immediately instead of waiting for the end of the play.- name: Flush Handlers Immediately meta: flush_handlers
Ansible Roles
Ansible Roles are a way to organize and modularize reusable configuration playbooks.
Roles break down complex playbooks into smaller, reusable components, making them easier to manage, maintain and share.
Why Use Roles?
Reusability: Roles can be reused across multiple playbooks and projects.
Modularity: Break down complex configurations into smaller, manageable components.
Standardization: Provides a consistent and predictable structure for organizing Ansible code.
Maintainability: Easier to update and manage specific parts of the configuration.
Collaboration: Facilitates sharing and collaboration among team members or the broader community.
Structure of an Ansible Role
When creating a role, it follows a standard directory structure:
<role_name>/ ├── tasks/ # Contains the main list of tasks to be executed. │ └── main.yml ├── handlers/ # Defines handlers triggered by tasks. │ └── main.yml ├── templates/ # Stores Jinja2 templates for configuration files. ├── files/ # Stores static files to be copied to remote systems. ├── vars/ # Defines variables specific to the role. │ └── main.yml ├── defaults/ # Defines default variables for the role. │ └── main.yml ├── meta/ # Metadata about the role, including dependencies. │ └── main.yml ├── tests/ # Test playbooks for the role. │ ├── inventory │ └── test.yml └── README.md # Documentation for the role.
How to Create and Use a Role in a Playbook
Create a Role:
ansible-galaxy init <role_name>
Reference the Role in a Playbook: Add the role to a playbook using the
roles
keyword.- hosts: webservers roles: - nginx
Roles location
We can define Ansible Roles inside a roles directory under the playbook directory like below:
my-playbooks/ ├── playbook.yml ├── roles/ # roles directory consisting of all the roles e.g. mysql │ └── mysql/ │ └── README.md │ └── templates/ │ └── tasks/ │ └── handlers/ │ └── vars/ │ └── defaults/ │ └── meta/
We can move the roles to a common directory designated for roles on the system
/etc/ansible/roles
location.Default location where ansible searches for the roles if it can’t be found in a playbook directory.
This is defined inside a Ansible Configuration file i.e.
/etc/ansible/ansible.cfg
.# /etc/ansible/ansible.cfg roles_path = /etc/ansible/roles
Ansible Galaxy
Ansible Galaxy is a community-driven platform for searching, sharing and managing Ansible Roles and Collections.
It act as a repository where users can publish their roles and collections.
Ansible Galaxy Operations
Initialize a New Role
ansible-galaxy init <role-name>
Find/Search Roles
ansible-galaxy search <role-name>
Install a Role
ansible-galaxy install <namespace>.<role-name>
List installed Roles
ansible-galaxy list
Remove a Role
ansible-galaxy remove <namespace>.<role_name>
Location where roles would be installed
ansible-config dump | grep ROLE # output EFAULT_PRIVATE_ROLE_VARS(default) = False DEFAULT_ROLES_PATH(default) = [u'/root/.ansible/roles', u'/usr/share/ansible/roles', u'/etc/ansible/roles'] GALAXY_ROLE_SKELETON(default) = None GALAXY_ROLE_SKELETON_IGNORE(default) = ['^.git$', '^.*/.git_keep$']
Install the role in current directory under roles
ansible-galaxy install <namespace>.<role-name> -p ./roles
Ansible Collections
- Ansible Collection is a broader packing format to bundle and distributes Ansible contents such as roles, modules, plugins, and playbooks as a single distribution unit.
Structure of Ansible Collection
A collection has a standard directory structure:
<namespace>/<collection_name>/ ├── docs/ # Documentation files ├── galaxy.yml # Metadata for the collection ├── plugins/ # Plugins (e.g., modules, filters, inventory, etc.) │ ├── modules/ │ ├── connection/ │ ├── filter/ │ ├── lookup/ ├── roles/ # Roles bundled in the collection ├── playbooks/ # Example playbooks ├── tests/ # Integration or unit tests ├── README.md # Collection overview and usage
Creating an Ansible Collection
Initialize a Collection:
ansible-galaxy collection init <namespace>.<collection_name>
List a Collection with version:
ansible-galaxy collection list <namespace>.<collection_name>
Install a Collection:
ansible-galaxy collection install <namespace>.<collection_name>
Uninstall a Collection:
ansible-galaxy collection uninstall <namespace>.<collection_name>
Ansible Template
- Ansible Template includes the use of Jinja2 templates to dynamically generate configuration files or scripts during playbook execution.
Jinja2 Template Syntax
Variable Substitution:
Hello, {{ name }}!
Filters: Modify variable output.
{{ hostname | upper }}
Default Value:
{{ value | default('default_value') }}
Replace Value:
{{ value | replace('XYZ', 'ABC') }}
Conditionals:
{% if status == 'active' %} Service is running. {% else %} Service is not running. {% endif %}
Loops:
{% for user in users %} - {{ user }} {% endfor %}
Macros: Define reusable code blocks.
{% macro greet(user) %} Hello, {{ user }}! {% endmacro %}
Creating and Using Templates
Create a Template
Templates are typically saved with a
.j2
extension. Here's an example for an Nginx configuration:# nginx.conf.j2 server { listen {{ nginx_port }}; server_name {{ server_name }}; location / { proxy_pass http://{{ backend_server }}; } }
Use the template module
Use the
template
module to render the template and deploy it to a managed node:- name: Deploy Nginx configuration template: src: templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf vars: nginx_port: 80 server_name: example.com backend_server: 127.0.0.1