Ansible connects over SSH and applies tasks described in YAML. No agent is needed on the servers; they only need Python and an SSH login. It suits configuration of virtual machines and bare metal (packages, users, files, services) and complements Terraform, which creates the infrastructure underneath.
Versions in use on this date. ansible-core 2.13 (released June 2022; it needs Python 3.8 or newer on the control machine) and Ubuntu 22.04 LTS (released April 2022) as the target, which ships Python 3.10. Modules now live in collections, so use fully qualified names such as ansible.builtin.apt.
How a run flows
flowchart TB
INV[Inventory<br/>hosts and groups] --> PB
VARS[group_vars and host_vars<br/>Vault encrypted secrets] --> PB[Playbook with roles]
PB --> CTRL[Control node runs ansible-playbook]
CTRL -->|SSH + Python module upload| W1[web-1]
CTRL -->|SSH| W2[web-2]
W1 --> R1[Each task reports ok, changed or failed]
W2 --> R1
R1 --> H{Task changed something?}
H -->|yes| HAND[Notify handler, for example reload nginx once at end]
H -->|no| SKIP[Idempotent - nothing done]
Project layout
infra/
ansible.cfg
inventory/
production.ini
group_vars/
all.yml
web.yml
vault.yml # encrypted with ansible-vault
site.yml
roles/
nginx/
tasks/main.yml
handlers/main.yml
templates/site.conf.j2
defaults/main.yml
ansible.cfg:
[defaults]
inventory = inventory/production.ini
host_key_checking = True
forks = 10
stdout_callback = yaml
[privilege_escalation]
become = True
inventory/production.ini:
[web]
web-1 ansible_host=10.0.1.11
web-2 ansible_host=10.0.1.12
[web:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
A playbook with a handler and a template
site.yml:
- name: Web servers
hosts: web
become: true
serial: 1 # roll one host at a time
vars:
server_name: shop.example.com
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Render site configuration
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/conf.d/shop.conf
owner: root
group: root
mode: "0644"
notify: Test and reload nginx
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test and reload nginx
ansible.builtin.command: nginx -t
changed_when: false
notify: Reload nginx
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
The first handler tests the configuration, and only if it is valid does it notify the reload handler; a failed test stops the play before a broken config is loaded. Where a tool accepts a file path, the validate: option of template and copy is even better, for example validate: visudo -cf %s for sudoers files.
Run:
python3 -m pip install --user "ansible-core==2.13.*"
ansible-galaxy collection install ansible.posix community.general
ansible web -m ping
ansible-playbook site.yml --check --diff # dry run, show differences
ansible-playbook site.yml --limit web-1 # canary on one host
ansible-playbook site.yml
Ideas that matter
- Idempotent: a second run changes nothing if the server is already correct. Prefer modules (
apt,user,template,lineinfile) overcommandandshell; if you must use them, addcreates:orchanged_when:so reports stay truthful. - Handlers run once at the end of a play, only when notified, so a config change triggers one reload, not five.
- Variables come from many places; keep defaults in
roles/*/defaults, environment values ingroup_vars, and remember that extra vars (-e) win. - Roles package tasks, templates and defaults for reuse; share them through collections or a Git repository.
- Tags and
--limitlet you apply part of a playbook or one host. serialandmax_fail_percentagegive rolling updates and stop a bad change from hitting every server.
Secrets with Vault
ansible-vault create inventory/group_vars/vault.yml # contains db_password: ...
ansible-playbook site.yml --ask-vault-pass
Reference {{ db_password }} in templates, set no_log: true on tasks that handle secrets so they are not printed, and keep the vault password in your password manager or CI secret store, never in the repository.
Safe operation
- Review changes by pull request, and run
ansible-lintandyamllintin CI. - Use SSH keys and a dedicated automation user with scoped
sudo; keephost_key_checkingon. - Test roles on a disposable VM or container first (Molecule if you want automation).
- Always run with
--check --diffon production before the real run, remembering that check mode cannot predict everycommandtask. - Pin collection versions in
requirements.ymlfor reproducible runs.
Troubleshooting
UNREACHABLE: SSH user, key, security group or host key problem; test with plainsshand-vvv./usr/bin/python: not found: setansible_python_interpreter=/usr/bin/python3.Missing sudo password: configure passwordless sudo for the automation user, or pass--ask-become-pass.- Module not found after upgrade: install the collection that now contains it.