Copyright (c) 2026 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

5.2.1. Ansible: Inventory, Playbooks, and Modules

💡 First Principle: Ansible connects to managed nodes over SSH (agentless), copies Python code to the node, executes it, captures results, and removes the code. The control node needs Ansible installed; managed nodes only need SSH access and Python. This is why Ansible is praised for low barrier to adoption — you don't need to pre-install agents on every machine.

Ansible Architecture:
Control Node (Ansible installed)
        │
        ├── Inventory file (lists managed nodes)
        │     192.168.1.10
        │     192.168.1.11
        │
        ├── Playbooks (.yml — what to do)
        │
        └── SSH connection to Managed Nodes
              (no agent required — needs Python + SSH)
Inventory:
# /etc/ansible/hosts or custom inventory file

# Simple list
192.168.1.10
192.168.1.11

# Groups
[webservers]
web01.example.com
web02.example.com

[databases]
db01.example.com ansible_user=dbadmin ansible_port=2222

[production:children]  # Group of groups
webservers
databases

[all:vars]             # Variables for all hosts
ansible_python_interpreter=/usr/bin/python3

# Dynamic inventory — executable that outputs JSON
ansible-inventory --list -i inventory.py
Ad-hoc Commands (quick one-liners):
# Test connectivity
ansible all -m ping -i inventory.ini

# Run a shell command
ansible webservers -m shell -a "df -h" -i inventory.ini

# Install a package
ansible all -m package -a "name=nginx state=present" -b -i inventory.ini
# -b = become (sudo)  -m = module  -a = arguments

# Copy a file
ansible webservers -m copy -a "src=app.conf dest=/etc/app/app.conf"
Playbooks:
# site.yml
---

- name: Configure web servers
  hosts: webservers
  become: yes                    # Equivalent to sudo

  vars:
    nginx_port: 8080
    max_connections: 1000

  tasks:
    - name: Install nginx
      package:
        name: nginx
        state: present

    - name: Deploy nginx config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart nginx       # Trigger handler if this task changes anything

    - name: Ensure nginx is running and enabled
      service:
        name: nginx
        state: started
        enabled: yes

    - name: Open firewall port
      firewalld:
        service: http
        permanent: yes
        state: enabled
        immediate: yes

  handlers:
    - name: Restart nginx
      service:
        name: nginx
        state: restarted
Key Ansible Modules:
ModulePurposeExample
packageInstall/remove packages (distro-agnostic)name: nginx state: present
apt / dnfDistro-specific package managementname: nginx state: latest
copyCopy file to remotesrc: file dest: /etc/
templateDeploy Jinja2 templatesrc: nginx.j2 dest: /etc/nginx/nginx.conf
serviceManage systemd servicesname: nginx state: started enabled: yes
userManage user accountsname: alice state: present groups: wheel
fileManage files/dirs/symlinkspath: /data state: directory mode: '0755'
command / shellRun commands (command = no shell features)cmd: /usr/bin/myapp --init
lineinfileEnsure a line exists in a filepath: /etc/hosts line: '10.0.0.5 db01'
cronManage cron entriesname: backup minute: 0 hour: 2 job: '/backup.sh'
Ansible Variables and Conditionals:
# Variable precedence (highest wins): extra vars > task vars > host vars > group vars > role defaults
vars:
  env: production

tasks:
  - name: Install dev tools
    package:
      name: git
      state: present
    when: env == "development"      # Conditional execution

  - name: Restart service on RHEL only
    service:
      name: httpd
      state: restarted
    when: ansible_os_family == "RedHat"

  # Loop
  - name: Create multiple users
    user:
      name: "{{ item }}"
      state: present
    loop:
      - alice
      - bob
      - carol
Ansible Galaxy — Roles:
ansible-galaxy install geerlingguy.nginx    # Download a role
ansible-galaxy list                          # List installed roles

Roles are reusable collections of tasks, variables, handlers, and templates — packaged for sharing.

⚠️ Exam Trap (M9): Ansible modules are idempotent — running the same playbook twice should produce the same result with no unintended changes. The command and shell modules are NOT idempotent by default (they always run their command). Use creates or removes with command/shell to make them idempotent, or better yet, use purpose-built modules (package, service, copy) whenever possible.

Reflection Question: An Ansible task uses the shell module to run mkfs.ext4 /dev/sdb1. Why is this dangerous in a playbook that might be run more than once, and what would be a safer approach?

Alvin Varughese
Written byAlvin Varughese
Founder18 professional certifications