Massimizzare le prestazioni di Ansible con ControlPersist e Pipelining

Aumenta significativamente le prestazioni dei tuoi playbook Ansible abilitando il riutilizzo delle connessioni SSH con ControlPersist e semplificando l'esecuzione dei moduli tramite Pipelining. Questa guida fornisce approfondimenti essenziali e configurazioni pratiche per ridurre i tempi di esecuzione, specialmente in ambienti su larga scala. Impara come ottimizzare il tuo `ansible.cfg` per un'automazione IT più veloce ed efficiente.

Massimizzare le prestazioni di Ansible con ControlPersist e Pipelining

Le esecuzioni lente di Ansible spesso sembrano misteriose finché non osservi cosa succede sulla rete. Un playbook con venti piccole attività su cento host può passare una quantità sorprendente di tempo ad aprire sessioni SSH, copiare file temporanei dei moduli, eseguire Python, raccogliere output e chiudere connessioni. Il lavoro sull'host remoto può richiedere millisecondi, ma l'overhead di connessione si ripete più e più volte.

Due impostazioni spesso aiutano: il riutilizzo della connessione SSH tramite ControlPersist e il pipelining di Ansible. Non sono interruttori magici e non risolveranno mirror di pacchetti lenti, database sovraccarichi o attività che svolgono lavori pesanti. Riducono l'overhead di comunicazione evitabile, che è esattamente dove molti playbook con piccole attività perdono tempo.

Prima, misura il dolore attuale

Prima di modificare la configurazione, esegui il playbook una volta con il timing abilitato:

ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml

Se quel callback non è installato, usa il semplice output di timing integrato dal tuo sistema CI o avvolgi il comando con time. L'obiettivo non è un benchmark perfetto. Vuoi una baseline e un'idea se le attività lente sono lavoro effettivo o piccole attività ripetute su molti host.

Un utile smoke test è un ping ad-hoc su un inventario rappresentativo:

time ansible all -m ping

Eseguilo due volte. Se la seconda esecuzione è molto più veloce dopo aver configurato il riutilizzo della connessione, hai confermato che il costo di configurazione SSH era parte del problema.

Cosa cambia ControlPersist

ControlPersist è una funzionalità di OpenSSH. Mantiene una connessione SSH master aperta per un periodo di tempo in modo che i successivi comandi SSH allo stesso host, utente e porta possano riutilizzarla. Ansible usa comunemente SSH per ogni attività, quindi il multiplexing della connessione rimuove le strette di mano ripetute.

Un pratico ansible.cfg a livello di progetto appare così:

[defaults]
forks = 20

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r

Crea la directory del socket con permessi privati:

mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp

ControlMaster=auto dice a SSH di usare una connessione master esistente quando disponibile e di crearne una quando non lo è. ControlPersist=600s mantiene il master per dieci minuti dopo che l'ultima sessione termina. Quel valore non è sacro. Per brevi lavori CI, pochi minuti possono essere sufficienti. Per un operatore che esegue ripetutamente playbook durante una finestra di manutenzione, dieci o quindici minuti possono essere comodi.

ControlPath è più importante di quanto ci si aspetti. I percorsi dei socket Unix hanno limiti di lunghezza su molti sistemi. Un nome host lungo nell'inventario all'interno di un percorso di workspace profondo può rompere il multiplexing con un errore confuso. Mantenere il percorso breve, ad esempio sotto ~/.ansible/cp, evita quella classe di errori.

Le versioni recenti di Ansible abilitano già il multiplexing SSH per impostazione predefinita in molte configurazioni normali, ma impostazioni esplicite in un ansible.cfg di progetto rendono il comportamento più facile da controllare. Verifica la configurazione attiva con:

ansible-config dump --only-changed

Cosa cambia il pipelining

Senza pipelining, Ansible spesso copia un modulo in una directory temporanea sull'host remoto, lo esegue, poi lo pulisce. Con il pipelining abilitato, Ansible può passare il codice del modulo attraverso la connessione SSH invece di scrivere tanti file temporanei. Questo risparmia round trip e lavoro sul filesystem remoto.

Abilitalo nello stesso file:

[ssh_connection]
pipelining = True

O testalo per una singola esecuzione:

ANSIBLE_PIPELINING=True ansible-playbook site.yml

L'impostazione è più evidente quando il playbook ha molti piccoli moduli: file, lineinfile, template, user, service e brevi attività di comando. Conta meno quando un'attività passa la maggior parte del tempo a installare pacchetti, compilare software, trasferire grandi artefatti o aspettare un servizio esterno.

La trappola di sudo requiretty

Il classico problema del pipelining è requiretty in sudoers. Alcune vecchie configurazioni Linux aziendali richiedevano un TTY per sudo. Il pipelining non funziona bene con quel requisito perché Ansible cerca di trasmettere lavoro attraverso SSH in modo non interattivo.

Controlla attentamente sudoers. Non modificare /etc/sudoers con un editor normale; usa visudo:

sudo visudo

Se vedi una riga globale come questa:

Defaults requiretty

Potresti doverla rimuovere o sovrascriverla per l'utente di automazione Ansible:

Defaults:ansible !requiretty

Fai questa modifica solo se corrisponde alla politica di sicurezza della tua organizzazione. Su molte distribuzioni moderne, requiretty non è abilitato per impostazione predefinita.

Una configurazione combinata più sicura

Per molti team, questo è un punto di partenza ragionevole:

[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True

forks controlla il parallelismo. È correlato alle prestazioni, ma non è la stessa ottimizzazione. Aumentare forks da 5 a 20 può aiutare se il tuo nodo di controllo e la rete possono gestirlo. Aumentarlo troppo può sovraccaricare host bastion, repository di pacchetti o i nodi gestiti stessi.

Il caching dei fatti aiuta un diverso tipo di lentezza. Se i tuoi playbook raccolgono fatti a ogni esecuzione e i fatti non devono essere freschi ogni minuto, il caching può rimuovere il lavoro di configurazione ripetuto. Usalo deliberatamente; fatti obsoleti possono essere confusi se ti affidi a dati correnti di memoria, disco o interfaccia.

Come testare senza ingannare te stesso

Testa su un sottoinsieme che assomiglia alla produzione. Cinque VM di test inattive sulla stessa sottorete non ti diranno molto su cento host misti dietro un bastion.

Esegui lo stesso playbook prima e dopo la modifica. Pulisci i socket di controllo esistenti tra le esecuzioni di test se hai bisogno di un confronto a freddo:

rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web

Poi esegui un confronto a caldo:

time ansible-playbook site.yml --limit web

Cerca meno secondi spesi in piccole attività ripetute. Osserva anche le modalità di errore. Se le attività che usano become iniziano a fallire, indaga sulla configurazione di sudo prima di incolpare il pipelining stesso.

Quando queste impostazioni non aiuteranno molto

ControlPersist e pipelining non rendono veloce il lavoro remoto lento. Se apt update aspetta un mirror, il riutilizzo della connessione non ti salverà. Se un riavvio del servizio aspetta trenta secondi perché l'app drena le connessioni, il pipelining è irrilevante. Se il tuo playbook copia un artefatto da 2 GB su ogni host, concentrati sulla distribuzione degli artefatti, caching o repository di pacchetti locali.

Non sostituiscono nemmeno un design idempotente del playbook. Un playbook che esegue comandi shell incondizionatamente sprecherà comunque tempo. Usa moduli che possono rilevare lo stato corrente, aggiungi creates o removes ai comandi quando appropriato ed evita di raccogliere fatti quando il play non li usa.

L'approccio pratico è semplice: abilita ControlPersist con un percorso di controllo breve e privato; testa il pipelining con la tua politica sudo; ottimizza forks gradualmente; e misura con lo stesso inventario e carico di lavoro ogni volta. In molti ambienti Ansible reali, queste modifiche trasformano un'esecuzione lenta in una tollerabile perché rimuovono l'overhead ripetuto anziché nasconderlo.

Host bastion e jump host

Molti inventari non si connettono direttamente ai nodi gestiti. Passano attraverso un bastion. ControlPersist aiuta comunque, ma devi pensare a entrambe le connessioni: nodo di controllo a bastion e bastion a target.

Una variabile comune dell'inventario appare così:

[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12

[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'

Se ogni attività costruisce ripetutamente una connessione jump, il bastion diventa parte dell'overhead. Mantieni il percorso di controllo breve e privato e osserva i limiti di connessione SSH del bastion. Un playbook che funziona bene su dieci host può sovraccaricare un piccolo bastion quando forks è aumentato a cinquanta.

Per client SSH più vecchi, potresti vedere ProxyCommand invece di ProxyJump:

ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'

Testa una connessione SSH semplice prima di incolpare Ansible:

ssh -o ProxyJump=bastion.example.com [email protected] hostname

Se è lenta, anche Ansible sarà lento.

Pipelining e become nei playbook reali

Il vecchio consiglio che il pipelining non funziona con l'escalation dei privilegi è troppo ampio. Il pipelining può funzionare con become; il blocco comune è sudo che richiede un TTY o una politica che interferisce con l'esecuzione non interattiva. L'unica risposta affidabile è testare con le stesse impostazioni become che usano i tuoi playbook.

Un piccolo play di test è sufficiente:

- hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Conferma che il comando privilegiato funzioni
      ansible.builtin.command: id
      changed_when: false

Eseguilo con il pipelining abilitato. Se fallisce, controlla sudoers, i prompt di autenticazione e se l'utente di automazione può eseguire i comandi richiesti senza un prompt per la password. I prompt per la password in grandi esecuzioni di automazione sono fragili; rendono anche rumorose le misurazioni dei tempi.

Ottimizza forks rispettando le dipendenze

Aumentare forks è allettante perché produce un cambiamento visibile immediato. Può anche creare un nuovo collo di bottiglia. Se un play aggiorna le cache dei pacchetti su ogni host contemporaneamente, un alto numero di fork può punire il mirror dei pacchetti. Se ogni host riavvia e si riconnette allo stesso database contemporaneamente, il database vede l'esplosione.

Un approccio misurato è migliore:

[defaults]
forks = 10

Esegui il play. Prova 20. Poi 30. Osserva la CPU del nodo di controllo, il numero di processi SSH, il carico del bastion, la saturazione della rete, il repository di pacchetti e il servizio che viene modificato. L'impostazione più veloce non è sempre quella con il parallelismo più alto. Per distribuzioni di applicazioni rolling, potresti anche volere serial nel playbook per proteggere la disponibilità:

- hosts: web
  serial: 10

forks controlla quanto Ansible può fare contemporaneamente. serial controlla quanti host il play dovrebbe elaborare alla volta. Risolvono problemi diversi.

Pulisci i socket di controllo obsoleti

I socket di controllo di solito si puliscono da soli, ma i laptop vanno in sospensione, i lavori CI vengono uccisi e i percorsi di rete cambiano. Se SSH inizia a segnalare errori di multiplexing, rimuovi i socket obsoleti:

rm -f ~/.ansible/cp/*

Questo è sicuro quando nessuna esecuzione Ansible è attiva per quei socket. In esecutori di automazione condivisi, evita di posizionare i socket di controllo in una directory scrivibile condivisa. Ogni utente di automazione dovrebbe avere il proprio percorso privato.

Il design dell'inventario può cancellare i guadagni di prestazioni

L'ottimizzazione della connessione aiuta, ma il design dell'inventario può comunque rallentare tutto. Script di inventario dinamico che chiamano API cloud lentamente, variabili di gruppo che eseguono lookup costosi e playbook che raccolgono fatti per host che non toccano mai possono aggiungere ritardo prima ancora che SSH inizi.

Se un'esecuzione si ferma prima della prima attività, profila il caricamento dell'inventario. Metti in cache l'inventario dinamico dove il plugin lo supporta. Evita lookup di variabili costosi al momento del parsing. Mantieni i pattern degli host stretti:

ansible-playbook site.yml --limit web:&prod

Questo comando prende di mira gli host sia in web che in prod. Eseguire un play ampio su all e poi saltare la maggior parte delle attività con condizioni when spreca tempo e rende l'output più difficile da leggere.

Preferisci attività meno numerose e più chiare quando lo stato è correlato

La leggibilità di Ansible è importante, ma attività eccessivamente piccole possono rendere l'overhead di connessione più visibile. Se imposti dieci righe correlate in un file di configurazione con dieci attività lineinfile separate, paghi l'overhead delle attività dieci volte e rendi più difficile il rollback. Un template può essere più chiaro e veloce:

- name: Render della configurazione dell'app
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'
  notify: riavvia app

Questo non è un argomento per script shell giganti all'interno di Ansible. È un promemoria per modellare lo stato desiderato al livello giusto. Un template per un file di configurazione è spesso meglio di molte micro-modifiche.

Evita modifiche globali premature

Metti le impostazioni di prestazioni nel ansible.cfg del progetto quando possibile. Modificare /etc/ansible/ansible.cfg su un nodo di controllo condiviso può sorprendere altri team. Un file a livello di progetto fa sì che il comportamento viaggi con il repository e mantiene CI, laptop e esecutori di automazione più vicini alla stessa configurazione.

Conferma quale file di configurazione sta usando Ansible:

ansible --version

L'output include il percorso della configurazione attiva. Questo coglie un errore comune: modificare un ansible.cfg mentre Ansible ne sta leggendo un altro.

Sappi quando smettere di ottimizzare Ansible

Se l'overhead di connessione non è più una parte importante del runtime, smetti di ottimizzare SSH e guarda al lavoro. Aggiornamenti della cache dei pacchetti, pull di container, migrazioni di database, controlli di salute dei servizi e chiamate API cloud spesso dominano i playbook maturi. A quel punto, l'ottimizzazione migliore potrebbe essere un mirror di pacchetti locale, caching degli artefatti, batch di distribuzione più piccoli o spostare il provisioning una tantum fuori da ogni esecuzione di deploy.