Are you tired of managing multiple hosts manually? It’s time to learn and automate configuration management with Ansible with this Ultimate Ansible tutorial with Ansible Playbook Examples.
Ansible is the most popular and widely used automation tool to manage configuration changes across your on-prem and cloud resources.
In this article, you’re going to learn everything you should know about Ansible and will get a jump start on running your Ansible commands and Ansible Playbooks!
Let’s get started!
Table of Contents
- What is Ansible?
- Ansible Architecture Diagram and Ansible components
- Ansible Inventory
- Ansible Adhoc command
- What is Ansible playbook and Ansible playbook examples!
- Executing Ansible when conditional using Ansible playbook
- Ansible Variables and Dictionary
- Ansible Error Handling
- Ansible Handlers
- Ansible Variables
- Ansible Tags
- Ansible Debugger
- What are Ansible Roles and a Ansible roles examples?
- Conclusion
What is Ansible?
Ansible is an IT automation tool and is most widely used for deploying applications and system configurations easily on multiple servers, either hosted on a data center or on Cloud, etc.
Ansible is an agentless automation tool that manages machines over the SSH protocol by default. Once installed, Ansible does not require any database, no daemons to start or keep running.
Ansible uses inbuilt ansible ad hoc command and Ansible Playbooks to deploy the changes. Ansible Playbooks are YAML-based configuration files that you will see later in the tutorial in depth.
Ansible Architecture Diagram and Ansible components
Now that you have a basic idea about what is Ansible? Let’s further look at Ansible Architecture Diagram and Ansible components that will give you how Ansible works and the components Ansible requires.
Ansible Control Node
Ansible Control Node, also known as Ansible Controller host, is the server where Ansible is installed. This node executes all the Ansible ad hoc commands and Ansible playbooks. /commands. You can have multiple Control nodes but not windows. Must have Python Installed.
Ansible Remote Node or Ansible Managed Nodes
Ansible remote nodes or Ansible managed nodes are the servers or network devices on which you deploy applications or configurations using the Ansible ad hoc commands or playbook. These are also known as Ansible hosts.
Ansible Inventory
Ansible inventory is the file on the Ansible controller host or control node, which contains a list of all the remote hosts or managed nodes.
Ansible Core
Ansible core modules or ansible-core are the main building block and architecture for Ansible, including CLI tools such as ansible-playbook, ansible-doc, and interacting with automation.
The Ansible core modules are owned and managed by the core ansible team and will always ship with ansible itself.
Ansible Modules
Ansible modules or Ansible core modules are the code plugins or libraries plugins that can be used from the command line or a playbook task. Ansible executes each module, usually on the remote managed node, and collects return values.
Ansible Collections
Ansible Collections are a distribution format for Ansible content. Using collections, you can package and distribute playbooks, roles, modules, and plugins. A typical collection addresses a set of related use cases. You can create a collection and publish it to Ansible Galaxy or a private Automation Hub instance.
Ansible Task
Ansible Task is a unit of action executed when running the Ansible Playbook. Ansible Playbook contains one or more Ansible tasks.

Ansible Inventory
Ansible works against Ansible remote nodes or hosts to create or manage the infrastructure, but how does Ansible know those Ansible remote nodes? Yes, you guessed it right. Ansible Inventory is a file containing the list of all Ansible remote nodes or remote nodes grouped together, which Ansible uses while deploying or managing the resources.
The default location for Ansible inventory is a file called /etc/ansible/hosts. You can specify a different inventory file using the -I <path> option at the command line. Ansible Inventory is declared in two formats, i.e., INI and YAML.
Ansible installed on the control node communicates with remote nodes over SSH Protocol.
Related: working-with-ssh-connectivity
- Ansible inventory in ini format is declared as shown below.
automate2.mylabserver.com
[httpd]
automate3.mylabserver.com
automate4.mylabserver.com
[labserver]
automate[2:6].mylabserver.com
- Ansible inventory in YAML format is declared as shown below.
all:
hosts:
automate2.mylabserver.com
children:
httpd:
hosts:
automate3.mylabserver.com
automate4.mylabserver.com
labserver:
hosts:
automate[2:6].mylabserver.com
Ansible Adhoc command
If you plan to create, launch, deploy, or work with a single configuration file such as restarting an nginx service, a reboot of the machine, copying a file to a remote machine, starting or stopping any particular service, etc. on Ansible remote node then running ad hoc commands will suffice.
Ad hoc commands are a quick and efficient way to run a single command on Ansible remote nodes. Let’s quickly learn how to run Ansible Adhoc commands.
- To ping all the Ansible remote nodes using Ansible Adhoc command.
ansible all -m ping # Ping Module to Ping all the nodes

- To run echo command on all the Ansible remote nodes using Ansible Adhoc command.
ansible all -a "/bin/echo Automate" # Echo Command to Provide the output

- To check uptime of all the Ansible remote nodes using Ansible Adhoc command.
ansible all -a /usr/bin/uptime # Provides the Uptime of the Server

- To create a user on a Ansible remote node using Ansible adhoc command.
ansible all -m ansible.builtin.user -a "name=name password=password" -b

- To install Apache nginx service on all the Ansible remote nodes using Ansible Adhoc command.
# b is to become root and gain the root privileges
ansible all -m apt -a "name=apache2 state=latest" -b
- To start Apache nginx service on all the Ansible remote nodes using Ansible Adhoc command.
ansible all -m ansible.builtin.service -a "name=apache2 state=started"

- To reboot all the remote nodes that are part of america-servers group in Ansible inventory using Ansible adhoc command.
- america-servers is a group of hosts which is saved in /etc/hosts.
- To run a command use -a flag.
- “/sbin/reboot” is to command to reboot the machine
- -f is used to simultenously execute command on 100 servers.
- -u is used to run this using a different username.
- –become is used to run as a root.
ansible america-servers -a "/sbin/reboot" -f 100 -u username --become
What is Ansible playbook and Ansible playbook examples!
Ansible playbooks are used to deploy complex applications, offer reusable and simple configuration management, offer multi-machine deployments, and perform multiple tasks multiple times. Ansible playbooks are written in YAML format containing multiple tasks and executed in sequential order.
Let’s learn how to declare the Ansible playbook to install Apache on the remote node.
# Playbook apache.yml
---
- name: Installing Apache service
hosts: my_app_servers # Define all the hosts
remote_user: ubuntu # Remote_user is ubuntu
# Defining the Ansible task
tasks:
- name: Install the Latest Apache
apt:
name: httpd
state: latest
- Before you run your first Ansible playbook using ansible-playbook command make sure to verify the Playbook to catch syntax errors and other problems before you run them using below command.
ansible-playbook apache.yml --syntax-check

- To verify the playbook in detailed view run the ansible-lint command.
ansible-lint apache.yml

- To verify the Ansible playbook run the below command with –check flag.
ansible-playbook apache.yml --check

- Finally execute the Ansible playbook using the below command.
ansible-playbook apache.yml

- If you intend to install and start the Apache service using Ansible Playbook, copy/paste the below content and run the ansible-playbook command.
# Playbook of apache installation and service startup
---
- name: Install and start Apache service
hosts: my_app_servers # Define all the hosts
remote_user: ubuntu # Remote_user is ubuntu
# Defining the tasks
tasks:
- name: Install the Latest Apache
apt:
name: httpd
state: latest
- name: Ensure apache2 serice is running
service:
name: apache2
state: started
become: yes # if become is set to yes means it has activated privileges
become_user: ubuntu # Changes user to ubuntu and by default it is root
Executing Ansible when conditional using Ansible playbook
The Ansible when is a Jinja2 expression that evaluates the test or the condition for all remote nodes and wherever the test passes (returns a value of True), run the Ansible task or Ansible playbook on that host.
- In the below Ansible Playbook there are four tasks i.e
- To check the latest version
- Ensuring Apache service is running
- Creating users with_item
- Finally editing the file only when ip matches using Ansible when.
---
- name: update web servers
hosts: webserver
remote_user: ubuntu
tasks:
- name: ensure apache is at the latest version
apt:
name: apache2
state: latest
- name: Ensure apache2 serice is running
service:
name: apache2
state: started
become: yes
- name: Create users
user:
name: "{{item}}"
with_items:
-bob
-sam
-Ashok
- name: Edit the text file
lineinfile:
path: /tmp/file
state: present
line: "LogLevel debug"
when:
- ansible_hostname == "ip-10-111-4-18"
- Finally execute the Ansible playbook using the below command.
ansible-playbook apache.yml

In case you have a common parent layer then you don’t need to declare common things every time in the Ansible task as it automatically inherits directives applied at the block level.
In the below example, Ansible when, Ansible become Ansible become_user, ignore_errors are common for the entire block.
---
- name: update web servers
hosts: webserver
remote_user: ubuntu
tasks:
- name: Install, configure and start Apache
block:
- name: Install apache
apt:
name: apache2
state: present
- name: Ensure apache2 serice is running
service:
name: apache2
state: started
# Below Directives are common for entire block i.e all tasks
when: ansible_facts['distribution'] == "Debian"
become: true
become_user: root
ignore_errors: yes

Ansible Variables and Dictionary
In this section, let’s quickly look into how Ansible loops iterate over a dictionary item and convert it into list items.
- Create another playbook named abc.yaml and copying the code below.
# Playbook using iterating over a dictionary
- name: Add several users
ansible.builtin.user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'automate1', groups: 'root' }
- { name: 'automate2', groups: 'root' }
- Now execute the Ansible Playbook using the below command.
ansible-playbook abc.yaml
As you can see below, the Dictionary items have been converted into the List.

Ansible Error Handling
When Ansible receives a non-zero return code from a command or a failure from a module, by default, it stops executing on that host and continues on other hosts. Still, at times, a non-zero return code indicates success, or you want a failure on one host to stop execution on all hosts.
Ansible uses Error handling to work with the above conditions to handle these situations and help you get the behavior and output you want.
- Create below playbook named main.yml and copy/paste the below code and execute the playbook. The below task perform various tasks such as:
- One of the Ansible tasks prints a message “i execute normally”
- Fails a task
- One task will not proceed
ansible-playbook main.yml
---
- name: update web servers
hosts: localhost
remote_user: ubuntu
tasks:
- name: Ansible block to perform error handling
block:
- name: This task prints a message i execute normally
ansible.builtin.debug:
msg: 'I execute normally'
- name: This task will fail
ansible.builtin.command: /bin/false
- name: This task will not proceed due to the previous task failing
ansible.builtin.debug:
msg: 'I never execute, '
rescue:
- name: Print when errors
ansible.builtin.debug:
msg: 'I caught an error, can do stuff here to fix it'
always:
- name: Always do this
ansible.builtin.debug:
msg: 'This executes always'

Ansible Handlers
Ansible handlers are used when you need to perform any task only when notified. You add all the tasks inside the handler block in Ansible Playbook, and Ansible handlers run whenever notify calls them.
For example, whenever there is any update or change in configuration or restarting, the service is required. Ansible Handlers run when they are notified.
In the below example, if there are changes in the /tmp/file.txt, the apache service is restarted. Ansible Handler will restart the Apache service only when the lineinfile task notifies it.
---
- name: update web servers
hosts: webserver
remote_user: ubuntu
tasks:
- name: ensure apache is at the latest version
apt:
name: apache2
state: latest
- name: Ensure apache2 serice is running
service:
name: apache2
state: started
become: yes
# Edit the text file using lineinfile module
- name: Edit the text file
lineinfile:
path: /tmp/file.txt
state: present
line: "LogLevel debug"
when:
- ansible_hostname == "ip-10-111-4-18"
notify:
- Restart Apache
handlers:
- name: Restart Apache
ansible.builtin.service:
name: apache2
start: restarted

Ansible Variables
Ansible uses Ansible variables to manage multiple configurations with various attributes. With Ansible, you can execute tasks and playbooks on multiple systems with a single command with variations among different systems.
Ansible variables are declared with standard YAML syntax, including lists and dictionaries. There are lots of ways in which you can set your Ansible variables inside the ansible-play; let’s learn by:
- Defining Ansible variable normally
- Defining Ansible variables from file
- Defining Ansible variables from roles
- Defining Ansible variable at run time
---
- name: update web servers
hosts: webserver
remote_user: ubuntu
remote_install_path: /tmp/mypath # Defining Simple Variable ~1
vars: # Defining Variables from Role ~2
favcolor: blue
vars_files: # Defining Variables from file ~3
- /vars/external_vars.yml
tasks:
- name: Check Config
ansible.builtin.template:
src: my.cfg.j2
dest: '{{remote_install_path}}/my.cfg'
- Defining Ansible variable at run time with key : value format
ansible-playbook myplaybook.yml --extra-vars "version=1.23.45 other_variable=auto"
- Defining Ansible variable at run time with JSON format
ansible-playbook myplaybook.yml --extra-vars '{"version":"1.23.45","other_variable":"auto"}'
Ansible Tags
When you need to run specific tasks in the Ansible playbook, you need to consider using Ansible Tags. Ansible tags are applied on Ansible blocks level, Ansible playbook, Ansible task, or at Ansible role level.
Let’s learn how to add Ansible Tags at different levels.
- Adding Ansible Tags to individual Ansible tasks.
---
- hosts: appservers
tasks:
- name: Deploy App Binary
copy:
src: /tmp/app.war
dest: /app/
tags:
- apptag # Applying Ansible Tag to task 1
- hosts: dbserver
tasks:
- name: Deploy DB Binary
copy:
src: /tmp/db.war`
dest: /db
tags: # Applying Ansible Tag to task 2
- dbtag
- Adding Ansible Tags to Ansible block.
tasks:
- block:
tags: ntp # Applying Ansible Tags to Ansible block
- name: Install ntp
ansible.builtin.yum:
name: ntp
state: present
- name: Enable and run ntpd
ansible.builtin.service:
name: ntpd
state: started
enabled: yes
- Adding Ansible Tags to Ansible Playbook.
- hosts: all
tags: ntp # Applying Ansible Tags to Ansible Playbook
tasks:
- name: Install ntp
ansible.builtin.yum:
name: ntp
state: present
Ansible Debugger
Ansible provides a debugger to fix errors during execution instead of editing it and then running.
- Using Ansible debugger on a Ansible task.
- name: Execute a command
ansible.builtin.command: "false"
debugger: on_failed

- Using Ansible debugger on a Ansible Playbook.
- name: My play
hosts: all
debugger: on_skipped
tasks:
- name: Execute a command
ansible.builtin.command: "true"
when: False
- Using Ansible debugger on a Ansible Playbook and Ansible task.
- name: Play
hosts: all
debugger: never
tasks:
- name: Execute a command
ansible.builtin.command: "false"
debugger: on_failed
What are Ansible Roles and a Ansible roles examples
Ansible Roles is a way to structurally maintain your Ansible playbooks, i.e., it lets you load the variables from files, variables, handlers, tasks based on the structure. Similar to the Ansible modules, you can create different Ansible roles and reuse them as many times as you need.
- Let’s look at what an Ansible Role directory structure looks like.
- Tasks : This directory contains one or more files with tasks . These file can also refer to files and templates without needing to provide the exact path.
- Handlers: Add all your handlers in this directory
- Files: This directory contains all your static files and scripts that might be copied or executed to remote machine.
- Templates: This directory is reserved for templates that generate files on remote hosts.
- Vars: You define variables inside this directory and then can be referenced elsewhere in role.
- defaults: This directory lets you define default variables for included or dependent role.
- meta: This directory is used for dependency management such as dependency roles.

- Creating a Sample Ansible Playbook as shown below which you will break in different file to understand how Ansible role has file structure.
---
- hosts: all
become: true
vars:
doc_root: /var/www/example
tasks:
- name: Update apt
apt: update_cache=yes
- name: Install Apache
apt: name=apache2 state=latest
- name: Create custom document root
file: path={{ doc_root }} state=directory owner=root group=root
- name: Set up HTML file
copy: src=index.html dest={{ doc_root }}/index.html owner=root group=root mode=0644
- name: Set up Apache virtual host file
template: src=vhost.tpl dest=/etc/apache2/sites-available/000-default.conf
notify: restart apache
handlers:
- name: restart apache
service: name=apache2 state=restarted
- Let’s break the playbook that you created previously into Ansible roles by:
- Creating a directory named roles inside home directory.
- Creating a directory named apache inside roles inside home directory.
- Creating a directory named defaults, tasks, files, handlers, vars, meta, templates inside apache
- Create main.yml inside ~/roles/apache/tasks directory.
---
- name: Update apt
apt: update_cache=yes
- name: Install Apache
apt: name=apache2 state=latest
- name: Create custom document root
file: path={{ doc_root }} state=directory owner=www-data group=www-data
- name: Set up HTML file
copy: src=index.html dest={{ doc_root }}/index.html owner=www-data group=www-data mode=0644
- name: Set up Apache virtual host file
template: src=vhost.tpl dest=/etc/apache2/sites-available/000-default.conf
notify: restart apache
- Create main.yml inside ~/roles/apache/handlers directory.
---
handlers:
- name: restart apache
service: name=apache2 state=restarted
- Create index.html inside ~/roles/apache/files directory.
<html>
<head><title>Configuration Management Hands On</title></head>
<h1>This server was provisioned using Ansible</h1>
</html>
- Create vhost.tpl inside cd ~/roles/apache/templates
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot {{ doc_root }}
<Directory {{ doc_root }}>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
- Create main.yml inside cd ~/roles/apache/meta
---
dependencies:
- apt
- Create my_app.yml inside home directory
---
- hosts: all
become: true
roles:
- apache
vars:
- doc_root: /var/www/example
Conclusion
In this Ultimate Guide, you learned what is Ansible, Ansible architecture, and understood Ansible roles and how to declare Ansible Playbooks.
Now that you have gained a handful of Knowledge on Ansible, what do you plan to deploy using it?