Patch management is where good intentions collide with operational reality. Change windows, fragile shell loops, manual spreadsheet tracking—this is the state of most infrastructure teams before they standardise. Ansible changes the contract: one control node, agentless architecture, human-readable playbooks, and a single command to validate connectivity across every managed host before you touch a package.
Why Agentless Matters
Most automation tools require an agent running on every managed node—meaning agent version drift, additional port exposure, and another process to patch. Ansible uses SSH (or WinRM for Windows), which you already have open and already manage. There is nothing to install on targets, only on the control node.
Control Node vs Managed Nodes
| Role | Description |
|---|---|
| Control node | Where Ansible runs. Requires Python 3.x. Linux or macOS native; Windows requires WSL2. |
| Managed node | Any host Ansible connects to. Requires Python 3.x and SSH access. No daemon. |
Ansible never daemonizes on managed nodes. Every run is a push from the control node outward.
Installing Ansible
Ubuntu / Debian:
sudo apt update && sudo apt install -y ansible
RHEL / CentOS / Fedora:
sudo dnf install -y ansible
macOS (Homebrew):
brew install ansible
Windows: Ansible control is not natively supported on Windows. Use WSL2:
wsl --install
# Then follow the Ubuntu steps above inside the WSL2 shell
Verify the installation:
ansible --version
SSH Key-Based Authentication
Ansible needs passwordless SSH access to managed nodes. Generate a dedicated key pair on the control node:
ssh-keygen -t ed25519 -C "ansible-control" -f ~/.ssh/ansible_id
Copy the public key to each managed node:
ssh-copy-id -i ~/.ssh/ansible_id.pub adminuser@TARGET_HOST
Test the connection manually before running any playbook:
ssh -i ~/.ssh/ansible_id adminuser@TARGET_HOST
Inventory Basics
The inventory tells Ansible which hosts exist and how to reach them.
INI format (inventory.ini):
[webservers]
web01.example.com
web02.example.com
[dbservers]
db01.example.com
[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/ansible_id
YAML format (inventory.yml):
all:
children:
webservers:
hosts:
web01.example.com:
web02.example.com:
dbservers:
hosts:
db01.example.com:
vars:
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/ansible_id
Validate Connectivity Before You Patch
Never start a patching run without confirming every host responds. The ping module is not ICMP—it tests the full Ansible connection stack (SSH + Python):
ansible all -i inventory.ini -m ping
Expected output for a healthy host:
web01.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
Any UNREACHABLE or FAILED here becomes a failed patch run. Fix connectivity first.
First Playbook: Dry-Run Package Update
Create update-check.yml:
---
- name: Dry-run package update on all hosts
hosts: all
become: true
tasks:
- name: Simulate package upgrade (apt)
ansible.builtin.apt:
upgrade: dist
update_cache: true
check_mode: true
register: apt_result
- name: Show what would change
ansible.builtin.debug:
var: apt_result
Run it with check mode and diff:
ansible-playbook -i inventory.ini update-check.yml --check --diff
--check simulates execution without making changes. --diff shows exactly which packages or files would be modified. Nothing touches the managed nodes until you remove --check.
Next in the series: Must-Know Ansible Commands and Core Concepts — ad-hoc commands, targeting patterns, privilege escalation, and running security-only updates from the command line.
