Migliori Pratiche per Ottimizzare Deployments Ansible su Larga Scala

Modi pratici per accelerare grandi esecuzioni Ansible con forks, plugin di strategia, caching dei fatti, riutilizzo SSH e una migliore progettazione dei playbook.

Migliori Pratiche per Ottimizzare Deployments Ansible su Larga Scala

I grandi deployment Ansible di solito diventano lenti per ragioni semplici: troppe strette di mano SSH, troppa raccolta di fatti, un controller con troppo poca CPU o un playbook che fa aspettare ogni host per il più lento. La soluzione raramente è una singola impostazione. Ottieni il miglior risultato riducendo il sovraccarico di connessione, ottimizzando la concorrenza e scrivendo play che fanno meno lavoro per host.

Non definirei "larga scala" con un numero rigoroso di host. Un inventario di 300 host può sembrare grande se ogni task installa pacchetti su collegamenti lenti. Un inventario di 3.000 host può essere gestibile se il controller è dimensionato bene e i playbook sono snelli. Tratta i numeri qui sotto come punti di partenza, poi misura con il tuo inventario e i tuoi moduli.


Ottimizza il Parallelismo Prima di Incolpare Ansible

Il parallelismo è di solito la prima leva da testare perché Ansible passa molto tempo ad aspettare gli host remoti. L'obiettivo non è "il maggior numero di forks vince". L'obiettivo è una concorrenza sufficiente a mantenere il controller occupato senza sopraffare SSH, l'elevazione dei privilegi, i repository di pacchetti o i target stessi.

Controllare la Concorrenza con forks

Il parametro forks definisce il numero di processi worker paralleli che il controller Ansible può generare. Trovare il numero ottimale richiede di bilanciare le risorse del controller (CPU e memoria) con i limiti di connessione dell'ambiente target.

Imposta forks nel tuo ansible.cfg o tramite riga di comando (-f o --forks).

[defaults]
forks = 100

Inizia più in basso di quanto pensi di aver bisogno. Esegui lo stesso playbook sullo stesso gruppo di host con 25, 50, 100 e 200 forks mentre monitori CPU, memoria, errori SSH e tempo di esecuzione. Se la CPU rimane per lo più inattiva e gli host passano il tempo ad aspettare, aumenta i forks. Se il controller inizia a fare swapping, i processi Python si accumulano o i target rifiutano le connessioni, riduci.

Scegliere il Plugin di Strategia Giusto

La strategia di esecuzione predefinita di Ansible è linear, il che significa che i task devono essere completati su tutti gli host target prima di passare al task successivo nel playbook. Per migliaia di nodi, un singolo host lento può creare un collo di bottiglia per l'intera esecuzione.

Per alcuni grandi deployment, usa la strategia free.

Strategia Free (strategy = free): free permette agli host di procedere indipendentemente attraverso il playbook non appena completano un task, senza aspettare gli host più lenti. Può migliorare il throughput quando i task sono indipendenti. Non usarla ciecamente per deploy rolling, migrazioni condivise o play dove l'ordine dei task su tutta la flotta è importante.

# Definizione di esempio del playbook
---
- hosts: all
  strategy: free
  tasks:
    - name: Assicurati che il servizio sia in esecuzione
      ansible.builtin.service:
        name: httpd
        state: started

Fai Cache dei Fatti Quando Li Riutilizzi

La raccolta dei fatti è utile, ma è facile pagarla ripetutamente. Se i tuoi playbook usano i fatti in diverse esecuzioni, fai cache. Se un play non ha bisogno dei fatti dell'host, disabilita la raccolta per quel play.

Usare Cache Esterne (Redis o Memcached)

Per un singolo controller, la cache su file JSON può essere sufficiente. Per più controller o worker di automazione, usa una cache esterna come Redis o Memcached in modo che ogni worker veda la stessa cache dei fatti.

Configurazione Attuabile in ansible.cfg:

[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 7200 ; Cache dei fatti per 2 ore (in secondi)
fact_caching_prefix = ansible_facts

; Se si usa Redis
fact_caching_connection = localhost:6379:0

Imposta gathering = smart quando i fatti in cache fanno parte del tuo flusso di lavoro. Se hai solo bisogno di una piccola parte dei dati dell'host, usa gather_subset invece di raccogliere tutto.

3. Ottimizzare Connessione e Trasporto

Ridurre il sovraccarico associato alla creazione di connessioni è fondamentale quando si ha a che fare con migliaia di sessioni SSH simultanee.

Pipelining SSH

Il pipelining riduce il numero di round trip SSH che Ansible usa per molte esecuzioni di moduli. Spesso vale la pena abilitarlo, ma testalo con le tue regole di elevazione dei privilegi.

Riutilizzo della Connessione SSH (ControlPersist)

Per target Unix-like, le impostazioni ControlMaster e ControlPersist impediscono ad Ansible di avviare una nuova sessione SSH per ogni singolo task. Mantiene un socket di controllo aperto per una durata specificata, permettendo ai task successivi di usare la connessione esistente.

Configurazione Attuabile in ansible.cfg:

[ssh_connection]
pipelining = True

; Usa un riutilizzo aggressivo della connessione (es., 30 minuti)
ssh_args = -C -o ControlMaster=auto -o ControlPersist=30m -o ServerAliveInterval=15

Il pipelining può entrare in conflitto con le configurazioni sudo che richiedono un TTY. Se hai ancora Defaults requiretty in sudoers, rimuovilo per l'utente di automazione o mantieni il pipelining disabilitato per quegli host.

Ottimizzazione Windows (WinRM)

Quando si targetizzano nodi Windows, ottimizza WinRM separatamente. Kerberos è di solito una scelta migliore per la produzione rispetto all'autenticazione di base, e i limiti del servizio WinRM potrebbero dover essere rivisti se molti job si connettono contemporaneamente.

4. Gestione dell'Inventario per la Scala

I file di inventario statici diventano problematici quando gli host vengono creati e distrutti spesso. L'inventario dinamico non è obbligatorio per ogni grande ambiente, ma è l'impostazione predefinita giusta per flotte cloud, gruppi di autoscaling e infrastrutture basate su CMDB.

Sorgenti di Inventario Dinamico

Utilizza i plugin di inventario per il tuo provider cloud (AWS EC2, Azure, Google Cloud) o sistema CMDB. L'inventario dinamico assicura che Ansible targetizzi solo host attivi con dati aggiornati.

# Esempio: Esecuzione contro un inventario AWS filtrato dinamicamente
ansible-playbook -i aws_ec2.yml site.yml --limit 'tag_Environment_production'

Targeting e Filtraggio Intelligente

Evita di eseguire playbook contro l'intero inventario (hosts: all) a meno che non sia assolutamente necessario. Usa gruppi granulari, limiti (--limit) e tag (--tags) per assicurarti che l'insieme dei target di esecuzione sia minimizzato.

5. Considerazioni Architetturali e Dimensionamento del Controller

Per deployment su larga scala, l'ambiente in cui Ansible viene eseguito deve essere adeguatamente provisionato.

Dimensionamento del Controller

Ansible dipende fortemente dalle risorse del controller, principalmente CPU e RAM, a causa della necessità di generare processi per l'esecuzione parallela.

  • CPU: Più forks di solito significa più lavoro Python sul controller. Monitora il carico medio e la saturazione per core durante le esecuzioni reali dei playbook.
  • RAM: Ogni fork consuma memoria. Grandi template, variabili grandi e plugin di callback loquaci possono aumentare rapidamente l'uso della memoria.
  • I/O di Archiviazione: Una memoria locale veloce aiuta quando il controller scrive molti file temporanei, log, artefatti o voci di cache dei fatti basate su file.

Utilizzare Piattaforme di Automazione

Per team che necessitano di scheduling, RBAC, audit trail e più worker di esecuzione, usa Ansible Automation Platform o AWX invece di una singola sessione shell di lunga durata su un nodo di controllo.

AAP fornisce:

  • Scheduling e Storico dei Job: Logging e auditing centralizzati.
  • Ambienti di Esecuzione: Ambienti runtime coerenti e riproducibili.
  • Clustering e Scaling: Distribuisce l'esecuzione su più nodi worker per gestire enormi esigenze di concorrenza senza sovraccaricare un singolo controller.
  • Gestione delle Credenziali: Gestione sicura dei segreti su larga scala.

6. Progettazione del Playbook per l'Efficienza

Anche con un'infrastruttura ottimizzata, playbook scritti male possono annullare i guadagni di performance.

Minimizzare la Raccolta dei Fatti

Se usi fatti in cache (Sezione 2), disabilita attivamente la raccolta ridondante dei fatti dove possibile:

- hosts: web_servers
  gather_facts: no # Disabilita la raccolta dei fatti per questo play
  tasks:
    # ... esegui solo task che non dipendono dai fatti di sistema raccolti

Usa run_once e delegate_to con Parsimonia

I task che devono essere eseguiti sequenzialmente o centralmente (es., avviare un deployment rolling, aggiornare un load balancer) dovrebbero essere gestiti tramite run_once: true e delegate_to: management_node. Questo evita un parallelismo dispendioso quando solo un host dovrebbe eseguire l'azione.

Preferisci Operazioni in Lotto

Ogni volta che è possibile, usa moduli che gestiscono operazioni in lotto nativamente (es., gestori di pacchetti come apt o yum che accettano una lista di pacchetti) invece di iterare attraverso una grande lista usando un loop o with_items su task package separati.

# Meglio: un task package con una lista
- name: Installa le dipendenze necessarie
  ansible.builtin.package:
    name:
      - nginx
      - python3-pip
      - firewall
    state: present

7. Misura il Playbook, Non Solo il Numero di Host

Quando un'esecuzione Ansible è lenta, aggiungi misurazioni temporali prima di cambiare altre manopole. Il callback integrato profile_tasks è un buon primo passo:

[defaults]
callbacks_enabled = profile_tasks, timer

Esegui il playbook una volta contro un gruppo di host rappresentativo e guarda i task più lenti. Potresti scoprire che la maggior parte del tempo è spesa in una singola installazione di pacchetto, un passaggio di rendering di un template o un comando che aspetta un servizio esterno. In tal caso, aumentare forks crea solo più pressione sullo stesso collo di bottiglia.

Per un test ripetibile, mantieni stabile la fetta di inventario:

ansible-playbook -i inventory site.yml --limit 'web:&production' -f 50
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 100
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 200

Registra il tempo totale di esecuzione, gli host falliti, la CPU del controller, la memoria del controller e qualsiasi errore SSH o sudo lato target. Monitora anche il repository di pacchetti o il server di artefatti durante il test. Un playbook può sembrare un problema di Ansible quando il vero problema è che ogni host sta scaricando lo stesso pacchetto contemporaneamente da un singolo mirror interno sovraccarico.

8. Riduci il Lavoro Prima di Aumentare la Concorrenza

Le grandi esecuzioni Ansible spesso migliorano di più facendo meno che facendolo più velocemente. Alcuni esempi si ripetono spesso:

  • Un task di template renderizza un grande file di configurazione ad ogni esecuzione anche se solo una piccola inclusione è cambiata.
  • Un task shell esegue un comando di scoperta su ogni host anche se il valore è già nell'inventario.
  • Un ruolo installa pacchetti uno alla volta in un loop.
  • Un handler riavvia un servizio dopo diversi cambi di template non correlati quando un singolo ricaricamento sarebbe sufficiente.

Usa l'idempotenza dei moduli invece di shell dove possibile. Un comando shell che riporta sempre "changed" può attivare handler su centinaia di host e trasformare un controllo innocuo in un riavvio rolling. Se devi usare command o shell, imposta changed_when e creates o removes con attenzione.

- name: Inizializza la directory dell'applicazione una volta
  ansible.builtin.command: /usr/local/bin/app-init /srv/app
  args:
    creates: /srv/app/.initialized

Questa piccola protezione previene il lavoro ripetuto ed evita falsi rapporti di cambiamento.

9. Usa Batch per il Controllo del Rischio

Le performance non sono l'unica preoccupazione su larga scala. A volte il playbook più veloce è operativamente pericoloso. Per flotte di servizi, usa serial per controllare il raggio di esplosione:

- hosts: app_servers
  serial: 10%
  max_fail_percentage: 5
  tasks:
    - name: Distribuisci il pacchetto dell'applicazione
      ansible.builtin.package:
        name: myapp
        state: latest

serial renderà l'esecuzione più lunga che colpire ogni host contemporaneamente, ma dà tempo ai load balancer, al monitoraggio e agli umani di reagire. Protegge anche le dipendenze condivise. Un mirror di pacchetti, un endpoint di migrazione del database o un gestore di segreti potrebbero non sopravvivere a migliaia di richieste simultanee.

I grandi deployment Ansible mettono sotto stress sistemi facili da dimenticare: risolutori DNS, repository di pacchetti, archivi di segreti, pipeline di logging ed endpoint di monitoraggio. Se un playbook rallenta solo a conteggi di forks più alti, controlla quei servizi condivisi prima di incolpare Ansible.

Tieni anche sotto controllo l'output dei callback. I log molto verbosi sono utili durante il debug, ma possono rallentare le grandi esecuzioni e seppellire il vero fallimento. Usa l'alta verbosità per una fetta ristretta di host, poi torna all'output normale per l'esecuzione sull'intera flotta.

10. Dividi i Play per Dominio di Fallimento

Un trucco di scaling spesso trascurato è smettere di trattare l'intero patrimonio come un'unica unità di deployment. Se host di database, host web, code e nodi cache vivono tutti nello stesso gigantesco play, un gruppo lento o rotto può ritardare lavoro non correlato. Separa i play per dominio di fallimento e ordine delle dipendenze.

Ad esempio, esegui la configurazione base del sistema operativo in modo ampio, ma distribuisci il codice dell'applicazione per livello di servizio. Aggiorna i nodi cache nel loro play. Svuota e riavvia i nodi web in batch. Applica la configurazione del database con controlli extra e concorrenza minore. Questo rende i tentativi più sicuri perché puoi rieseguire la parte fallita senza ripetere il lavoro su ogni host.

Rende anche più chiara la proprietà. Il team responsabile di un servizio può ottimizzare la dimensione del batch, i controlli di salute e il comportamento di rollback senza cambiare le impostazioni globali di automazione. Ansible su larga scala rimane manutenibile quando la struttura del playbook corrisponde al modo in cui l'infrastruttura effettivamente fallisce durante incidenti reali e finestre di manutenzione.

Il lavoro di performance Ansible a più alto impatto è di solito semplice: riutilizza le connessioni SSH, evita la raccolta di fatti non necessaria, dimensiona correttamente forks e impedisci ai playbook di fare piccole operazioni ripetute. Dopo di ciò, guarda all'architettura. Se un singolo controller non riesce a tenere il passo in modo pulito, dividi il lavoro tra nodi di esecuzione e rendi inventario, credenziali e logging ripetibili.