What is Ansible #

In today’s world, where IT infrastructure evolves at breakneck speed, managing servers manually is no longer a realistic option. When an organization runs dozens, hundreds, or even thousands of servers, manual maintenance takes forever, is prone to human error, and is extremely difficult to audit. Ansible was built to solve exactly these challenges. By providing an automation system that is simple yet highly reliable, Ansible lets you manage the entire lifecycle of IT infrastructure consistently through code (Infrastructure as Code).

Background and Evolution of IT Automation #

To understand why Ansible became so popular, we need to look back at how infrastructure automation evolved over time. In the early days of system administration, engineers relied on manual shell scripts (such as Bash or PowerShell) to automate administrative tasks. This approach had major limitations: shell scripts are imperative, hard to share across teams, and have no built-in mechanism for handling system failures at scale.

Then came the first generation of configuration management tools like Chef (2009) and Puppet (2005). Although these tools introduced declarative concepts and desired-state management, they carried a heavy burden of complexity. Both required installing agent daemons on every target server, managing SSL certificates for secure communication between agent and master, and understanding a fairly complex programming language (like Ruby).

In 2012, Michael DeHaan created Ansible with a completely different philosophy. He wanted to build an automation tool that could be used right away with no elaborate setup, wouldn’t burden target servers with extra background processes, and would be easy for anyone to read. This radical approach sparked mass adoption across the IT industry, and eventually Red Hat acquired Ansible in 2015, making it the enterprise automation standard in the Linux and cloud ecosystem.


The Agentless Architecture in Depth #

The architectural advantage that most sets Ansible apart from its predecessors is its agentless nature. Let’s look at how this system works without needing any extra agent on the target side.

With traditional agent-based tools, configuration management runs on a Pull model. An agent installed on the target server periodically contacts the master server, asks for the latest configuration, then applies it locally.

Ansible, in contrast, uses a centralized Push model. When you run an automation command from your control machine (Control Node), Ansible automatically performs the following operational steps behind the scenes:

flowchart TD
    Start["1. You run the automation command on the Control Node"] --> Compile["2. Ansible compiles the tasks into modular Python code"]
    Compile --> Connect["3. Ansible opens an SSH connection (or WinRM for Windows) to the Target"]
    Connect --> Copy["4. Ansible copies that Python code to a temporary directory on the target (/tmp)"]
    Copy --> Execute["5. Ansible executes the Python code on the target using the local interpreter"]
    Execute --> Capture["6. The code sends status output back to the Control Node in JSON format"]
    Capture --> Cleanup["7. Ansible removes the temporary code from the target directory and closes SSH"]
    Cleanup --> End["Done"]

Why Is the Agentless Approach So Beneficial? #

  1. Better Security: Every agent running on a target server is a potential source of security vulnerabilities. Agents also require additional communication ports to be opened on the firewall. With Ansible, you only use the SSH protocol (or WinRM for Windows) that is already installed and secured on every industry-standard server.
  2. No Target Resource Consumption: Traditional agent servers can burn RAM and CPU on target servers around the clock just to monitor system state. Ansible only consumes target server resources while a task is actually executing. Once the task finishes, no background process (daemon) is left behind.
  3. Zero-Bootstrapping: You can manage a server that was just provisioned (bare-metal or a fresh VM) instantly. There’s no need to install extra repositories, configure communication certificates, or keep agent versions updated. As long as you have SSH access and Python installed on the target, the server is immediately ready to be managed.

Here’s a visual illustration of Ansible’s communication architecture:

flowchart TD
    subgraph Control Node
        CN["Your Control Machine (Laptop/Bastion Server)"]
        IN["Inventory File (Host & Group Variables)"]
        PB["Playbook (YAML Desired State)"]
        CN --- IN
        CN --- PB
    end

    subgraph Managed Nodes
        MN1["Managed Node 1 (Ubuntu Server - Web)"]
        MN2["Managed Node 2 (CentOS Server - Database)"]
        MN3["Managed Node 3 (Windows Server - Active Directory)"]
    end

    CN -- "SSH (Port 22) + Python" --> MN1
    CN -- "SSH (Port 22) + Python" --> MN2
    CN -- "WinRM (Port 5986) + PowerShell" --> MN3

Ansible’s Core Components #

Ansible automation is built on several integrated component blocks that complement each other. Understanding the role of each component is crucial before you write your first automation code.

1. Control Node #

The Control Node is the machine where you install Ansible. You run all execution commands (like ansible or ansible-playbook) from this machine. The system requirements for a Control Node are very light:

  • A Unix-like operating system (Linux, macOS, BSD). Windows is not supported as a Control Node directly, but you can use it through WSL (Windows Subsystem for Linux).
  • A modern version of Python (3.9 or later).

2. Managed Nodes (Hosts) #

Managed Nodes are the target servers whose configuration you want to manage. Managed nodes don’t need Ansible installed. You only need:

  • Network access that allows connections from the Control Node.
  • An SSH user account with sufficient privileges (usually with sudo access).
  • Python installed on the system (for Linux/Unix targets) or PowerShell (for Windows targets).

3. Inventory #

Inventory is the registry containing the list of all managed node servers you administer. Inside the inventory, you can group servers by location (for example, dev, staging, and prod servers) or by function (for example, webservers, dbservers). The inventory can be written in the simple INI format or the more structured YAML format.

Let’s compare inventory file structures using the INI and YAML formats:

# Example Inventory File (INI Format)
[webservers]
web-prod-01.example.com ansible_host=192.168.10.11
web-prod-02.example.com ansible_host=192.168.10.12

[dbservers]
db-prod-01.example.com ansible_host=192.168.10.20

[production:children]
webservers
dbservers
# Example Inventory File (YAML Format)
all:
  children:
    webservers:
      hosts:
        web-prod-01.example.com:
          ansible_host: 192.168.10.11
        web-prod-02.example.com:
          ansible_host: 192.168.10.12
    dbservers:
      hosts:
        db-prod-01.example.com:
          ansible_host: 192.168.10.20
    production:
      children:
        webservers: {}
        dbservers: {}

You can also use Dynamic Inventory. This feature is especially useful in dynamic cloud environments (such as AWS, GCP, or OpenStack) where servers come and go automatically. A dynamic inventory contacts the cloud provider’s API periodically to update the list of target servers in real time.

4. Modules #

Modules are the functional units of work that perform real tasks on target servers. When you write a task in Ansible, you’re actually calling a specific module with parameters you specify. Ansible ships with thousands of ready-to-use built-in modules. Some of the most commonly used basic modules include:

  • apt / yum / dnf: Used to manage software package installation on Linux operating systems.
  • copy / template: Used to copy files from the Control Node to Managed Nodes (with variable rendering support for template).
  • service / systemd: Used to manage OS service states (such as starting, stopping, or enabling at boot).
  • file: Used to create directories, set permissions, or create symbolic links.
  • user: Used to create, modify, or delete users on target servers.

5. Playbook #

A Playbook is a YAML-format configuration file where you lay out your automation scenarios. Inside a playbook, you specify which group of target servers to manage (hosts), which user runs the tasks (become), and the list of tasks to run in sequence (tasks).

Here’s a simple playbook example for installing and configuring Nginx on your production servers:

---
- name: Production web server configuration scenario
  hosts: webservers
  become: true
  vars:
    http_port: 80
    max_clients: 512

  tasks:
    - name: Ensure the latest Nginx package is installed
      apt:
        name: nginx
        state: latest
        update_cache: true

    - name: Apply custom Nginx configuration file from Jinja2 template
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx service

  handlers:
    - name: Restart Nginx service
      service:
        name: nginx
        state: restarted

The Core Principle of Idempotency #

Understanding idempotency is the single most crucial key to becoming an Ansible expert. Simply put, an idempotent task is one that, when run once, brings the system to the desired state — but when run repeatedly on a system that already matches, makes no changes and produces no side effects.

Let’s look at the fundamental difference between a traditional imperative script (Bash) and a declarative Ansible task:

Case: Adding a New User and an Application Directory #

If you use a traditional Bash script, it will fail on the second run unless you write many extra lines of conditional code:

# Traditional Bash script (Imperative & Not Idempotent by Default)
# The first attempt runs smoothly.
# The second attempt errors out because the user and directory already exist!
useradd deployer
mkdir /var/www/myapp
chown deployer:deployer /var/www/myapp

To fix it in Bash, you’d have to modify it like this:

# Bash script manually modified to be idempotent
if ! id -u deployer >/dev/null 2>&1; then
    useradd deployer
fi

if [ ! -d "/var/www/myapp" ]; then
    mkdir -p /var/www/myapp
    chown deployer:deployer /var/www/myapp
fi

Now compare that with how you’d express the same thing declaratively in an Ansible Playbook:

# Ansible Playbook (Declarative & Idempotent by Default)
# You can run this playbook 100 times.
# Ansible checks the system state first.
# If the 'deployer' user already exists with the same parameters, Ansible skips it (SUCCESS / OK).
- name: Ensure the deployer user is configured
  user:
    name: deployer
    state: present
    shell: /bin/bash

- name: Ensure the application directory is ready
  file:
    path: /var/www/myapp
    state: directory
    owner: deployer
    group: deployer
    mode: '0755'

Why Is Idempotency So Important in Production Environments? #

  • Operational Safety: You can re-run the same playbook at any time to make sure no configuration has drifted, without fear of breaking or disrupting services currently running on the target servers.
  • Time and Network Efficiency: Because Ansible only makes changes when there’s a difference, you save significant task execution time and network bandwidth on large-scale infrastructure.
  • Clear Reporting: Ansible execution results provide very clear metrics. You can immediately see how many tasks were skipped (ok), how many modified the system (changed), and which tasks failed (failed).

Task Execution Lifecycle #

When you run a playbook execution command (for example: ansible-playbook site.yml), Ansible doesn’t just execute tasks randomly. There’s a structured lifecycle in place to ensure the safety and consistency of the target server state.

Here are the execution stages in detail:

Stage 1: Reading Configuration and Parsing Inventory #

Ansible reads the default configuration file (ansible.cfg), loads the inventory file to map which servers are execution targets, and reads all related variables from the group_vars and host_vars directories.

Stage 2: Opening SSH Connections #

Ansible opens encrypted network connections (SSH or WinRM) to all target servers in parallel. The number of servers connected simultaneously is controlled by the forks parameter in the Ansible configuration (5 by default).

Stage 3: Gathering Facts #

Before running your first task, Ansible executes an internal module called setup by default. This module collects thousands of data points specific to the target server’s current state, such as:

  • Operating system version and kernel distribution.
  • Free RAM capacity and total disk storage capacity.
  • Internal/external IP addresses along with network interface configuration.
  • CPU architecture (x86_64 or ARM).

These facts are stored temporarily in memory as variables (for example ansible_os_family or ansible_memtotal_mb) that you can use to dynamically control your automation.

Stage 4: Executing Tasks Sequentially #

Ansible runs tasks one by one from top to bottom. Each task is executed simultaneously on all eligible target servers before moving on to the next task. For every task:

  1. Ansible creates a modular copy of the Python file for the module being called.
  2. Sends it to the target server via SFTP/SCP.
  3. Runs it locally on the target side.
  4. Captures the JSON response generated.
  5. Removes the temporary Python file from the target server.

Stage 5: Triggering Handlers (if any) #

If a task successfully modified the system (status changed) and that task has a notify declaration, Ansible records the trigger. After all tasks in a play have finished executing, Ansible then runs the special task list (handlers) to avoid duplicate executions (for example, restarting a service multiple times).


Ansible’s Strengths and Limitations #

As IT professionals, we need to evaluate tools objectively. No single tool in the world is perfect for everything. Let’s look at Ansible’s pros and cons fairly.

Ansible’s Main Strengths #

  • Very Gentle Learning Curve: Because it uses YAML format and declarative logic, anyone — even a beginner or a developer who rarely touches server operations — can understand what an Ansible playbook is trying to do within minutes.
  • Remarkable Flexibility: Ansible isn’t just for managing OS configuration. It can also automate network switch configuration (Cisco, Juniper), interact with cloud APIs, work with Kubernetes clusters, and even trigger CI/CD pipeline flows.
  • Efficient Security Management: With no agents whose certificates need managing, you minimize security risks as well as the administrative burden of rolling out agent daemon version updates across your entire infrastructure.

Ansible’s Limitations #

  • Sequential Push Model Scalability: Because it uses a centralized push model over SSH, when you manage thousands of servers at once from a single Control Node, the Control Node’s performance can degrade due to the heavy load of handling so many parallel SSH connections.
  • Not a Real-Time Monitoring System: Ansible isn’t designed to detect configuration changes in real time and restore them within seconds. If you need a strict compliance watchdog that runs on its own around the clock without manual triggering, an agent-based tool like Puppet may be superior.
  • Dependency on Python: If a target server has problems with its Python interpreter (for example, due to a broken library installation or an outdated Python version), Ansible won’t be able to run Python-based modules on that target.

Summary #

  • Ansible is a declarative IT automation tool designed with a philosophy of simplicity, security, and efficiency through its agentless principle.
  • Centralized Push Model — Automation runs from the Control Node to Managed Nodes using the industry-standard SSH protocol without leaving any background processes on the targets.
  • Three Core Components — Ansible’s flexibility rests on the collaboration between Inventory (the server list), Playbook (the YAML instruction script), and Modules (the operational work tools).
  • The Idempotency Principle — Guarantees that running a playbook repeatedly on your infrastructure always produces the same end state without causing system damage or side effects.
  • Structured Lifecycle — Task execution follows an orderly flow, from reading the inventory and gathering server-specific data (gathering facts) to cleaning up temporary files after execution finishes.

← Previous: Introduction Next: Manual Infrastructure →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact