Hitesh Sahu
Hitesh SahuHitesh Sahu
  1. Home
  2. ›
  3. posts
  4. ›
  5. …

  6. ›
  7. 6 Ansible

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🤯 Your stomach gets a new lining every 3–4 days.

🍪 This website uses cookies

No personal data is stored on our servers however third party tools Google Analytics cookies to measure traffic and improve your website experience. Learn more

Loading ⏳
Fetching content, this won’t take long…


💡 Did you know?

🐙 Octopuses have three hearts and blue blood.
Programming

    AI-AgenticAI

    AI-DeepLearning

    AI-GenAI

    AI-Infrastructure

    AI-Machine-Learning

    AI-Math

    AWS

    Azure

    Hobbies

    kubernetes

    Management

    Programming
    • 📘 Data Structures, Algorithms & DBMS

    • 💻 Principles of Programming 📖

    • ⚙️ Backend & DevOps 📖

    • 🧪 Testing 📖

    • 🌀 Agile Methodology 📖

    • 🛠️ Backend - Microservices 📖

    • Ansible: Agentless Configuration Management

    • CI/CD Pipelines: From Commit to Production

    • Programming Index


    Terraform

    Z_Appendix

    0-root

Cover Image for Ansible: Agentless Configuration Management
Programming

Ansible: Agentless Configuration Management

Ansible from the ground up — agentless push architecture, inventory, playbooks, modules, roles, idempotency, Ansible Vault, a real GPU-node provisioning example, Ansible vs Terraform, and a set of interview questions with answers.

Ansible
Configuration Management
IaC
DevOps
Automation
Cloud
← Previous

CI/CD Pipelines: From Commit to Production

Next →

KCSA Mock Exam — Set 2

Ansible

Ansible is Agentless Configuration Management

Terraform answers

"what infrastructure should exist?"

  • VMs, networks, load balancers, GPU node pools.

Ansible answers:

"what should be installed and configured on the machines that already exist?"

  • packages, users, config files, services, NVIDIA drivers, kubelet versions.

They get used together constantly: Terraform provisions the box, Ansible configures what runs on it.

flowchart TD

    Terraform["Terraform <br/> Provision infrastructure <br/> (VMs, VPCs, GPU node pools)"]
    Terraform-->|"hands off IPs / inventory"| Ansible["Ansible <br/> Configure the machines <br/> (packages, drivers, services)"]
    Ansible-->Running["Running, configured node <br/> ready for workloads"]

Why Ansible

Ansible automates the management of remote systems and controls their desired state.

Most configuration management tools before Ansible (Puppet, Chef) required an agent running on every managed node, phoning home to a central server on a schedule.

Ansible does neither. It's agentless and push-based:

flowchart TD

    Control["Control Node <br/> (your laptop / CI runner)"]
    Control-->|"SSH<br/>(or WinRM for Windows)"| Node1["Managed Node 1"]
    Control-->|"SSH"| Node2["Managed Node 2"]
    Control-->|"SSH"| Node3["Managed Node N"]

    Control-.->|"copies a small Python script<br/>over SSH, runs it, deletes it"| Node1

Ansible is Agentless, Push-Based

Agent-based (Puppet, Chef) Ansible
Requires software on managed node Yes (agent daemon) No — just SSH + Python
Execution model Pull (agent polls the server) Push (control node initiates)
Setup overhead Install and register an agent everywhere Nothing to install on targets
Real-time ad-hoc commands Awkward Native (ansible <host> -m ...)

Because there's no agent, a new node is manageable the moment it has SSH access and Python — which is exactly why it fits bare-metal GPU node bring-up so well:

  • image the box
  • get SSH working

and Ansible can take it from there.

Idempotency

Running ansible multiple times produces the same result.

The single most important property of a well-written playbook: running it twice produces the same end state, and the second run reports "nothing changed."

flowchart LR
    Run1["ansible-playbook site.yml <br/> (1st run)"]
    Run1-->Changed["changed=3 <br/> ok=5"]

    Run2["ansible-playbook site.yml <br/> (2nd run, same target)"]
    Run2-->NoOp["changed=0 <br/> ok=8"]

This is what makes a playbook safe to run in a cron job, in CI, or by a teammate without knowing exactly what state the target is currently in.

Ansible's built-in modules check current state before acting (apt checks if the package is already installed; service checks if it's already running) rather than blindly re-executing shell commands.


Core Concepts

flowchart LR
    subgraph Control["Control Node"]
        CLI["Ansible CLI"]
        Inventory["Inventory"]
        Playbooks["Playbooks"]
    end

    CLI --> SSH1
    CLI --> SSH2
    CLI --> SSH3

    SSH1 --> Host1["Managed Node 1"]
    SSH2 --> Host2["Managed Node 2"]
    SSH3 --> Host3["Managed Node 3"]
Concept What it is
Inventory The list of managed hosts, optionally grouped ([gpu_nodes], [web])
Module A unit of work — apt, copy, service, user, nvidia_driver (custom)
Task One module invocation with specific arguments
Playbook A YAML file containing one or more plays
Play A set of tasks mapped to a group of hosts
Role A reusable, self-contained bundle of tasks, templates, and defaults
Handler A task that only runs when notified (e.g. "restart nginx" after a config change)
Facts Auto-discovered data about a host (OS, IP, CPU count, GPU presence)

💻 Control node

A system on which Ansible is installed.

The Control Node initiates connections to the managed nodes over SSH (or WinRM for Windows), reads the inventory, and executes tasks defined in playbooks.

The control node is responsible for:

  • Reading the inventory
  • Loading playbooks
  • Connecting to remote hosts
  • Executing Ansible modules
  • Collecting task results

Example: Any Macbook with Ansible installed can work as Control Node

ansible

ansible-playbook

ansible-inventory

ansible-galaxy

📋 Inventory

An Inventory is a list of systems that Ansible manages.

Inventory on the control node describe host deployments to Ansible.

It tells Ansible:

  • Which hosts exist
  • How to connect to them
  • How they are grouped

Example inventory:

    [web]
    web1.example.com
    web2.example.com
    
    [database]
    db1.example.com
    
    [production:children]
    web
    database

The inventory allows you to target groups instead of individual machines.

For example:

ansible web -m ping

This runs the ping module on every host in the web group.

At real cluster scale, inventory is usually dynamic

Usually a script or plugin (e.g. querying a cloud provider API, or NVIDIA Base Command Manager) generates the host list on the fly instead of a hand-maintained file.

[gpu_nodes]
dgx-01.cluster.local
dgx-02.cluster.local

[control_plane]
master-01.cluster.local

[gpu_nodes:vars]
ansible_user=ubuntu
nvidia_driver_version=550.90.07

🖥️ Managed node

A Managed Node is any remote machine controlled by Ansible.

Managed nodes:

  • Do not require an Ansible agent
  • Need Python installed (for most Linux modules)
  • Must be reachable from the control node

Complete Workflow

sequenceDiagram
    participant User
    participant Control as Control Node
    participant Inventory
    participant Host as Managed Node 🖥️

    User->>Control: ansible-playbook site.yml
    Control->>Inventory: Read inventory
    Inventory-->>Control: Target hosts
    Control->>Host: SSH connection
    Control->>Host: Execute Ansible module
    Host-->>Control: Return results
    Control-->>User: Display task status

📒 Playbooks

A Playbook is a YAML file that defines the automation tasks Ansible should perform.

Anatomy of a Playbook


- name: Play Name
  hosts: web
  become: true

  vars:

  tasks:

  handlers:

Playbook Execution Flow

sequenceDiagram
    participant User
    participant Control Node
    participant Inventory
    participant Host

    User->>Control: ansible-playbook site.yml
    Control->>Inventory: Read inventory
    Inventory-->>Control: Target hosts
    Control->>Host: Execute Task 1
    Host-->>Control: OK
    Control->>Host: Execute Task 2
    Host-->>Control: Changed
    Control->>Host: Run Handler

Running a Playbook

ansible-playbook site.yml

Specify inventory

ansible-playbook \
    -i inventory.ini \
    site.yml

Dry run

ansible-playbook \
    --check \
    site.yml

📜 What is a Play?

A Play maps a group of hosts to a set of tasks.

📋 Tasks

A Task is a single unit of work.

Example

tasks:
  - name: Install Nginx
    ansible.builtin.apt:
      name: nginx
      state: present

Examples of tasks

  • Install packages
  • Create users
  • Copy files
  • Start services
  • Configure firewalls

🧩 Modules

A unit of code or binary that Ansible runs on managed nodes.

Tasks execute modules.


- name: My first play
  hosts: myhosts
  tasks:
   - name: Ping my hosts
     ansible.builtin.ping:

   - name: Print message
     ansible.builtin.debug:
       msg: Hello world

There are many builtin modules/fucntions can be used to perform a task

    ansible.builtin.copy
    ansible.builtin.file
    ansible.builtin.user
    ansible.builtin.service

📝 Variables

Variables make playbooks reusable.

vars:
  package_name: nginx

Use

name: "{{ package_name }}"

Loops

Execute a task multiple times.

tasks:
  - name: Install packages
    ansible.builtin.apt:
      name: "{{ item }}"
      state: present
    loop:
      - git
      - curl
      - nginx

Conditionals

Run tasks only when conditions are met.

Example

- name: Install package
  ansible.builtin.apt:
    name: nginx
  when:
    ansible_os_family == "Debian"

🔧 Handlers

Handlers execute only when notified.

If nothing changes, the handler is not executed.

Example

tasks:

- name: Update Config
  ansible.builtin.copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
  notify:
    Restart Nginx

handlers:

- name: Restart Nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

🏷️ Tags

Run specific tasks.

Example

tasks:
- name: Install Nginx
  tags:
    - install

Execute

ansible-playbook site.yml \
    --tags install

👤 Become

Run tasks as another user.

Usually root.

become: true

Equivalent to

sudo

ℹ️ Facts

Ansible automatically gathers system information.

Example

ansible_hostname

ansible_distribution

ansible_memory_mb

Disable Fact Gathering

gather_facts: false

🎭 Roles

A single flat playbook doesn't scale past a handful of tasks.

Roles package tasks, variables, templates, and handlers into a standard directory layout that any playbook can reuse:

roles/
└── nvidia_driver/
    ├── tasks/main.yml       # the actual steps
    ├── handlers/main.yml    # notify targets (e.g. reboot)
    ├── templates/           # Jinja2 config templates
    ├── defaults/main.yml    # default variable values (lowest precedence)
    └── vars/main.yml        # role-specific variables (higher precedence)

A playbook then just says:

- hosts: gpu_nodes
  roles:
    - nvidia_driver
    - kubelet_bootstrap

ansible-galaxy is the package manager for roles.

ansible-galaxy install nvidia.nvidia_driver pulls a published role the same way helm install pulls a chart, or terraform init pulls a provider.


Templates — Jinja2

Static config files rarely stay static across environments. Ansible renders Jinja2 templates (.j2 files), substituting variables per host:

# templates/kubelet-config.yaml.j2
maxPods: {{ max_pods_per_node }}
podCIDR: {{ pod_cidr }}
{% if gpu_node %}
featureGates:
  DevicePlugins: true
{% endif %}
- name: Render kubelet config
  template:
    src: kubelet-config.yaml.j2
    dest: /etc/kubernetes/kubelet-config.yaml
  notify: Restart kubelet

Same mechanism Helm uses for chart templates (see Helm: Kubernetes Package Manager) — one template, many rendered outputs, one per target.


Ansible Vault — Secrets

Playbooks routinely need secrets — registry credentials, API tokens, TLS keys.

Ansible Vault encrypts a file (or just specific variables) at rest so it's safe to commit alongside the rest of the playbook:

# Encrypt a file containing secrets
ansible-vault encrypt group_vars/gpu_nodes/secrets.yml

# Edit it later (decrypts, opens editor, re-encrypts on save)
ansible-vault edit group_vars/gpu_nodes/secrets.yml

# Run a playbook that needs vault-encrypted vars
ansible-playbook site.yml --ask-vault-pass

Encrypted content looks like this in the repo — safe to diff, safe to commit, useless without the vault password:

$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653063336164663966303231363934653561363964363833313335
...

Ansible vs Terraform

The two aren't competitors so much as adjacent layers — but they overlap enough (both can technically create cloud resources; both are declarative-ish) that it's a very common interview comparison.

Terraform Ansible
Primary job Provision infrastructure (what exists) Configure infrastructure (what's installed/running)
State Tracks state explicitly (terraform.tfstate) Stateless — checks live system state on every run
Execution model Plan → diff against state → apply Sequential task execution against inventory
Language HCL (declarative) YAML (declarative-ish, but tasks run in order)
Idempotency mechanism State file diffing Per-module state checks
Typical scope Cloud resources, networking, GPU node pools OS packages, drivers, config files, service state
Agent required No No

In practice: Terraform creates the DGX node pool and the VPC; Ansible then installs the NVIDIA driver, container toolkit, and kubelet on top of what Terraform built. See Terraform IaC Concepts for the provisioning side of this pairing.


Essential Commands

# Ad-hoc command — no playbook needed
ansible gpu_nodes -m ping
ansible gpu_nodes -m shell -a "nvidia-smi --query-gpu=name,driver_version --format=csv"

# Run a playbook
ansible-playbook -i inventory.ini site.yml

# Dry run — show what would change without changing it
ansible-playbook site.yml --check --diff

# Target one host or one tag
ansible-playbook site.yml --limit dgx-01.cluster.local
ansible-playbook site.yml --tags "drivers"

# List all facts Ansible gathered about a host
ansible dgx-01.cluster.local -m setup

Key Takeaways

Concept Summary
Agentless Just SSH + Python on the target; nothing to install or maintain
Push-based The control node initiates every run — no polling agent
Inventory The list of managed hosts, static or dynamically generated
Playbook / Play / Task Playbook contains plays; each play maps tasks to a host group
Module The actual unit of work (apt, copy, service, ...) — checks state before acting
Idempotency Re-running a playbook against an unchanged target reports zero changes
Role Reusable, shareable bundle of tasks/templates/vars — the unit ansible-galaxy distributes
Handler A task that only fires when notified by a change elsewhere in the play
Vault Encrypts secrets at rest so they're safe to commit alongside the playbook
vs Terraform Terraform provisions the infrastructure; Ansible configures what runs on it

Ansible's entire design point is removing the agent: no daemon to install, register, or lose contact with — just SSH and Python, which is exactly why it's the default choice for bringing a freshly-imaged bare-metal GPU node the rest of the way to "ready for workloads."


Interview Questions

Q: What makes Ansible "agentless," and why does that matter operationally? A: No persistent daemon runs on managed nodes — Ansible connects over SSH, copies a small Python module to the target, executes it, and removes it. Operationally this means there's nothing to install, upgrade, or lose contact with on thousands of nodes; a node is manageable the moment SSH and Python are available, which matters a lot during bare-metal bring-up.

Q: Explain idempotency in Ansible and why it's important. A: Idempotency means running the same playbook twice against an unchanged target produces zero additional changes the second time. Ansible modules check the current state of a resource before acting rather than blindly re-running commands — apt checks if a package is already at the target version, service checks if it's already running. This makes playbooks safe to schedule in cron or CI without knowing exactly what state a target is currently in.

Q: What is the difference between a module, a task, a play, and a playbook? A: A module is a single unit of work (apt, copy, service). A task is one module invocation with specific arguments. A play maps a list of tasks to a group of hosts from the inventory. A playbook is a YAML file containing one or more plays.

Q: How would you handle secrets in a playbook that gets committed to source control? A: Ansible Vault — encrypt the file (or specific variables within it) with ansible-vault encrypt, commit the encrypted content, and supply the vault password at run time (--ask-vault-pass, a vault password file, or an external secrets integration). The repo stays diffable and reviewable; the plaintext secret never touches source control.

Q: When would you reach for Ansible over Terraform, and when do they work together? A: Terraform answers "what infrastructure should exist" — it provisions VMs, networks, GPU node pools, and tracks that in state. Ansible answers "what should be installed and running on machines that already exist" — packages, drivers, config files, service state — and it's stateless, checking live system state on every run instead of diffing against a state file. The common pattern is Terraform provisioning the node, then handing its inventory off to Ansible to configure the OS, NVIDIA driver, container toolkit, and kubelet on top.

Q: How do dynamic inventories work, and why would you need one over a static inventory file? A: A dynamic inventory is a script or plugin that generates the host list at run time — querying a cloud provider's API, a CMDB, or a cluster manager like NVIDIA Base Command Manager — instead of a hand-maintained .ini file. It's necessary once nodes are added/removed frequently or autoscale, since a static file goes stale immediately in that environment.

Q: What's a handler, and how does it differ from a regular task? A: A handler is a task that only executes when explicitly notified by another task via notify, and only if that task reported a change. The canonical example is restarting a service after its config file changes — the restart shouldn't happen on every playbook run, only when the config actually changed. Handlers also only run once per play even if notified multiple times.

Q: How do roles improve on a single large playbook? A: Roles package tasks, handlers, templates, and default variables into a standard, reusable directory structure. Instead of one growing playbook, functionality is decomposed into independent, testable, shareable units (nvidia_driver, kubelet_bootstrap) that any playbook can compose by name, and that can be published and versioned via Ansible Galaxy — directly analogous to how Helm charts package Kubernetes manifests.

Q: How would you test a playbook change is safe before running it against production nodes? A: --check mode (dry run — reports what would change without changing it) combined with --diff (shows the actual before/after content of files that would be modified), scoped with --limit to a single canary host first, before running against the full inventory group.

Hitesh Sahu
Written by Hitesh Sahu, a passionate developer and blogger.

Wed Jul 29 2026

Share This on

← Previous

CI/CD Pipelines: From Commit to Production

Next →

KCSA Mock Exam — Set 2

Programming/6-Ansible
Let's work together
+49 176-2019-2523
hiteshkrsahu@gmail.com
WhatsApp
Skype
Munich 🥨, Germany 🇩🇪, EU
Playstore
Hitesh Sahu's apps on Google Play Store
Need Help?
Let's Connect
Navigation
  Home/About
  Skills
  Work/Projects
  Lab/Experiments
  Contribution
  Awards
  Art/Sketches
  Thoughts
  Contact
Links
  Sitemap
  Legal Notice
  Privacy Policy

Made with

NextJS logo

NextJS by

hitesh Sahu

| © 2026 All rights reserved.