Agentless Automation
Ansible is a configuration-management and automation tool that is agentless: it connects to managed hosts over SSH (or WinRM on Windows) and runs modules, then removes them. There is no daemon to install on target machines — the only requirements are SSH access and Python on the target.
Push, not pull
Unlike agent-based tools, Ansible pushes configuration from a control node on demand. This keeps managed hosts clean and makes it easy to run against ephemeral cloud instances.
Inventory
The inventory lists the hosts Ansible manages, organized into groups. It can be written in INI or YAML, or generated dynamically from a cloud provider.
INI format
[web]
web1.example.com
web2.example.com
[db]
db1.example.com ansible_host=10.0.1.5
[prod:children]
web
db
[prod:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
YAML format
all:
children:
web:
hosts:
web1.example.com:
web2.example.com:
db:
hosts:
db1.example.com:
ansible_host: 10.0.1.5
vars:
ansible_user: deploy
Ad-Hoc Commands
For one-off tasks, run a module directly with ansible instead of writing a playbook.
# Ping all hosts
ansible all -m ping
# Check uptime on the web group
ansible web -m command -a "uptime"
# Install nginx (needs privilege escalation)
ansible web -m ansible.builtin.apt -a "name=nginx state=present" --become
# Gather facts
ansible db1.example.com -m setup
Playbooks and Tasks
A playbook is a YAML file describing a set of plays. Each play maps a group of hosts to a list of tasks, and each task calls a module with arguments. Tasks run top to bottom, in order.
---
- name: Configure web servers
hosts: web
become: true
vars:
app_port: 8080
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy config from template
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
notify: Restart nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Run it with ansible-playbook -i inventory.yml site.yml. Add --check for a dry run and --diff to preview file changes.
Idempotency
Idempotency means running a playbook repeatedly produces the same result — modules only change what is not already in the desired state. A second run should report changed=0. Prefer purpose-built modules over command/shell, which are not idempotent unless you add guards like creates.
Handlers
A handler is a task that runs only when notified by another task that reported a change. Handlers run once, at the end of the play, even if notified many times — ideal for restarting a service only when its config actually changed.
Variables and Facts
Variables parameterize plays and can be defined in inventory, playbooks, group_vars/, host_vars/, or the command line. Facts are variables Ansible automatically discovers about a host (OS, IPs, memory) via the setup module.
tasks:
- name: Show the OS family (a fact)
ansible.builtin.debug:
msg: "Running on {{ ansible_facts['os_family'] }}"
- name: Only run on Debian-based systems
ansible.builtin.apt:
name: htop
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Loop over a list variable
ansible.builtin.user:
name: "{{ item }}"
state: present
loop: "{{ dev_users }}"
Jinja2 Templates
The template module renders Jinja2 files, substituting variables and facts. Template files end in .j2.
# templates/nginx.conf.j2
server {
listen {{ app_port }};
server_name {{ ansible_facts['hostname'] }};
{% for backend in backends %}
upstream_server {{ backend }};
{% endfor %}
}
Roles
A role is a standardized directory structure that bundles tasks, handlers, templates, files, and defaults into a reusable unit. Roles keep large playbooks organized and shareable.
roles/
webserver/
tasks/main.yml # main task list
handlers/main.yml # handlers
templates/ # .j2 files
files/ # static files to copy
defaults/main.yml # default variables (lowest precedence)
vars/main.yml # role variables (higher precedence)
meta/main.yml # role metadata / dependencies
# Using a role in a playbook
- name: Set up web tier
hosts: web
become: true
roles:
- webserver
- { role: monitoring, tags: ["obs"] }
ansible-vault
Store secrets encrypted at rest with ansible-vault. Encrypted files or variables are decrypted transparently at runtime when you supply the vault password.
# Encrypt a secrets file
ansible-vault encrypt group_vars/prod/vault.yml
# Edit an encrypted file
ansible-vault edit group_vars/prod/vault.yml
# Run a playbook, prompting for the vault password
ansible-playbook site.yml --ask-vault-pass
# Or point at a password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Best practice
Keep secret values in a dedicated vault.yml and reference them from plain files. This lets you version-control most variables in the clear while encrypting only the sensitive ones.
ansible-galaxy
Ansible Galaxy is the hub for sharing roles and collections. Use it to scaffold roles and install community content.
# Scaffold a new role
ansible-galaxy init roles/webserver
# Install a collection
ansible-galaxy collection install community.general
# Install roles/collections from a requirements file
ansible-galaxy install -r requirements.yml
Practice Exercises
- Write a YAML inventory with
webanddbgroups and ping all hosts with an ad-hoc command. - Write a playbook that installs and starts nginx, then run it twice to confirm idempotency (
changed=0the second time). - Add a handler that restarts nginx only when a templated config file changes.
- Use a fact in a
whencondition so a task runs only on Debian-family hosts. - Refactor your playbook into a role using
ansible-galaxy init. - Encrypt a secrets file with ansible-vault and run the playbook using a vault password file.