Table of Contents
What Ansible Is (in Practice)
Ansible is a configuration management and automation tool that connects to your systems (usually over SSH) and applies the state you describe in YAML files (called playbooks).
Key properties:
- Agentless: no agent process on target machines, just Python + SSH.
- Push-based: you run Ansible from a control node and push changes.
- Idempotent: tasks are designed so running them multiple times leads to the same final state (e.g., “package is installed” instead of “run install command”).
At a very high level, you:
- Have a control node (where Ansible is installed).
- Define an inventory (what machines to manage).
- Write playbooks (what to do on those machines).
- Run
ansibleoransible-playbookto apply changes.
This chapter focuses on hands‑on basics: inventories, modules, ad‑hoc commands, and simple playbooks.
Installing Ansible (for Learning)
On a typical Linux workstation:
- On Debian/Ubuntu:
sudo apt update
sudo apt install ansible- On Fedora:
sudo dnf install ansible
A very distribution‑agnostic way is using pip (Python package manager):
python3 -m pip install --user ansibleYou can confirm installation with:
ansible --versionYou only need Ansible installed on the control node. Managed hosts just need:
- Python (usually already installed)
- SSH access from the control node
The Ansible Inventory
The inventory is how Ansible knows what systems to manage.
By default, Ansible looks at /etc/ansible/hosts, but you can pass any file with -i.
Simple INI-Style Inventory
Example inventory.ini:
[webservers]
web1.example.com
web2.example.com
[dbservers]
db1.example.com
[all:vars]
ansible_user=devopsExplanation:
[webservers]and[dbservers]are groups.- Each line under a group is a host.
[all:vars]sets variables for all hosts in this inventory (e.g., SSH user).
You can test connectivity with:
ansible -i inventory.ini all -m pingCommon inventory host variables:
ansible_host– IP or DNS name if different from the inventory hostname.ansible_user– SSH username.ansible_port– non‑standard SSH port.ansible_ssh_private_key_file– path to SSH key.
Example with host‑specific variables:
[webservers]
web1 ansible_host=192.0.2.10 ansible_user=ubuntu
web2 ansible_host=192.0.2.11 ansible_user=ubuntuYAML Inventory (Basic Idea)
Inventory can also be YAML. A minimal example:
all:
children:
webservers:
hosts:
web1:
ansible_host: 192.0.2.10
web2:
ansible_host: 192.0.2.11
dbservers:
hosts:
db1:
ansible_host: 192.0.2.20
vars:
ansible_user: devopsFormat choice is mostly preference at the beginner level; INI style is simpler to start with.
Ansible Modules and Ad-Hoc Commands
Modules are small units of work that Ansible executes on target hosts. Everything you do with Ansible uses modules under the hood.
You can run a single module without a playbook using ad‑hoc commands:
ansible <pattern> -i inventory.ini -m <module> -a "<arguments>"Where:
<pattern>selects hosts (e.g.,all,webservers,db1).-mselects the module (e.g.,ping,command,copy,yum).-apasses arguments.
Common Beginner Modules
ping: test if Ansible can talk to a host:
ansible all -i inventory.ini -m pingcommand: run a command (does not use a shell):
ansible webservers -i inventory.ini -m command -a "uptime"shell: run a shell command (supports pipes, redirects):
ansible webservers -i inventory.ini -m shell -a "df -h /"copy: copy a file from control node to hosts:
ansible webservers -i inventory.ini -m copy \
-a "src=/tmp/index.html dest=/var/www/html/index.html owner=www-data group=www-data mode=0644"file: manage file attributes or create directories:
ansible webservers -i inventory.ini -m file \
-a "path=/var/www/html state=directory owner=www-data mode=0755"- Package modules (e.g.,
apt,dnf,yum,package):
ansible webservers -i inventory.ini -m apt \
-a "name=nginx state=present update_cache=yes" --become
--become tells Ansible to use privilege escalation (like sudo).
Ad‑hoc commands are excellent for trying modules and doing quick, one‑off changes.
Your First Playbook
For anything more than quick actions, you write a playbook: a YAML file describing:
- Which hosts to target
- What tasks (modules + parameters) to run
- In what order
Example: a simple playbook to install and start Nginx on Debian/Ubuntu web servers:
webserver.yml:
- name: Configure web servers
hosts: webservers
become: yes
tasks:
- name: Ensure apt cache is updated
apt:
update_cache: yes
cache_valid_time: 3600
- name: Ensure nginx is installed
apt:
name: nginx
state: present
- name: Ensure nginx is running and enabled
service:
name: nginx
state: started
enabled: yesRun it with:
ansible-playbook -i inventory.ini webserver.ymlConcepts illustrated:
- A play (top‑level
- name: ... hosts: ...) applies tasks to one group of hosts. become: yesto gain root privileges (likesudo).tasksis a list; each task:- Has a
name(for readable output). - Calls a module (
apt,service) with arguments as YAML key/value pairs.
Ansible output will show tasks as:
- ok – nothing changed; system already in desired state.
- changed – Ansible made changes.
- failed – the task could not complete.
Basic Playbook Syntax Rules
A few essentials to avoid common beginner mistakes:
- YAML is indentation‑sensitive.
- Use spaces, not tabs.
- Common style: 2 spaces or 4 spaces per level (just be consistent).
- Lists start with
-: - Plays:
- name: ... - Tasks:
- name: ... - Modules and their options are keys under the task:
- name: Ensure nginx is installed
apt:
name: nginx
state: present- Strings with colons or special characters might need quotes:
- name: Print a message
debug:
msg: "User: {{ ansible_user }}"
If in doubt, use ansible-lint or yamllint (optional tools) to validate syntax.
Variables (Very Basic Use)
Variables let you avoid repeating values and make playbooks reusable.
Where they commonly show up in “basics”:
- Inventory variables:
[webservers]
web1 http_port=80
web2 http_port=8080Then in a playbook, you can use:
- name: Show HTTP port
debug:
msg: "This host uses port {{ http_port }}"- Playbook vars section:
- name: Install a package
hosts: webservers
become: yes
vars:
web_package: nginx
tasks:
- name: Ensure web package is installed
apt:
name: "{{ web_package }}"
state: presentKey syntax:
- Use
{{ variable_name }}to reference variables in templates and module arguments.
Idempotence and “Desired State”
Ansible’s power is in expressing state, not just commands. For example:
- Bad (imperative style):
- name: Install nginx with command
shell: "apt-get install -y nginx"Running this multiple times may be slow, and Ansible cannot easily tell if something changed.
- Better (declarative style):
- name: Ensure nginx is installed
apt:
name: nginx
state: presentHere:
- If nginx is not installed: Ansible installs it →
changed. - If already installed: nothing to do →
ok.
The goal: describe the final state you want, not step‑by‑step shell commands, whenever possible.
Using `--check` and `--diff` (Dry Runs)
When you’re learning, it’s useful to see what Ansible would change:
--check: simulate changes (where supported by modules).
ansible-playbook -i inventory.ini webserver.yml --check--diff: show differences for files/templates.
ansible-playbook -i inventory.ini webserver.yml --diff
Not all modules perfectly support --check, but it’s still a valuable sanity check before touching many systems.
Basic Troubleshooting Tips
If something goes wrong:
- Increase verbosity:
ansible-playbook -i inventory.ini webserver.yml -vv
-v, -vv, -vvv give progressively more detail.
- Check SSH connectivity:
ansible all -i inventory.ini -m ping -vv- Verify Python is available on target hosts
Many modules need Python on the managed host (usually/usr/bin/python3). - Inspect failed task
Read the “msg” field in the failure output; it often includes the underlying command error.
A Minimal Workflow to Practice
- Create a test inventory (
inventory.ini) with one or two Linux VMs. - Test connectivity with:
ansible all -i inventory.ini -m ping- Run a simple ad‑hoc command:
ansible all -i inventory.ini -m command -a "hostname"- Write a tiny playbook to:
- Install a package
- Create a directory
- Copy a small file
- Run with
--checkfirst, then without, and observeokvschanged.
This is enough to get comfortable with the basics so you can move on to more advanced topics like structured roles, complex variables, and integration into larger DevOps pipelines.