Il primo Playbook: da zero a produzione
Nel post precedente abbiamo capito perché Ansible esiste e cosa lo rende diverso dagli altri strumenti di automazione. Ora passiamo al pratico: scriveremo il primo playbook, riga per riga, e lo eseguiremo su un server reale.
L'obiettivo è installare e configurare Nginx su un server remoto, il classico "Hello World" dell'automazione infrastrutturale. Semplice abbastanza da capire tutto, complesso abbastanza da toccare tutti i concetti fondamentali.
Prima di scrivere una riga di YAML
Per seguire questo laboratorio servono tre cose: Ansible installato sul control node, un server raggiungibile via SSH, e una coppia di chiavi SSH già configurata.
- Control Node
Mac o Linux da cui si lancia Ansible. Windows richiede WSL2 - Managed Node
Un server Linux (Ubuntu 22.04 ideale). Può essere una VM locale, un VPS, o un'istanza cloud - Chiavi SSH
La chiave pubblica deve essere presente in~/.ssh/authorized_keyssul server - Ansible
Versione 2.14 o superiore. Installabile con pip in un virtualenv
# Create a dedicated virtualenv
$ python3 -m venv ~/.venv/ansible
$ source ~/.venv/ansible/bin/activate
# Install Ansible and linter
$ pip install ansible ansible-lint
# Check Ansible installation
$ ansible --version
ansible [core 2.17.0]
python version = 3.11.4Installazione di Ansible
Pro tip
Usa sempre un virtualenv per Ansible. Isola le dipendenze, permette di avere versioni diverse per progetti diversi, e non inquina il sistema Python globale.
L'Inventory: dire ad Ansible dove andare
Prima di scrivere cosa fare, Ansible ha bisogno di sapere dove farlo. L'inventory è esattamente questo: l'elenco dei server che deve gestire, organizzati in gruppi logici.
Crea una cartella per il progetto e al suo interno un file inventory.ini:
# Groups are enclosed in square brackets
[webservers]
web01 ansible_host=192.168.1.10 ansible_user=ubuntu
web02 ansible_host=192.168.1.11 ansible_user=ubuntu
# Variables shared across the group
[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=~/.ssh/id_ed25519inventory.ini
Prima di procedere, verificare che Ansible riesca a raggiungere i server con il modulo ping. Attenzione: non è il ping ICMP standard, è un test di connettività Ansible via SSH:
$ ansible webservers -i inventory.ini -m ping
web01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
web02 | SUCCESS => {
"changed": false,
"ping": "pong"
}Nota
Se il risultato è SUCCESS su tutti i server, si può procedere. In caso di errori di connessione, verificare che la chiave SSH sia corretta e che il firewall permetta connessioni sulla porta 22.
Anatomia del playbook
Un playbook è un file YAML che descrive cosa ottenere sui server. Non una serie di comandi shell da eseguire in sequenza, ma una dichiarazione dello stato finale desiderato. Questa differenza è fondamentale.
Creiamo install_nginx.yml per installare Nginx ed eseguirlo sugli host remoti.
Intestazione del playbook
Un play inizia sempre con tre informazioni fondamentali:
- il nome descrittivo
- il gruppo di server target
- i privilegi di esecuzione
---
- name: Install and configure Nginx # human-readable name for this play
hosts: webservers # target group defined in inventory
become: true # escalate privileges via sudoTask nei playbook
Ogni task ha un nome descrittivo e chiama un modulo Ansible. Il nome non è obbligatorio ma è una best practice: comparirà nell'output durante l'esecuzione.
tasks:
- name: Update apt cache # task name shown in the output
ansible.builtin.apt: # fully qualified module name (FQCN)
update_cache: true # module parameter
cache_valid_time: 3600 # skip update if cache is freshStati
Il parametro state è il cuore dell'idempotenza: non si dice ad Ansible di installare Nginx, si dichiara che Nginx deve essere presente. Se lo è già, Ansible non fa nulla.
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present # desired state, not an actionLo stesso principio vale per i servizi: state: started significa che il servizio deve essere in esecuzione, enabled: true che deve partire automaticamente al boot.
- name: Ensure Nginx is started and enabled
ansible.builtin.service:
name: nginx
state: started # service must be running
enabled: true # start automatically on boot
Nota Tecnica
Il prefisso ansible.builtin davanti al nome del modulo è il Fully Qualified Collection Name (FQCN). Non è obbligatorio, ma è la best practice raccomandata: evita ambiguità quando il progetto usa moduli di collezioni esterne con lo stesso nome.
Mettendo tutto insieme, il playbook finale è questo:
---
- name: Install and configure Nginx
hosts: webservers
become: true
tasks:
# Update the apt cache before installing packages
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Ensure Nginx is started and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: truePlaybook installazione Nginx
Attenzione a YAML
YAML è sensibile all'indentazione: usare sempre spazi, mai tab. Configurare l'editor con expandtab e indentazione a 2 spazi per i file .yml e .yaml. Un errore di indentazione è spesso la causa di errori misteriosi.
Esegui il playbook
Prima di eseguire su produzione, usare sempre il flag --check per un dry-run: Ansible simula l'esecuzione senza toccare nulla sui server.
$ ansible-playbook -i inventory.ini install_nginx.yml --check
PLAY [Install and configure Nginx] ***************************
TASK [Gathering Facts] ***************************************
ok: [web01]
ok: [web02]
TASK [Update apt cache] *****************************************
changed: [web01]
changed: [web02]
TASK [Install Nginx] *********************************************
changed: [web01]
changed: [web02]
TASK [Ensure Nginx is started and enabled] ***************
ok: [web01]
ok: [web02]Il dry-run mostra cosa cambierebbe senza applicare modifiche. Quando non ci sono errori, si rimuove --check ed esegue il playbook:
$ ansible-playbook -i inventory.ini install_nginx.yml
PLAY [Install and configure Nginx] ***************************
TASK [Gathering Facts] ***************************************
ok: [web01]
ok: [web02]
TASK [Update apt cache] *****************************************
changed: [web01]
changed: [web02]
TASK [Install Nginx] *********************************************
changed: [web01]
changed: [web02]
TASK [Ensure Nginx is started and enabled] ***************
ok: [web01]
ok: [web02]
PLAY RECAP **************************************************
web01 : ok=3 changed=2 unreachable=0 failed=0
web02 : ok=3 changed=2 unreachable=0 failed=0Leggi il PLAY RECAP come un semaforo
- ok significa che il task ha verificato lo stato e non ha dovuto fare nulla: il server era già nello stato desiderato
- changed significa che qualcosa è stato modificato
- failed e unreachable a zero: tutto è andato come previsto
Idempotenza: esegui mille volte, stesso risultato
Eseguendo lo stesso playbook senza aver modificato nulla nel frattempo, il risultato cambia:
Prima esecuzione
$ ansible-playbook -i inventory.ini install_nginx.yml
PLAY [Install and configure Nginx] ***************************
TASK [Gathering Facts] ***************************************
ok: [web01]
ok: [web02]
TASK [Update apt cache] *****************************************
changed: [web01]
changed: [web02]
TASK [Install Nginx] *********************************************
changed: [web01]
changed: [web02]
TASK [Ensure Nginx is started and enabled] ***************
ok: [web01]
ok: [web02]
PLAY RECAP **************************************************
web01 : ok=3 changed=2 unreachable=0 failed=0
web02 : ok=3 changed=2 unreachable=0 failed=0Seconda esecuzione: idempotenza
$ ansible-playbook -i inventory.ini install_nginx.yml
PLAY [Install and configure Nginx] ***************************
TASK [Gathering Facts] ***************************************
ok: [web01]
ok: [web02]
TASK [Update apt cache] *****************************************
ok: [web01]
ok: [web02]
TASK [Install Nginx] *********************************************
ok: [web01]
ok: [web02]
TASK [Ensure Nginx is started and enabled] ***************
ok: [web01]
ok: [web02]
PLAY RECAP **************************************************
web01 : ok=3 changed=0 unreachable=0 failed=0
web02 : ok=3 changed=0 unreachable=0 failed=0Alla seconda esecuzione: zero changed. Ansible ha verificato che Nginx era già installato e avviato, e non ha fatto nulla. Questo è l'idempotenza.
In pratica: è possibile eseguire un playbook quante volte si vuole senza timore di rompere qualcosa. È il fondamento della configuration drift prevention: se un sysadmin modifica manualmente un server, la prossima esecuzione del playbook riporta tutto allo stato desiderato.
Struttura il progetto come un professionista
Organizzare i file correttamente fin dall'inizio evita refactoring dolorosi in seguito. Una struttura di riferimento:
nginx-project/
├── ansible.cfg # project-level config
├── inventory.ini # managed hosts
├── install_nginx.yml # main playbook
├── group_vars/
│ └── webservers.yml # group variables
├── host_vars/
│ └── web01.yml # host-specific variables
└── roles/ # next post...Il file ansible.cfg nella root del progetto permette di non dover specificare l'inventory ad ogni esecuzione e di configurare comportamenti di default.
[defaults]
inventory = inventory.ini
remote_user = ubuntu
host_key_checking = False ; disable only in dev
stdout_callback = yaml ; human-readable output
[privilege_escalation]
become = True
become_method = sudoCon questa configurazione l'esecuzione si semplifica da:
ansible-playbook -i inventory.ini install_nginx.yml
a semplicemente:
ansible-playbook install_nginx.yml
Conclusione
Scrivere un playbook Ansible non richiede molto: poche righe di YAML bastano per installare e avviare Nginx su due server in parallelo, senza toccarli uno per uno.
Ma la cosa più importante emersa in questo post non è il playbook in sé; è quello che succede alla seconda esecuzione: nessun changed, zero modifiche. Ansible verifica che lo stato desiderato sia già raggiunto e non fa nulla. Questa è l'idempotenza, ed è il motivo per cui è possibile eseguire un playbook su 200 server in produzione senza paura.