LearnDevOps
DevOps05. Configuration Management·EXPERT·6 min read

15. Automation

Automation

TL;DR

  • Ansible is agentless (SSH-based, no persistent agent); this removes agent-management failure modes but depends on SSH and Python being available at runtime.
  • Idempotency (task produces same result on re-run) is the property that makes playbooks safe to re-run; built-in modules are idempotent by design; raw shell tasks are not.
  • Handlers fire only when a task reports a change, avoiding unnecessary service disruptions on every playbook run.
  • Grouped inventory (by role, environment, region) enables targeted execution; flat inventory risks accidental production targeting.
  • Roles package tasks, handlers, templates, and variables into reusable units; the alternative is copy-pasted, drift-prone duplication.

Core Concepts

Agentless architecture and SSH dependency

Ansible has no persistent local agent. It connects over SSH, executes Python modules on the target, and disconnects. This removes an entire class of failure mode:

  • No outdated agents to manage
  • No crashed agent processes to debug
  • No agent-controller sync issues
  • No “agent is down” maintenance burden

Trade-off: Ansible depends on SSH connectivity and Python being available at runtime. If SSH is down or Python isn’t installed, playbooks fail. This is a real operational constraint (network reachability, Python version compatibility) not present with agent-based tools.

Idempotency: the property of safe re-runs

Idempotency means running a playbook twice with no changes produces no changes. This is what makes “just run the playbook again” a safe response to a failed or interrupted run.

# ❌ NOT idempotent — shell task has no state awareness
- name: Create app directory
  shell: mkdir /app/data
  # Running this twice either errors (dir exists) or does nothing

# ✅ Idempotent — file module checks current state first
- name: Create app directory
  file:
    path: /app/data
    state: directory
  # Running this twice = same result (directory exists, no change reported)

Ansible’s built-in modules (file, package, service, copy, template) are idempotent by design: they check current state before acting and report “changed” only when they actually changed something. Raw shell/command tasks have no built-in state awareness.

Handlers: conditional task execution

Handlers fire only when a task reports a change. This avoids unnecessary, disruptive actions on every run:

- name: Update application config
  template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  notify: restart app service      # Triggers handler only if changed

handlers:
- name: restart app service
  service:
    name: app
    state: restarted
  # This runs only if the config task reported a change
  # Not on every playbook run, regardless of whether anything changed

Inventory structure and targeting precision

Inventory defines hosts and their groupings. Grouped inventory enables targeted execution:

[webservers]
web-01.internal
web-02.internal

[databases]
db-01.internal
db-02.internal

[staging:children]
webservers
databases

[production:children]
webservers
databases

Targeted execution becomes possible:

ansible-playbook site.yml -i inventory --limit staging
# Only runs against staging servers

Flat, ungrouped inventory loses this precision. Without groups, “run against staging web servers” requires manually listing individual hostnames every time — error-prone and a real risk of accidentally targeting production.

Roles: reusable configuration units

Roles package tasks, handlers, templates, and variables into reusable, shareable units. Instead of copy-pasting the same 15-task sequence across multiple playbooks:

# playbook.yml
- hosts: webservers
  roles:
    - role: server_hardening
      vars: { open_ports: [80, 443] }
    - role: monitoring_agent
      vars: { monitoring_enabled: true }

- hosts: databases
  roles:
    - role: server_hardening
      vars: { open_ports: [5432] }
    - role: monitoring_agent
      vars: { monitoring_enabled: true }

Both playbooks use the same roles with different variable overrides. Updates to the role apply everywhere automatically; no drift from independent copies getting out of sync.

Ansible vs Puppet vs Chef

Aspect Ansible Puppet Chef
Architecture Agentless (SSH) Agent-based (pull) Agent-based (push/pull)
Idempotency Modules built-in idempotent DSL built-in idempotent Ruby code — manual discipline
Learning curve Lower (YAML playbooks) Medium (Puppet DSL) Higher (Ruby code)
State monitoring Only on-demand (when run) Continuous (agents pull regularly) On-demand or scheduled
Scaling SSH-dependent; scales to ~1000s Agent-based; scales to many hosts Agent-based; scales to many hosts
Common use Configuration, ad-hoc tasks, provisioning Continuous configuration enforcement Complex infrastructure code

Configuration Reference

# Basic playbook structure
---
- name: Configure web servers
  hosts: webservers
  become: yes                    # Run tasks with elevated privileges

  vars:
    app_port: 8080

  roles:
    - common_packages           # Install base packages
    - firewall                  # Configure firewall

  tasks:
    - name: Create application directory
      file:
        path: /opt/myapp
        state: directory
        owner: appuser
        group: appuser
        mode: '0755'

    - name: Deploy application
      template:
        src: app.conf.j2
        dest: /opt/myapp/config.conf
      notify: restart app

  handlers:
    - name: restart app
      service:
        name: myapp
        state: restarted
# Common commands
ansible-playbook playbooks/site.yml                   # Run against all hosts
ansible-playbook playbooks/site.yml -i inventory      # Specific inventory
ansible-playbook playbooks/site.yml --limit webservers  # Limit to group
ansible-playbook playbooks/site.yml --limit host1     # Single host
ansible-playbook playbooks/site.yml --check          # Dry-run (check mode)
ansible-playbook playbooks/site.yml --tags hardening # Only tagged tasks

ansible -i inventory webservers -m setup             # Gather host facts
ansible -i inventory webservers -m ping              # Connectivity test

Common Pitfalls

  • Pitfall: Raw shell task causes non-idempotent behavior; re-run does something unexpected. Why: Shell tasks don’t check current state; no idempotency guarantee. Fix: Replace with built-in modules (file, package, service, copy, template); they’re idempotent by design.

  • Pitfall: Service restarts on every playbook run, causing unnecessary brief outages. Why: Restart was unconditional, not wired through a handler. Fix: Use notify to trigger handlers only when a change actually occurred.

  • Pitfall: Flat inventory across 200 hosts; an accidental production-targeted run happens instead of staging. Why: No group-based targeting available; manual hostname lists are error-prone. Fix: Group inventory by role, environment, region; use --limit for targeted execution.

  • Pitfall: Same 15-task sequence is duplicated across multiple playbooks; updates drift between copies. Why: Duplication instead of roles. Fix: Extract to a shared role; both playbooks include it with variable overrides.

  • Pitfall: Playbook fails due to “Python not found on target.” Why: Ansible’s SSH/Python dependency wasn’t understood. Fix: Ensure Python is installed on all targets; document SSH and Python requirements as part of system setup.

Interview Questions

  • A shell task is flagged for not being idempotent. Explain exactly why, and what replaces it. — Tests understanding of module-based state checking versus raw shell execution.

  • Design an inventory structure for 200 servers across 3 environments (dev/staging/prod) and 4 roles (web/database/cache/monitoring). — Tests whether grouped, hierarchical inventory design is understood as a real practice.

  • A config file change should trigger a service restart, but only when the config actually changed. How do you implement this correctly? — Tests whether handlers as conditional execution are the immediate answer.

  • An Ansible playbook fails with “Python not found on targets.” What does this mean operationally, and how do you prevent it? — Tests understanding of Ansible’s runtime dependencies and how to architect around them.

  • Two playbooks manage nearly-identical server configuration. How do you structure this without duplication? — Tests whether roles as reusable units are understood.

🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →
Related in 05. Configuration Management
16. Configuration Tools
EXPERT