Complete Ansible Tutorial

From beginner basics to practical playbooks, variables, templates, handlers, roles, vault, and project structure


1. What is Ansible?

Ansible is an open-source automation and configuration management tool. It helps you manage many servers from one place without manually logging into each server one by one.

Think of this problem:

Install Apache on 1 server     -> easy manually
Install Apache on 10 servers   -> boring
Install Apache on 100 servers  -> painful
Install Apache on 1000 servers -> impossible manually

Ansible solves this by allowing you to describe the desired state of your systems in YAML files called playbooks.

Ansible is agentless. You install Ansible on a control node, and it manages remote machines over SSH, PowerShell remoting, WinRM, or other supported transports. Managed Linux/Unix nodes do not need Ansible installed, though they commonly need Python for many modules. (docs.ansible.com)


2. Why do we use Ansible?

Ansible helps with:

BenefitMeaning
Save timeAutomate repeated server tasks
Save costFewer manual operations
Reduce mistakesSame steps run consistently
Manage scaleOne control node can manage many servers
Documentation as codePlaybooks show exactly what should happen
RepeatabilitySame playbook can be run again and again
IdempotencyMost modules change things only when needed

Example:

Requirement:
Deploy package X to 100 servers.

Manual method:
SSH into every server and install manually.

Ansible method:
Write one playbook and run it against all 100 servers.

Tiny bit magical, but not scary magic. More like “YAML with a wrench.”


3. What can Ansible manage?

Ansible can manage almost every common system configuration area.

3.1 Hardware-related configuration

Examples:

Disk
Mount points
Network interfaces
Storage setup
Cloud instances

3.2 Software configuration

Examples:

Files
Directories
Packages
Services
Repositories
Commands
Applications
Web servers
Databases

3.3 People and access

Examples:

Users
Groups
SSH keys
Permissions
Sudo access

3.4 Process and policy

Examples:

Security hardening
Patch management
Application deployment
Compliance checks
Service restart policies
Backup jobs
Cron jobs

4. Ansible products and ecosystem

There are a few related names students should understand.

Product / ProjectTypePurpose
ansible-coreOpen source CLI runtimeMinimal Ansible engine with built-in modules/plugins
ansible packageOpen source CLI packageLarger package including ansible-core plus community collections
AWXOpen source web UIUpstream project for Red Hat Ansible Automation Platform
Automation ControllerEnterprise web UI/APICommercial controller inside Red Hat Ansible Automation Platform
Red Hat Ansible Automation PlatformEnterprise platformFull enterprise automation platform with controller, web console, API, analytics, execution environments, RBAC, and more

The official docs describe ansible-core as the minimal package and ansible as the larger “batteries included” community package. (docs.ansible.com) AWX provides a web UI, REST API, and task engine and is an upstream project for Red Hat Ansible Automation Platform. (GitHub) Red Hat Ansible Automation Platform includes automation controller, web console, REST API, analytics, execution environments, and more. (docs.ansible.com)


5. How Ansible works: architecture

Basic architecture:

Human
  |
  v
Ansible Control Server / Control Node
  |
  | SSH for Linux/Unix
  | WinRM / PSRP / SSH for Windows
  v
Ansible Remote Servers / Managed Nodes

In your rough notes:

HUMAN -----> ACS -----> ARS
             |
             | ansible command / ansible-playbook
             |
             +---- SSH ---- Linux servers
             +---- WinRM -- Windows servers

5.1 Control node

The control node is the machine where Ansible is installed and from where commands are executed.

Examples:

Ubuntu server
RHEL server
Debian server
macOS
WSL on Windows

Windows without WSL is not natively supported as an Ansible control node, according to the official installation guide. (docs.ansible.com)

5.2 Managed nodes

The managed nodes are the servers/devices that Ansible manages.

Examples:

Web servers
Database servers
Application servers
Load balancers
Cloud VMs
Network devices
Windows servers

For Linux/Unix managed nodes, Ansible usually connects using SSH. For Windows, Ansible commonly uses WinRM or PSRP, and Windows must be configured appropriately before Ansible can manage it. (docs.ansible.com)


6. Main Ansible components

To deploy something using Ansible, you usually need:

1. Ansible installed on the control node
2. Inventory file
3. Playbook file
4. SSH key or password access to managed nodes
5. Optional ansible.cfg configuration file

Example workflow:

Step 1: Install Ansible
Step 2: Create inventory file
Step 3: Create playbook file
Step 4: Ensure SSH access to remote servers
Step 5: Optional: configure ansible.cfg
Step 6: Run ansible-playbook

Command:

ansible-playbook -i inventory web.yaml -u ubuntu --private-key node.pem

Older examples often use:

--key-file=node.pem

But the clearer modern option is:

--private-key node.pem

7. Installing Ansible

7.1 Recommended method using pipx

The official installation docs recommend pipx as a good option on systems where installing Python packages globally is restricted. (docs.ansible.com)

sudo apt update
sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible

Check installation:

ansible --version

7.2 Install using pip

python3 -m pip install --user ansible

Check:

ansible --version
ansible-community --version

The official docs show ansible --version to confirm the installed ansible-core version and ansible-community --version to check the community package version. (docs.ansible.com)

7.3 Ubuntu/Debian package manager method

For labs, this is often easiest:

sudo apt update
sudo apt install -y ansible
ansible --version

7.4 RHEL/CentOS/Fedora style

sudo dnf install -y ansible
ansible --version

8. Creating your first project directory

Create a clean lab directory:

mkdir ansible-demo
cd ansible-demo

Recommended beginner structure:

ansible-demo/
├── ansible.cfg
├── inventory
├── web.yaml
├── index.html
└── templates/
    └── index.html.j2

Later, for roles:

ansible-demo/
├── ansible.cfg
├── inventory
├── site.yml
├── group_vars/
│   └── all.yml
├── host_vars/
│   └── server1.yml
└── roles/
    └── apache/
        ├── tasks/
        │   └── main.yml
        ├── handlers/
        │   └── main.yml
        ├── templates/
        │   └── index.html.j2
        ├── files/
        │   └── index.html
        ├── vars/
        │   └── main.yml
        ├── defaults/
        │   └── main.yml
        └── meta/
            └── main.yml

The official playbook guide covers reusable files, roles, includes/imports, tags, conditionals, loops, handlers, facts, and related playbook features. (docs.ansible.com)


9. Inventory file

An inventory is the list of managed nodes that Ansible should connect to. Official Ansible docs define inventory as the list of managed nodes or hosts that Ansible deploys and configures. (docs.ansible.com)

9.1 Simple inventory

Create a file named inventory:

3.110.83.207
3.110.83.208
3.110.83.209

Test connectivity:

ansible -i inventory all -m ping -u ubuntu --private-key node.pem

9.2 Inventory with groups

[web_servers]
3.110.83.207
3.110.83.208

[db_servers]

3.110.83.209 3.110.83.210

Now you can target only web servers:

ansible -i inventory web_servers -m ping -u ubuntu --private-key node.pem

Or only database servers:

ansible -i inventory db_servers -m ping -u ubuntu --private-key node.pem

9.3 Inventory with host variables

[web_servers]
web1 ansible_host=3.110.83.207 ansible_user=ubuntu ansible_private_key_file=./node.pem
web2 ansible_host=3.110.83.208 ansible_user=ubuntu ansible_private_key_file=./node.pem

[db_servers]

db1 ansible_host=3.110.83.209 ansible_user=ubuntu ansible_private_key_file=./node.pem

Now you can run:

ansible -i inventory all -m ping

No need to pass -u ubuntu --private-key node.pem every time.

9.4 Inventory with group variables

[web_servers]
web1 ansible_host=3.110.83.207
web2 ansible_host=3.110.83.208

[db_servers]

db1 ansible_host=3.110.83.209

[all:vars]

ansible_user=ubuntu ansible_private_key_file=./node.pem

9.5 Inventory group naming tip

Your notes used:

[web-servers]

That can work, but for beginner labs I recommend:

[web_servers]

or:

[webservers]

Then keep the same name in your playbook:

hosts: web_servers

Consistency saves lives. Well, at least YAML errors.


10. Ansible configuration file: ansible.cfg

ansible.cfg controls Ansible behavior. The official docs say the stock configuration is enough for most users, but settings can be adjusted using a configuration file, environment variables, or command-line options. (docs.ansible.com)

Create ansible.cfg in your project directory:

[defaults]
inventory = ./inventory
remote_user = ubuntu
private_key_file = ./node.pem
host_key_checking = False
forks = 10
stdout_callback = yaml

[privilege_escalation]

become = True become_method = sudo become_user = root become_ask_pass = False

Now commands become shorter:

ansible all -m ping
ansible-playbook web.yaml

Important lab note:

host_key_checking = False

This is convenient for training labs, but in production, disabling host key checking is not recommended unless your security team approves it.


11. Ad-hoc Ansible commands

Ad-hoc commands are one-line Ansible commands used for quick tasks.

Syntax:

ansible <host-pattern> -i <inventory> -m <module> -a "<module arguments>"

11.1 Ping all servers

ansible -i inventory all -m ping -u ubuntu --private-key node.pem

11.2 Run uptime

ansible -i inventory all -m command -a "uptime" -u ubuntu --private-key node.pem

11.3 Check disk space

ansible -i inventory all -m shell -a "df -h" -u ubuntu --private-key node.pem

11.4 Install package

Ubuntu/Debian:

ansible -i inventory web_servers -m apt -a "name=apache2 state=present update_cache=yes" -b

RHEL/CentOS/Amazon Linux:

ansible -i inventory web_servers -m yum -a "name=httpd state=present" -b

11.5 Restart service

Ubuntu/Debian:

ansible -i inventory web_servers -m service -a "name=apache2 state=restarted" -b

RHEL/CentOS:

ansible -i inventory web_servers -m service -a "name=httpd state=restarted" -b

12. What is a playbook?

A playbook is a YAML file that describes automation steps. Official docs call playbooks “automation blueprints” in YAML format that Ansible uses to deploy and configure nodes in an inventory. (docs.ansible.com)

Hierarchy:

Playbook
  |
  +-- Play
        |
        +-- hosts
        +-- vars
        +-- tasks
        +-- handlers

12.1 Playbook

A playbook can contain one or more plays.

12.2 Play

A play maps hosts to tasks.

Example:

- name: Configure web servers
  hosts: web_servers
  become: true
  tasks:
    - name: Install Apache
      ansible.builtin.apt:
        name: apache2
        state: present

12.3 Task

A task calls one module.

Example:

- name: Copy index file
  ansible.builtin.copy:
    src: index.html
    dest: /var/www/html/index.html

12.4 Module

A module is the unit of work Ansible runs.

Examples:

ansible.builtin.copy
ansible.builtin.template
ansible.builtin.apt
ansible.builtin.yum
ansible.builtin.service
ansible.builtin.user
ansible.builtin.file
ansible.builtin.command
ansible.builtin.shell

The official module index lists modules such as setup, shell, template, uri, user, wait_for, and many more. (docs.ansible.com)


13. What is a module?

A module is a reusable script/plugin that performs a specific task.

Example:

ansible.builtin.copy:
  src: index.html
  dest: /var/www/html/index.html

Meaning:

Take index.html from the Ansible control node
Copy it to /var/www/html/index.html on the remote server

The official ansible.builtin.copy module copies files or directories to remote locations and can also set permissions, ownership, and other file metadata. (docs.ansible.com)

13.1 Common modules

ModulePurpose
ansible.builtin.pingTest Ansible connectivity
ansible.builtin.copyCopy files
ansible.builtin.templateDeploy Jinja2 templates
ansible.builtin.fileManage files, directories, permissions
ansible.builtin.aptManage Debian/Ubuntu packages
ansible.builtin.yumManage older RHEL-style packages
ansible.builtin.dnfManage newer RHEL/Fedora packages
ansible.builtin.serviceManage services
ansible.builtin.systemd_serviceManage systemd units
ansible.builtin.userManage users
ansible.builtin.groupManage groups
ansible.builtin.commandRun commands without shell features
ansible.builtin.shellRun shell commands
ansible.builtin.debugPrint messages and variable values
ansible.builtin.setupGather facts

14. First complete playbook: install Apache

Create web.yaml:

---
- name: Install and start Apache on web servers
  hosts: web_servers
  become: true

  tasks:
    - name: Update apt cache and install Apache2
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true

    - name: Ensure Apache service is started and enabled
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true

Run:

ansible-playbook -i inventory web.yaml -u ubuntu --private-key node.pem

Or, if ansible.cfg is configured:

ansible-playbook web.yaml

Check in browser:

http://<server-public-ip>

15. Corrected handler example from your notes

Your notes had notify: Restart Apache, but the handlers: section was missing. Here is the complete version.

---
- name: Install Apache using handlers
  hosts: web_servers
  become: true

  tasks:
    - name: Install Apache2
      ansible.builtin.apt:
        name: apache2
        state: latest
        update_cache: true
      notify:
        - Restart Apache

    - name: Ensure Apache service is enabled and running
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true

  handlers:
    - name: Restart Apache
      ansible.builtin.service:
        name: apache2
        state: restarted

What is a handler?

A handler is a special task that runs only when notified by another task.

Example:

If template changed -> restart Apache
If template did not change -> do not restart Apache

This prevents unnecessary service restarts.


16. Copy files using copy

Create index.html:

<h1>Welcome to DevOpsSchool Ansible Demo</h1>
<p>This page was copied using Ansible.</p>

Playbook:

---
- name: Copy website file
  hosts: web_servers
  become: true

  tasks:
    - name: Install Apache2
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true

    - name: Copy index.html to Apache document root
      ansible.builtin.copy:
        src: index.html
        dest: /var/www/html/index.html
        owner: root
        group: root
        mode: '0644'

    - name: Start Apache
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true

Run:

ansible-playbook web.yaml

17. Variables in Ansible

Variables help avoid hardcoding values.

Example:

vars:
  package_name: apache2
  service_name: apache2

Ansible variables can be defined in playbooks, inventory, reusable files, roles, command-line arguments, or created during playbook execution using registered task results. (docs.ansible.com)

17.1 Variable example

---
- name: Install Apache using variables
  hosts: web_servers
  become: true

  vars:
    package_name: apache2
    service_name: apache2
    app_name: "DevOpsSchool Demo App"

  tasks:
    - name: Show app name
      ansible.builtin.debug:
        msg: "Installing {{ package_name }} for {{ app_name }}"

    - name: Install package
      ansible.builtin.apt:
        name: "{{ package_name }}"
        state: present
        update_cache: true

    - name: Start service
      ansible.builtin.service:
        name: "{{ service_name }}"
        state: started
        enabled: true

17.2 Variable from command line

ansible-playbook web.yaml -e "package_name=apache2 service_name=apache2"

17.3 Variables in inventory

[web_servers]
web1 ansible_host=3.110.83.207 app_name="Web App 1"
web2 ansible_host=3.110.83.208 app_name="Web App 2"

Playbook:

- name: Print app name
  ansible.builtin.debug:
    msg: "This host runs {{ app_name }}"

17.4 Variables in group_vars

Create:

group_vars/
└── web_servers.yml

Content:

package_name: apache2
service_name: apache2
document_root: /var/www/html

Use in playbook:

- name: Install package
  ansible.builtin.apt:
    name: "{{ package_name }}"
    state: present

18. Facts in Ansible

Facts are automatically discovered information about remote systems.

Examples:

Operating system
IP address
Hostname
Memory
CPU
Disk
Architecture
Distribution
Kernel

Playbook:

---
- name: Display facts
  hosts: all

  tasks:
    - name: Show OS distribution
      ansible.builtin.debug:
        msg: "This server is running {{ ansible_distribution }} {{ ansible_distribution_version }}"

    - name: Show hostname
      ansible.builtin.debug:
        msg: "Hostname is {{ ansible_hostname }}"

Run:

ansible-playbook facts.yaml

Ad-hoc fact gathering:

ansible all -m setup

19. Conditionals: when

Conditionals let tasks run only when a condition is true.

Example:

---
- name: Install web server based on OS
  hosts: web_servers
  become: true

  tasks:
    - name: Install Apache on Ubuntu/Debian
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      when: ansible_os_family == "Debian"

    - name: Install Apache on RHEL/CentOS/Amazon Linux
      ansible.builtin.dnf:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"

19.1 Conditional service names

---
- name: Start correct Apache service
  hosts: web_servers
  become: true

  tasks:
    - name: Start apache2 on Debian family
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true
      when: ansible_os_family == "Debian"

    - name: Start httpd on RedHat family
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: true
      when: ansible_os_family == "RedHat"

20. Loops and iterations

Loops help repeat one task multiple times.

20.1 Install multiple packages

---
- name: Install multiple packages
  hosts: web_servers
  become: true

  tasks:
    - name: Install packages
      ansible.builtin.apt:
        name: "{{ item }}"
        state: present
        update_cache: true
      loop:
        - apache2
        - git
        - curl
        - vim

20.2 Create multiple users

---
- name: Create multiple users
  hosts: all
  become: true

  tasks:
    - name: Create users
      ansible.builtin.user:
        name: "{{ item }}"
        state: present
      loop:
        - raj
        - amit
        - priya

20.3 Loop with dictionaries

---
- name: Create users with groups
  hosts: all
  become: true

  tasks:
    - name: Ensure groups exist
      ansible.builtin.group:
        name: "{{ item.group }}"
        state: present
      loop:
        - { name: "raj", group: "devops" }
        - { name: "amit", group: "qa" }

    - name: Create users
      ansible.builtin.user:
        name: "{{ item.name }}"
        group: "{{ item.group }}"
        state: present
      loop:
        - { name: "raj", group: "devops" }
        - { name: "amit", group: "qa" }

21. Templates using Jinja2

Use copy when the file is static.

Use template when the file contains variables.

The official template module processes templates using the Jinja2 templating language. (docs.ansible.com)

21.1 Create template file

Create:

templates/index.html.j2

Content:

<html>
  <head>
    <title>{{ app_name }}</title>
  </head>
  <body>
    <h1>Welcome to {{ app_name }}</h1>
    <p>Deployed by Ansible.</p>
    <p>Hostname: {{ ansible_hostname }}</p>
    <p>Operating System: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
  </body>
</html>

21.2 Playbook using template

---
- name: Apache setup with template
  hosts: web_servers
  become: true

  vars:
    app_name: "DevOpsSchool Demo App"

  tasks:
    - name: Install Apache2
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true

    - name: Deploy index.html from Jinja2 template
      ansible.builtin.template:
        src: templates/index.html.j2
        dest: /var/www/html/index.html
        owner: root
        group: root
        mode: '0644'
      notify:
        - Restart Apache

    - name: Ensure Apache is running
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true

  handlers:
    - name: Restart Apache
      ansible.builtin.service:
        name: apache2
        state: restarted

22. Register task output

Sometimes you want to store command output and use it later.

---
- name: Register command output
  hosts: all

  tasks:
    - name: Run uptime command
      ansible.builtin.command: uptime
      register: uptime_output

    - name: Print uptime
      ansible.builtin.debug:
        var: uptime_output.stdout

23. Error handling

23.1 Ignore errors

- name: Try to stop non-existing service
  ansible.builtin.service:
    name: fake_service
    state: stopped
  ignore_errors: true

Use carefully. Ignoring errors blindly is how infrastructure goblins are born.

23.2 Mark task as failed manually

- name: Check if Apache is reachable
  ansible.builtin.uri:
    url: "http://localhost"
    status_code: 200
  register: result
  failed_when: result.status != 200

23.3 Mark task as changed manually

- name: Run custom command
  ansible.builtin.command: /usr/local/bin/custom-check
  register: result
  changed_when: "'changed' in result.stdout"

24. Include and import

For large automation, do not keep hundreds of tasks in one file.

Bad:

site.yaml with 500 tasks

Better:

site.yaml
web.yaml
app.yaml
db.yaml

24.1 Import another playbook

site.yml:

---
- import_playbook: web.yml
- import_playbook: app.yml
- import_playbook: db.yml

Run:

ansible-playbook site.yml

24.2 Include tasks

Directory:

tasks/
├── install.yml
├── configure.yml
└── service.yml

Playbook:

---
- name: Apache setup using included tasks
  hosts: web_servers
  become: true

  tasks:
    - name: Include install tasks
      ansible.builtin.include_tasks: tasks/install.yml

    - name: Include configure tasks
      ansible.builtin.include_tasks: tasks/configure.yml

    - name: Include service tasks
      ansible.builtin.include_tasks: tasks/service.yml

25. Tags

Tags let you run only selected parts of a playbook.

---
- name: Apache with tags
  hosts: web_servers
  become: true

  tasks:
    - name: Install Apache
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      tags:
        - install

    - name: Copy website
      ansible.builtin.template:
        src: templates/index.html.j2
        dest: /var/www/html/index.html
      tags:
        - deploy

    - name: Restart Apache
      ansible.builtin.service:
        name: apache2
        state: restarted
      tags:
        - restart

Run only install tasks:

ansible-playbook web.yaml --tags install

Skip restart:

ansible-playbook web.yaml --skip-tags restart

The official playbook guide includes tags as a way to run selected parts of a playbook. (docs.ansible.com)


26. Check mode and diff mode

26.1 Check mode

Check mode shows what would change, without applying changes.

ansible-playbook web.yaml --check

26.2 Diff mode

Diff mode shows file differences.

ansible-playbook web.yaml --diff

26.3 Use both

ansible-playbook web.yaml --check --diff

The official playbook execution guide includes check mode and diff mode for validating tasks. (docs.ansible.com)


27. Privilege escalation: become

Many tasks need root privilege.

Example:

become: true

Full play:

---
- name: Install package with sudo
  hosts: web_servers
  become: true

  tasks:
    - name: Install Apache
      ansible.builtin.apt:
        name: apache2
        state: present

Command with sudo password prompt:

ansible-playbook web.yaml --ask-become-pass

Short form:

ansible-playbook web.yaml -K

28. Ansible Vault

Ansible Vault encrypts sensitive data such as passwords, tokens, and secret variables. The official docs describe Vault as a way to encrypt and manage sensitive data such as passwords. (docs.ansible.com)

28.1 Create encrypted file

ansible-vault create secrets.yml

Example content:

db_password: "SuperSecretPassword"
api_token: "abcdef123456"

28.2 Edit encrypted file

ansible-vault edit secrets.yml

28.3 View encrypted file

ansible-vault view secrets.yml

28.4 Use vault file in playbook

---
- name: Use vault variables
  hosts: all
  vars_files:
    - secrets.yml

  tasks:
    - name: Print secret variable safely
      ansible.builtin.debug:
        msg: "Password is loaded but not printed here"

Run:

ansible-playbook secure.yaml --ask-vault-pass

28.5 Encrypt existing file

ansible-vault encrypt secrets.yml

28.6 Decrypt file

ansible-vault decrypt secrets.yml

29. Roles

A role is a reusable Ansible structure. Roles organize tasks, handlers, variables, templates, and files into standard folders.

Instead of this:

one huge playbook with 300 lines

Use this:

roles/apache/
roles/mysql/
roles/app/
roles/common/

29.1 Create role manually

mkdir -p roles/apache/{tasks,handlers,templates,files,vars,defaults,meta}
touch roles/apache/tasks/main.yml
touch roles/apache/handlers/main.yml

29.2 Role structure

roles/apache/
├── tasks/
│   └── main.yml
├── handlers/
│   └── main.yml
├── templates/
│   └── index.html.j2
├── files/
│   └── index.html
├── vars/
│   └── main.yml
├── defaults/
│   └── main.yml
└── meta/
    └── main.yml

29.3 Role task file

roles/apache/tasks/main.yml:

---
- name: Install Apache2
  ansible.builtin.apt:
    name: "{{ apache_package }}"
    state: present
    update_cache: true

- name: Deploy index.html
  ansible.builtin.template:
    src: index.html.j2
    dest: /var/www/html/index.html
    owner: root
    group: root
    mode: '0644'
  notify:
    - Restart Apache

- name: Ensure Apache is running
  ansible.builtin.service:
    name: "{{ apache_service }}"
    state: started
    enabled: true

29.4 Role handler file

roles/apache/handlers/main.yml:

---
- name: Restart Apache
  ansible.builtin.service:
    name: "{{ apache_service }}"
    state: restarted

29.5 Role defaults

roles/apache/defaults/main.yml:

---
apache_package: apache2
apache_service: apache2
app_name: "DevOpsSchool Apache App"

29.6 Role template

roles/apache/templates/index.html.j2:

<h1>{{ app_name }}</h1>
<p>Managed by Ansible role.</p>
<p>Host: {{ ansible_hostname }}</p>

29.7 Use role in playbook

site.yml:

---
- name: Configure web servers using role
  hosts: web_servers
  become: true

  roles:
    - apache

Run:

ansible-playbook site.yml

30. Collections

Collections are a distribution format for Ansible content. They can include playbooks, roles, modules, and plugins. (docs.ansible.com)

30.1 Install a collection

ansible-galaxy collection install community.general

30.2 List installed collections

ansible-galaxy collection list

30.3 Use fully qualified collection names

Recommended style:

ansible.builtin.copy:

Instead of only:

copy:

The official module docs often recommend using fully qualified collection names, such as ansible.builtin.apt, to avoid conflicts and make documentation linking easier. (docs.ansible.com)


31. Complete beginner lab: Apache deployment

31.1 Final directory

ansible-demo/
├── ansible.cfg
├── inventory
├── site.yml
└── roles/
    └── apache/
        ├── tasks/
        │   └── main.yml
        ├── handlers/
        │   └── main.yml
        ├── defaults/
        │   └── main.yml
        └── templates/
            └── index.html.j2

31.2 inventory

[web_servers]
web1 ansible_host=3.110.83.207
web2 ansible_host=3.110.83.208

[db_servers]

db1 ansible_host=3.110.83.209

[all:vars]

ansible_user=ubuntu ansible_private_key_file=./node.pem

31.3 ansible.cfg

[defaults]
inventory = ./inventory
remote_user = ubuntu
private_key_file = ./node.pem
host_key_checking = False
stdout_callback = yaml

[privilege_escalation]

become = True become_method = sudo become_user = root become_ask_pass = False

31.4 site.yml

---
- name: Configure Apache web servers
  hosts: web_servers
  become: true

  roles:
    - apache

31.5 roles/apache/defaults/main.yml

---
apache_package: apache2
apache_service: apache2
app_name: "DevOpsSchool Ansible Demo"
document_root: /var/www/html

31.6 roles/apache/tasks/main.yml

---
- name: Install Apache package
  ansible.builtin.apt:
    name: "{{ apache_package }}"
    state: present
    update_cache: true

- name: Deploy home page
  ansible.builtin.template:
    src: index.html.j2
    dest: "{{ document_root }}/index.html"
    owner: root
    group: root
    mode: '0644'
  notify:
    - Restart Apache

- name: Ensure Apache service is running and enabled
  ansible.builtin.service:
    name: "{{ apache_service }}"
    state: started
    enabled: true

31.7 roles/apache/handlers/main.yml

---
- name: Restart Apache
  ansible.builtin.service:
    name: "{{ apache_service }}"
    state: restarted

31.8 roles/apache/templates/index.html.j2

<!DOCTYPE html>
<html>
<head>
  <title>{{ app_name }}</title>
</head>
<body>
  <h1>{{ app_name }}</h1>
  <p>This server is managed by Ansible.</p>
  <p>Hostname: {{ ansible_hostname }}</p>
  <p>OS: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
</body>
</html>

31.9 Run the lab

Check syntax:

ansible-playbook site.yml --syntax-check

Dry run:

ansible-playbook site.yml --check --diff

Apply changes:

ansible-playbook site.yml

Run only on one host:

ansible-playbook site.yml --limit web1

Run with verbose output:

ansible-playbook site.yml -v

More verbose:

ansible-playbook site.yml -vvv

32. Troubleshooting common Ansible errors

32.1 SSH permission denied

Error:

Permission denied (publickey)

Fix:

chmod 400 node.pem
ssh -i node.pem ubuntu@3.110.83.207

Then test:

ansible -i inventory all -m ping

32.2 Wrong remote user

Try different users:

-u ubuntu
-u ec2-user
-u admin
-u centos

AWS examples:

Ubuntu AMI     -> ubuntu
Amazon Linux   -> ec2-user
CentOS         -> centos
RHEL           -> ec2-user or cloud-user
Debian         -> admin or debian

32.3 Sudo password required

Use:

ansible-playbook site.yml -K

Or configure passwordless sudo for lab users.

32.4 Python missing on managed node

Error may look like:

/usr/bin/python: not found

Fix for Ubuntu/Debian:

sudo apt update
sudo apt install -y python3

You can set interpreter:

[all:vars]
ansible_python_interpreter=/usr/bin/python3

32.5 Inventory group mismatch

Inventory:

[web_servers]
3.110.83.207

Playbook must use:

hosts: web_servers

Not:

hosts: webservers

Tiny typo. Big headache. Classic YAML gremlin.


33. Best practices

Use these habits from the beginning:

1. Use Git for Ansible code.
2. Use meaningful task names.
3. Use FQCN module names, such as ansible.builtin.copy.
4. Do not hardcode secrets in playbooks.
5. Use Ansible Vault for passwords and tokens.
6. Use roles for reusable automation.
7. Use variables instead of repeating values.
8. Test with --syntax-check.
9. Preview with --check --diff.
10. Use handlers for service restarts.
11. Avoid shell/command when a proper module exists.
12. Keep inventory, variables, playbooks, and roles organized.
13. Use group_vars and host_vars.
14. Keep production and staging inventories separate.
15. Keep host_key_checking enabled in production unless you have a valid reason.

34. Mini cheat sheet

Inventory test

ansible-inventory -i inventory --list

Ping all nodes

ansible all -m ping

Run command

ansible all -m command -a "uptime"

Run playbook

ansible-playbook site.yml

Syntax check

ansible-playbook site.yml --syntax-check

Dry run

ansible-playbook site.yml --check

Show diff

ansible-playbook site.yml --diff

Limit to one host

ansible-playbook site.yml --limit web1

Run by tag

ansible-playbook site.yml --tags install

Skip tag

ansible-playbook site.yml --skip-tags restart

Pass extra variable

ansible-playbook site.yml -e "app_name=DemoApp"

Ask sudo password

ansible-playbook site.yml -K

Ask vault password

ansible-playbook site.yml --ask-vault-pass

Reference URLs

URLs from your rough notes

https://www.devopsschool.com/blog/ansible-installation-and-configuration-guide/

https://docs.ansible.com/projects/ansible-core/devel/collections/ansible/builtin/copy_module.html#ansible-collections-ansible-builtin-copy-module

https://docs.ansible.com/projects/ansible/latest/collections/index_module.html

https://docs.ansible.com/projects/ansible/latest/collections/all_plugins.html

Ansible Tutorials: Ansible Variables in Playbook
How to set condition module executations in Ansible Playbook with Example?
https://www.devopsschool.com/blog/how-to-loop-and-iteration-executions-in-ansible-playbook-with-example/
Ansible Tutorials: Calling one Play & Tasks from another play in Playbook
Ansible Playbook Example
Ansible Template and Handlers explained with Example

Additional official reference URLs added

https://docs.ansible.com/projects/ansible/latest/index.html

https://docs.ansible.com/projects/ansible/latest/installation_guide/index.html

https://docs.ansible.com/projects/ansible/latest/installation_guide/intro_installation.html

https://docs.ansible.com/projects/ansible/latest/installation_guide/installation_distros.html

https://docs.ansible.com/projects/ansible/latest/installation_guide/intro_configuration.html

https://docs.ansible.com/projects/ansible/latest/inventory_guide/index.html

https://docs.ansible.com/projects/ansible/latest/playbook_guide/index.html

https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_variables.html

https://docs.ansible.com/projects/ansible/latest/vault_guide/index.html

https://docs.ansible.com/projects/ansible/latest/module_plugin_guide/index.html

https://docs.ansible.com/projects/ansible/latest/collections_guide/index.html

https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/index.html

https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/copy_module.html

https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/template_module.html

https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/apt_module.html

https://docs.ansible.com/projects/ansible/latest/os_guide/intro_windows.html

https://docs.ansible.com/projects/ansible/latest/os_guide/windows_winrm.html

https://docs.ansible.com/collections.html

https://github.com/ansible/awx

https://docs.ansible.com/projects/ansible/latest/reference_appendices/tower.html

https://www.redhat.com/en/technologies/management/ansible/automation-controller

https://www.redhat.com/en/technologies/management/ansible/compare-awx-vs-ansible-automation-platform