In this episode we will dissect how Ansible works behind the scenes, from the Control Node and Managed Nodes architecture, the module execution flow over SSH, to key components such as Inventory, Playbook, and ansible.cfg.

After episode 1, where we discussed the history, background, and reasons why Ansible became the top choice in the automation world — from the evolution of shell scripting, the agentless concept, to its comparison with Puppet, Chef, and Terraform — in this episode we will pull back the curtain and dissect what actually happens behind the scenes every time we run an Ansible command.
Why do we need to understand this architecture? There's a saying in the engineering world: "you can't fix what you don't understand". When your playbook fails in production, when execution suddenly becomes slow, or when you have to design a management strategy for hundreds of servers — this architectural understanding is what separates an engineer who merely guesses from one who can diagnose accurately. This episode is the foundation of your mental model of Ansible.
Before discussing the components in detail, let's look at the big picture of Ansible's architecture first.
+------------------------------+
| CONTROL NODE |
| |
| +------------------------+ |
| | ANSIBLE ENGINE | |
| | (playbook, modules, | |
| | inventory, config) | |
| +------------+-----------+ |
+---------------|--------------+
| SSH (port 22)
+-------------------+------------------+
| | |
+--------+---------+ +------+--------+ +--------+---------+
| MANAGED NODE 1 | | MANAGED NODE 2 | | MANAGED NODE 3 |
| Python 3 | | Python 3 | | Python 3 |
| (Ubuntu Server) | | (Rocky Linux) | | (Debian Server) |
+------------------+ +-----------------+ +-----------------+At first glance the picture above may look simple, but hidden inside are very elegant design decisions. There are two major roles in this architecture:
The Control Node is the brain of all Ansible operations. It's the machine where you run ansible, ansible-playbook, ansible-galaxy, and all other Ansible tooling. Its characteristics:
ansible.cfg — live here.Note
Make a note of this: the Control Node generally cannot manage itself as a managed node by default, and almost no server platform is supported as a Control Node (for example, Windows cannot be a Control Node, only a managed node). For learning, keep the Control Node focused on Linux, macOS, or WSL2.
Managed Nodes are the targets of all Ansible work — they can be Linux servers, network devices (switches/routers), Windows servers, and even containers. The requirements are very light:
This is the beauty of the agentless approach we discussed in episode 1: the cost of "onboarding" a server into Ansible management is almost zero.
The big question that often comes up: "So what actually happens when Ansible sends a command?" Let's break down the execution flow step by step.
Control Node Managed Node
| |
| 1. Baca inventory & validasi playbook |
|----------------------------------->|
| 2. Buka koneksi SSH |
|----------------------------------->|
| 3. Kirim modul Python + argumen |
|----------------------------------->|
| | 4. Eksekusi modul (cek state)
| | 5. Terapkan perubahan bila perlu
| | 6. Hapus script sementara
| 7. Kembalikan hasil JSON |
|<-----------------------------------|
| 8. Render output ke terminal |Tip
Because each execution opens a new SSH connection and sends a Python module, Ansible is considered connection-oriented and stateless. No persistent connection is kept between tasks. This simplifies the design, but it also means network overhead per task — later in the performance tuning episode we will learn to overcome this with SSH pipelining and forks.
Let's see real evidence of this flow by running the ping module and observing its JSON output:
ansible -i inventory.ini all -m ping -vNotice "ping": "pong" — this is not ICMP ping, but proof that the Control Node successfully: (1) connected via SSH, (2) sent the ping.py Python module to the target, (3) executed it, and (4) received the JSON result back. The whole agentless cycle above happens within seconds.
Important
The keyword to remember this flow: SSH -> Send module -> Execute -> Delete -> Return JSON. These five steps are the "heart" of every Ansible operation. Once you internalize this mental model, all advanced concepts (handlers, roles, async) will make much more sense.
Now we move into the components that make up Ansible's "vocabulary". These are the terms you will use every day.
Inventory is the list of managed nodes known to Ansible, along with the variables attached to them. It can be in INI format (classic) or YAML (modern), and can be static (a file) or dynamic (generated from cloud APIs).
[webservers]
web01 ansible_host=10.0.0.11 ansible_user=devops
web02 ansible_host=10.0.0.12 ansible_user=devops
[dbservers]
db01 ansible_host=10.0.0.13 ansible_user=devops
[all:vars]
ansible_port=22
ansible_ssh_private_key_file=~/.ssh/id_ed25519all:
children:
webservers:
hosts:
web01:
ansible_host: 10.0.0.11
ansible_user: devops
web02:
ansible_host: 10.0.0.12
ansible_user: devops
dbservers:
hosts:
db01:
ansible_host: 10.0.0.13
ansible_user: devopsNote
Full details on inventory — host grouping, host patterns like webservers:&staging, and dynamic inventory for the cloud — will be covered thoroughly in episode 3. For this episode, what matters is understanding its function first: the inventory is the map of all managed servers.
Playbook is a YAML file containing declarative instructions — "what should be done to which servers". A playbook contains one or more Plays. Each Play defines:
hosts — which host pattern the commands apply to (the target).become — whether privilege escalation (sudo) is needed.tasks — the list of tasks run against those hosts.---
- name: Konfigurasi dasar semua web server # <- Play 1
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Konfigurasi dasar semua db server # <- Play 2
hosts: dbservers
tasks:
- name: Install postgresql
ansible.builtin.apt:
name: postgresql
state: presentHere's how to read the hierarchy: a Playbook contains Plays, a Play contains Tasks, and a Task uses Modules. Like reading a book: a book (playbook) consists of chapters (play), chapters consist of paragraphs (task), and paragraphs use words (module).
Task is the smallest execution unit in a playbook — a single instruction run against hosts. Module, meanwhile, is the code (usually Python) that actually does the work on the managed node.
Ansible has hundreds of modules grouped into Collections (e.g., ansible.builtin, community.general, amazon.aws). Here are some of the most frequently used modules:
| Module | Full Name | Function |
|---|---|---|
ping | ansible.builtin.ping | Test connection & target readiness (not ICMP) |
setup | ansible.builtin.setup | Collect the target's system facts |
command | ansible.builtin.command | Run a plain CLI command (not through a shell) |
shell | ansible.builtin.shell | Run a command through a shell (supports pipes, redirects) |
file | ansible.builtin.file | Manage file/directory attributes (mode, owner, state) |
copy | ansible.builtin.copy | Copy files from the Control Node to the managed node |
apt | ansible.builtin.apt | Manage packages on Debian/Ubuntu |
dnf | ansible.builtin.dnf | Manage packages on RHEL/Rocky/Fedora |
service | ansible.builtin.service | Manage service status (started/stopped/enabled) |
Let's look at the two most basic modules: ping and setup.
# Tampilkan semua fakta sistem target (output sangat panjang)
ansible -i inventory.ini web01 -m setup
# Filter fakta spesifik (misal OS family & total memori)
ansible -i inventory.ini web01 -m setup -a "filter=ansible_os_family"
ansible -i inventory.ini web01 -m setup -a "filter=ansible_memtotal_mb"10.0.0.11 | SUCCESS => {
"ansible_facts": {
"ansible_os_family": "Debian",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false
}Tip
The setup module is the engine behind Ansible Facts — automatic data about every target (OS, architecture, IP, RAM, and much more) that playbooks can use to make dynamic decisions, for example choosing a package manager based on ansible_facts['os_family']. We will cover facts in depth in episode 7.
ansible.cfg)All of Ansible's default behavior is managed in the ansible.cfg configuration file. Its search priority order (from highest to lowest):
ANSIBLE_CONFIG).ansible.cfg file in the current working directory.~/.ansible.cfg file in the user's home directory./etc/ansible/ansible.cfg file (system default).[defaults]
inventory = ./inventory.yml
forks = 10
host_key_checking = False
remote_user = devops
private_key_file = ~/.ssh/id_ed25519
timeout = 30
retry_files_enabled = False
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = FalseLet's discuss the most important options in the example above:
inventory: sets the default inventory file, so we don't have to keep typing -i inventory.yml.forks: the number of parallel connections to managed nodes at once. Default is 5; set it higher to speed up execution across many servers.host_key_checking: in a lab, set to False so it doesn't prompt for fingerprint confirmation. In production, leave it active or manage known_hosts properly.remote_user & private_key_file: the default SSH credentials used by all hosts (can be overridden per host in the inventory).[privilege_escalation]: defaults for become/sudo, so you don't need to write become: true in every task (but it can still be overridden).Warning
The Ansible configuration file is not a place to store secrets. Never put SSH passwords or vault passwords in ansible.cfg or the inventory, let alone commit them to a Git repository. For secrets, use Ansible Vault (covered in episode 14) or a credential manager integration.
To view the active configuration along with its source, use the ansible-config dump or ansible-config list commands:
# Lihat semua nilai konfigurasi + dari mana asalnya
ansible-config dump
# Lihat hanya opsi tertentu
ansible-config dump | grep -i forksBefore we close, let's assemble all of this episode's concepts into one complete mental model:
ansible.cfg (konfigurasi default)
|
inventory.yml (daftar managed nodes)
|
playbook.yml (Play -> Tasks -> Modules)
|
Control Node --SSH--> Managed Node (Python 3)
| |
+--- kirim modul ------------> |
| +--- eksekusi & cek state
+--- terima hasil JSON <----- |In short: ansible.cfg governs how, inventory determines where to, and playbook determines what needs to be done — all executed from the Control Node to the Managed Nodes over SSH following the send module, execute, delete, return JSON flow.
In episode 2 we dissected Ansible's architecture and core components. We learned that Ansible works with two roles — the Control Node as the brain and the Managed Nodes as the targets — with an elegant, agentless execution flow: sending Python modules over SSH, executing them on the target, deleting the temporary script, then returning results in JSON format.
Key takeaways:
ansible.cfg governs default behavior (inventory, forks, SSH, privilege escalation).ping and setup modules are the first debugging tools you must master.How was episode 2? Hopefully Ansible's architecture, which used to seem "magical", now feels more transparent and understandable. Trust us, this kind of foundational understanding is what makes you confident when facing errors in your playbook later.
Now, to continue our journey, in episode 3 we will discuss Managing Inventory (Static & Dynamic) — from INI vs YAML formats, host grouping, host patterns, to an introduction to dynamic inventory for the auto-scaling cloud era. Keep your enthusiasm up, because starting next episode we will be wrestling with configuration files and real commands more and more! 😄