SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-21

Ansible: limiti di --check e --diff nel dry run

Scopri cosa dimostrano davvero --check e --diff di Ansible e perché i moduli senza supporto possono dare una risposta errata prima dell’esecuzione reale.

Cosa fa la modalità check di Ansible

La modalità check di Ansible è un’esecuzione a secco: ansible-playbook --check si connette a ogni host del play, chiede a ciascun modulo se lo stato attuale corrisponde già allo stato richiesto e segnala le modifiche previste senza scrivere nulla. Aggiungendo --diff, stampa anche il contenuto precedente e quello successivo dei file che verrebbero modificati. Insieme, queste opzioni rispondono alla domanda da porsi prima di ogni esecuzione reale: cosa sta per cambiare su questi server?

La modalità check non simula il playbook. Non esiste alcun modello del server. A ogni modulo viene semplicemente chiesto di leggere invece di scrivere. Un modulo in grado di rispondere in sola lettura segnala changed e prosegue. Un modulo che non è in grado di rispondere non esegue alcuna operazione e non restituisce alcun risultato. La documentazione di Ansible lo riassume in una riga: "I moduli che non supportano la modalità check non restituiscono risultati e non eseguono operazioni." È questa lacuna a produrre risposte errate durante un’esecuzione a secco; per questo gran parte di questa guida è dedicata a questo limite.

Esegui la simulazione: --check e --diff

ansible-playbook -i inventory.ini site.yml --check --diff --limit web1

-C e -D sono le forme brevi dei due flag. --limit è intenzionale. Il diff di un host è leggibile. Il diff di venti host richiede di scorrere molte righe.

Quattro parole descrivono l'intero report.

  • ok: [web1] indica che il modulo ha verificato lo stato e che questo corrisponde già a quello richiesto. Non verrebbe modificato nulla.
  • changed: [web1] indica che il modulo avrebbe scritto qualcosa. Con --diff, le righe precedenti mostrano che cosa verrebbe scritto.
  • skipping: [web1] indica che l'attività non è stata valutata. Un when era falso oppure il modulo non può essere eseguito in modalità check.
  • fatal: [web1] indica che l'attività non è riuscita durante la verifica. Leggi il messaggio prima di concludere che il playbook è errato.

--diff stampa un diff unificato per i moduli che gestiscono file. Le righe rimosse sono contrassegnate con - e quelle aggiunte con +. Le righe dell'intestazione iniziano con --- before e +++ after e indicano il percorso di destinazione. I moduli che non scrivono file mostrano il proprio stato precedente e successivo, quindi ansible.builtin.user visualizza gli attributi che verrebbero modificati anziché il contenuto del file.

Attiva il diff in modo permanente in ansible.cfg, così non dimenticherai mai il flag:

[diff]
always = true
context = 5

Prima della modalità check conviene eseguire due controlli più rapidi. ansible-playbook site.yml --syntax-check analizza il formato YAML e la struttura del play senza contattare alcun host. ansible-playbook site.yml --list-tasks stampa le attività che verrebbero eseguite. In questo modo puoi verificare che un ruolo che pensavi avesse un tag in realtà non lo abbia. Nessuno dei due comandi stabilisce connessioni, quindi entrambi vengono eseguiti immediatamente.

La modalità check, invece, stabilisce connessioni. Apre una sessione SSH verso ogni host incluso nel pattern e raccoglie i facts. Di conseguenza, un host non disponibile fa fallire la simulazione. Questo è già un segnale utile. È anche il motivo per cui conviene decidere come un playbook deve gestire gli host irraggiungibili prima di inserire una simulazione nella pipeline CI.

Perché la modalità di controllo non funziona su un server appena installato

Questo play è corretto. Eseguilo con --check su un server che non dispone ancora di nginx: la maggior parte delle attività avrà esito negativo.

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Write the site config
  ansible.builtin.template:
    src: site.conf.j2
    dest: /etc/nginx/conf.d/site.conf

- name: Start and enable nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

L'attività apt restituisce changed, e il risultato è corretto: il pacchetto non è presente, quindi un'esecuzione effettiva lo installerebbe. La modalità di controllo non lo ha installato. L'attività template ha quindi esito negativo, perché su questo host non esiste /etc/nginx/conf.d/ e nessuna attività lo ha creato. Anche l'attività service ha esito negativo, perché non esiste alcuna unità nginx da interrogare. Nessuno dei due errori indica un problema nel playbook. L'esecuzione a secco non disponeva dello stato necessario. È questo il significato dell'avvertenza nella documentazione: la modalità di controllo non può produrre un risultato utile per un'attività il cui input dipende dalla modifica apportata da un'attività precedente.

La formulazione più corretta della regola è quindi questa: la modalità di controllo è accurata su un host già portato allo stato desiderato dal playbook, ma produce molti errori su un host appena installato. Un'esecuzione --check in cui ogni attività restituisce ok descrive realmente lo stato di un host già configurato, perché significa che non verrebbe modificato nulla. Su un host completamente nuovo, --check indica soprattutto che l'host è nuovo. Quando scrivi il tuo primo playbook Ansible per un VPS, aspettati che la prima esecuzione a secco produca una lunga serie di errori e valuta il playbook in base alla seconda.

Perché le attività command e shell vengono saltate in modalità check

ansible.builtin.command e ansible.builtin.shell non sanno che cosa esegue il comando. Non esiste un modo in sola lettura per eseguire un binario arbitrario, quindi in modalità check il modulo rifiuta di eseguirlo. Il risultato dell'attività contiene skipped: true e il messaggio Command would have run if not in check mode, mentre l'output mostra skipping: [web1].

La documentazione del modulo definisce il supporto alla modalità check come «parziale» e indica come soluzione creates e removes. Assegnando all'attività un percorso creates, la modalità check può almeno valutare il test sul file:

- name: Extract the release bundle
  ansible.builtin.command: /usr/bin/tar xf /tmp/app.tar.gz -C /opt/app
  args:
    creates: /opt/app/bin/app

Se /opt/app/bin/app esiste già, la modalità check restituisce Would not run command since '/opt/app/bin/app' exists, che è una risposta effettiva. Se il percorso non esiste, restituisce Command would have run if not in check mode, che è anch'essa una risposta effettiva. Senza creates, questa attività resta uno spazio vuoto nella simulazione.

L'effetto a cascata è peggiore dello spazio vuoto. Un'attività saltata produce comunque un risultato, ma si tratta di un risultato di skip privo della chiave stdout. La condizione dell'attività successiva fallisce durante la valutazione, con un errore simile a 'dict object' has no attribute 'stdout'. Il playbook funziona durante l'esecuzione reale e si interrompe nella simulazione: è il comportamento più difficile da diagnosticare dell'intera funzionalità.

check_mode: false, e dove deve essere usato

check_mode: false in un task significa «eseguilo realmente, anche in --check». È la soluzione al problema dei comandi saltati ed è sicuro solo per un task che esegue operazioni di lettura.

- name: Read the installed app version
  ansible.builtin.command: /usr/local/bin/app --version
  register: app_version
  check_mode: false
  changed_when: false

Questo task è coerente in entrambe le modalità. Legge una versione e non scrive mai; changed_when: false impedisce che segnali una modifica che non ha effettuato, mentre check_mode: false fa sì che app_version.stdout esista durante una simulazione, così le condizioni basate su di esso continuano a essere valutate.

Leggete la parola chiave alla lettera prima di copiarla altrove. Un task con check_mode: false scrive sui server durante ansible-playbook --check. Se la inserite in un task apt o in un task template per rendere più ordinata l’anteprima, la simulazione non è più una simulazione. Quando non è possibile rendere sicuro un task di scrittura, usate invece una condizione di protezione:

- name: Apply the database migration
  ansible.builtin.command: /usr/local/bin/app migrate --apply
  when: not ansible_check_mode

ansible_check_mode è una variabile speciale che Ansible imposta su true durante un’esecuzione in modalità di controllo. Esiste anche la parola chiave inversa. check_mode: true forza sempre un task in modalità di controllo, anche durante un’esecuzione reale, trasformandolo in un rilevatore di differenze: registrate il risultato e un report changed indica che l’host non corrisponde più allo stato richiesto dal task.

Perché un task segnala una modifica a ogni esecuzione

Esegui il playbook due volte consecutivamente, senza modificare nulla nel frattempo. Al secondo avvio, ogni task dovrebbe segnalare ok. Un task che continua a segnalare changed indica una di due condizioni: il modulo non riesce a rilevare lo stato che gestisce oppure l’input fornito non è stabile. Entrambe le condizioni sono risolvibili e non devono essere semplicemente nascoste.

  • command e shell senza creates, removes o changed_when segnalano changed a ogni esecuzione, perché il modulo non ha modo di sapere se è successo qualcosa. Aggiungi creates oppure imposta changed_when su una stringa presente nell’output.
  • ansible.builtin.file con state: touch segnala changed a ogni esecuzione per progettazione, perché la modifica di un file ne aggiorna i timestamp. Usa state: file se devi soltanto impostare il proprietario o i permessi.
  • Un template il cui output renderizzato cambia riscrive il file a ogni esecuzione. Un timestamp generato da ansible_date_time, una chiamata a now() o una password generata ogni volta producono byte diversi; quindi il modulo segnala correttamente una modifica. Rimuovi il valore variabile dal template.
  • ansible.builtin.user con password: "{{ pw | password_hash('sha512') }}" cambia a ogni esecuzione, perché password_hash genera un salt casuale ogni volta che viene chiamato; di conseguenza, l’hash risultante non coincide mai con quello già presente in /etc/shadow. Passa un salt esplicito derivato da un valore stabile.
  • state: latest su un modulo per i pacchetti segnala changed quando è disponibile un aggiornamento. In questo caso il risultato è corretto. Per questo state: latest produce un playbook con un risultato non prevedibile. Usa state: present e aggiorna i pacchetti intenzionalmente.
  • ansible.builtin.unarchive indirizzato a un URL senza creates scarica ed estrae nuovamente il contenuto. Fornisci un percorso creates.

--diff è il modo più rapido per distinguere questi casi. Se un task segnala changed e il diff mostra byte diversi, l’input non è stabile. Se segnala changed e il diff non mostra alcuna differenza, il modulo non riesce a esprimere ciò che ha modificato. Di solito si tratta di un task command o di una scrittura che modifica soltanto i metadati, ad esempio un timestamp.

Non usare changed_when: false per nascondere un task rumoroso. Questa opzione sopprime il report, quindi notify non viene mai attivato e l’handler che riavvia il servizio non viene mai eseguito. Correggi invece il task.

Ridurre l'impatto: --limit, --tags e --step

La modalità di controllo mostra quali modifiche verrebbero applicate. Questi flag determinano quante macchine vengono coinvolte contemporaneamente.

--limit limita il play a un sottoinsieme dell'inventario. Accetta gli stessi pattern di hosts:, quindi funzionano sia --limit web1 sia --limit 'webservers:!web3'. Racchiudi il pattern tra virgolette. Un ! non racchiuso tra virgolette in una sessione bash interattiva attiva l'espansione della cronologia sul punto esclamativo e la shell modifica il comando prima che Ansible possa leggerlo.

Verifica il pattern prima di considerarlo affidabile. ansible-playbook site.yml --limit 'webservers:!web3' --list-hosts stampa gli host corrispondenti e termina senza connettersi a nessuno di essi. Un pattern che non corrisponde ad alcun host è sicuro, perché Ansible non utilizza automaticamente l'intero inventario. Stampa un avviso indicando che non è stato possibile trovare corrispondenze per il pattern degli host, quindi termina con un errore che indica che gli host e --limit non corrispondono ad alcun host. Sapere come il file di inventario definisce questi gruppi rende prevedibile il comportamento del pattern.

--tags deploy esegue solo le attività contrassegnate, mentre --skip-tags packages esegue tutte le altre. --list-tags stampa i tag disponibili. I tag diventano utili quando un play supera il punto in cui si è disposti a eseguirlo interamente. Questo è anche uno dei motivi per suddividere un playbook lungo in ruoli.

--start-at-task "Write the site config" riprende un'esecuzione non riuscita da un'attività specificata. Usalo per il ripristino, tenendo presente il costo: tutto ciò che precede quell'attività viene ignorato, comprese le attività che impostano i fact o registrano le variabili utilizzate dalle attività successive.

--step richiede una conferma prima di ogni attività e attende una risposta yes, no o continue. È lento, ma è lo strumento corretto la prima volta che esegui un'operazione distruttiva, perché consente di fermarsi tra due attività anziché dopo venti.

Distribuire la modifica in modo seriale

Per impostazione predefinita, Ansible esegue un'attività su ogni host del play prima di avviare l'attività successiva. È rapido, ma significa che un'attività difettosa raggiunge l'intera flotta nello stesso secondo. Quando avete letto l'errore e premuto Ctrl-C, la modifica è già stata applicata ovunque.

serial suddivide il play in batch. L'intero play viene eseguito sul primo batch, quindi su quello successivo.

- name: Roll out the web tier
  hosts: webservers
  serial: [1, 5, "30%"]
  max_fail_percentage: 0
  tasks:
    - name: Deploy the release
      ansible.builtin.include_role:
        name: webapp

Il primo batch contiene un host. Se supera il controllo, il secondo batch contiene cinque host e ogni batch successivo contiene il 30 percento degli host del play. max_fail_percentage: 0 termina il play non appena un host del batch non riesce, quindi una release difettosa si ferma su una sola macchina. any_errors_fatal: true è la versione più drastica: termina il play per tutti al primo errore di un host.

Eseguire prima il play su un solo host non è paranoia. Il motivo è concreto. I gruppi dell'inventory cambiano nel tempo. Un server aggiunto sei mesi dopo gli altri potrebbe usare una release diversa della distribuzione, avere un servizio installato manualmente da qualcuno oppure una disposizione diversa dei dischi. Il playbook può essere corretto per il gruppo e non esserlo per quell'unico host; nessuna simulazione su un host allineato alla configurazione attesa lo evidenzierà. Gestire una flotta di server Linux significa in gran parte individuare l'host anomalo prima che lo faccia la modifica.

Ordine di esecuzione

  1. ansible-playbook site.yml --syntax-check rileva gli errori YAML e di struttura senza usare la rete.
  2. ansible-playbook site.yml --limit web1 --list-hosts verifica che il pattern corrisponda a ciò che deve corrispondere.
  3. ansible-playbook site.yml --limit web1 --check --diff esegue una simulazione. Leggi il diff.
  4. ansible-playbook site.yml --limit web1 --diff applica la modifica a quell'host.
  5. Esegui nuovamente il passaggio 4. Tutto dovrebbe restituire ok. Qualsiasi elemento che restituisce ancora changed deve essere corretto prima di applicare la modifica al resto dell'infrastruttura.
  6. A questo punto, ansible-playbook site.yml --check --diff sull'intero inventory restituisce un risultato significativo, perché gli host già allineati non producono output e ciò che rimane rappresenta il delta effettivo.

Una nota sul passaggio 3. --diff stampa il contenuto dei file nel terminale e nel log del job CI. Di conseguenza, se un template genera la password di un database, quella password viene scritta nel log. Imposta diff: false su quel task per sopprimerne l'output oppure usa no_log: true per nascondere l'intero risultato. Mantieni il valore in un file Ansible Vault crittografato, non nel repository.

FAQ

ansible-playbook --check modifica qualcosa sul server?

No, con un'unica eccezione che puoi controllare. In modalità check, ogni modulo deve segnalare le modifiche invece di applicarle. I moduli che non supportano questa modalità non segnalano nulla e non eseguono operazioni. L'eccezione è la parola chiave dell'attività check_mode: false, che forza l'esecuzione effettiva di quella singola attività anche durante un'esecuzione --check. Prima di affidarti a una simulazione, cerca check_mode: false nei playbook e nei ruoli. Verifica che ogni corrispondenza sia un'attività che legge soltanto lo stato.

Qual è la differenza tra --check e --diff?

--check stabilisce se qualcosa viene eseguito realmente. --diff stabilisce il livello di dettaglio visualizzato. --check da solo indica che un file verrebbe modificato. --diff da solo applica la modifica e mostra le righe modificate. Usali insieme per ottenere una simulazione leggibile. Mantieni --diff attivo anche nelle esecuzioni reali, impostando always = true in [diff] all'interno di ansible.cfg.

Perché la mia attività Ansible segnala una modifica a ogni esecuzione?

Perché il modulo non riesce a rilevare lo stato che gestisce oppure perché il valore fornito è diverso ogni volta. command e shell segnalano sempre changed, a meno che tu non aggiunga creates o changed_when. file con state: touch cambia per progettazione. Un template che genera un timestamp o una password appena generata produce byte diversi a ogni esecuzione. Di conseguenza, il file viene realmente riscritto. Esegui il playbook due volte consecutive. Qualsiasi elemento che risulta ancora changed al secondo passaggio identifica l'attività da correggere.

Perché le attività command e shell vengono saltate durante una simulazione?

Perché non esiste un modo di sola lettura per eseguire un comando arbitrario. In modalità check, il modulo command imposta skipped: true con il messaggio Command would have run if not in check mode. Aggiungi creates o removes, così la modalità check può valutare invece il test sul file. Per un'attività che legge soltanto lo stato, imposta check_mode: false insieme a changed_when: false. In questo modo il risultato registrato esiste anche durante la simulazione e le condizioni che dipendono da quel risultato continuano a funzionare.

Perché la modalità check non riesce su un server nuovo ma funziona su uno esistente?

Perché la modalità check non crea lo stato da cui dipendono le attività successive. Una simulazione su un host senza nginx segnala l'installazione come changed, quindi non riesce nell'attività che scrive in /etc/nginx/conf.d/, perché quella directory non è mai stata creata. Questo comportamento è previsto. La modalità check rileva la deriva su host che il playbook ha già portato allo stato previsto. Non può convalidare una prima esecuzione. Su un host nuovo, applica il playbook a una macchina e analizza invece la seconda esecuzione.

#ansible#check-mode#idempotency#automation#safety