Come ottenere informazioni con Ansible


Ansible è un tool di gestione dei sistemi operativi, nei precedenti articoli abbiamo visto come abilitare la gestione di alcuni server Linux e come impartire un comando. Vediamo in questo articolo come ottenere informazioni dai server gestiti. A volte è necessario recuperare alcuni parametri di configurazione di un sistema.

La configurazione di base per il setup di Ansible la potete trovare negli articoli precedenti, in questo invece vediamo alcuni tipi di playbook.

L’intento dell primo playbook è di restituire l’output del comando /usr/bin/date, che dovrebbe mostrarci il giorno e l’ora corrente.

- hosts: all
  user: ansible_service
  tasks:
   - name: Get clock
     shell: /usr/bin/date
     changed_when: False
     register: date
   - debug:
      msg: "{{ date.stdout }}"

Il playbook viene lanciato su alcuni server. La maggior parte dei sistemi restituisce un messaggio in cui è riportato il giorno e l’orario.

Tuttavia nel caso del server con IP 192.168.217.11 (Linux Mint) si può notare come l’esecuzione del comando restituisce un errore: “/bin/sh: 1: /usr/bin/date: not found“, l’estratto è interpretabile in maniera semplice: il comando /usr/bin/date non esiste sul sistema in questione. Infatti su server Linux Mint, /usr/bin/date non è disponibile.

Da ciò si può capire che l’uso dei comandi shell non è consigliato perchè questi comandi non sono disponibili su tutte le distribuzioni Linux. Alternativamente possiamo usare dei “costrutti” incorporati in Ansible chiamati “built-in” fact (traducibile con dati integrati).

Si può ottenere una lista dei fact (e relativi valori) usando il comando

ansible localhost -m ansible.builtin.setup

Questo mostrerà a video tutti i dati disponibili, nel caso specifico quelli del localhost. Nel mio caso più di 1000 righe di output. Fra questi, ce ne è uno che può essere usato per recuperare la data e l’ora; si tratta di ansible_date_time.iso8601.

Per evitare un errore simile a quello riscontrato con Linux Mint, abbiamo aggiunto una condizione chiamata when che ci permette di recuperare il dato dal sistema solo se esso è definito.

Riscriveremo il nostro playbook in questo modo:

- hosts: all
  user: ansible_service
  tasks:
   - name: Print day and time
     ansible.builtin.debug:
       msg: Host {{ inventory_hostname }} {{ ansible_date_time.iso8601 }}
     when: ansible_date_time.iso8601 is defined

Una nota: l’orario restituito essendo di tipo ISO 8601 è riportato col fuso orario UTC, per cui bisogna aggingere un’ora quando il fuso è CET oppure 2 ore quando è CEST.

Nell’esempio successivo invece desideriamo riavviare il servizio ntpd, quello che tipicamente mantiente l’ora sincronizzata.

Il playbook è abbastanza semplice:

- hosts: all
  user: ansible_service
  become: yes
  become_user: root
  tasks:
    - name: restart service ntp
      service:
       name: ntpd
       state: restarted

Tuttavia l’esecuzione è alquanto “disastrosa”

Purtroppo il servizio ntpd non è disponibile su nessuna delle macchine. Questo ci obbliga a modificare lo script. Sulle più recenti distribuzioni come le derivate da RHEL 8 e SUSE 15, il deamon responsabile della sincronizzazione dell’orologio via rete si chiama chrony. Spesso non è attivo di default.

Il deamon di sincronizzazione non è avviato.

Abbiamo dunque lanciato il nuovo playbook:

- hosts: all
  user: ansible_service
  become: yes
  become_user: root
  tasks:
    - name: restart service time synch
      service:
       name: chronyd
       state: restarted

Il riepilogo ci mostra che solo l’host Linux Mint riporta la mancata esecuzione del task. Questo perchè su quell’host il servizio chrony non è disponibile. Ma su gli altri host cosa è successo? Il servizio chronyd è stato riavviato e questo ha provocato un cambio di stato del sistema per cui abbiamo un changed=1.

Abbiamo creato un playbook per recuperare lo stato del servizio:

- hosts: all
  user: ansible_service
  tasks:
   - name: checking service status
     command: systemctl status chronyd
     changed_when: False
     register: result
     ignore_errors: yes

   - name: display results
     debug: var=result.stdout

Questo playbook ci consente di capire se sul sistema è attivo chrony oppure un altro servizio per sincronizzare l’orologio come il vecchio ntpd.

In questo caso abbiamo questo risultato:

Dall’esecuzione del playbook, è possibile scoprire che il servizio chrony non sia attivo sull’host 192.168.217.14.

Concludiamo col precedente playbook questa guida su come ottenere informazioni usando Ansible, nei successivi articoli incontreremo nuove sfide.

Come aggiornare una distribuzione con Ansible

Nel precedente articolo Primi passi con Ansible, abbiamo visto come configurare l’ambiente Ansible e come predisporre i sistemi pronti ad essere controllati con Ansible. Ora vediamo come aggiornare i pacchetti delle distribuzioni che abbiamo nel nostro parco macchine.

La cosa in se è molto semplice, basta preparare un playbook che permetta l’aggiornamento con gli ultimi update. Eccolo qua:

- hosts: all
  user: ansible_service
  become: yes
  become_user: root
  tasks:
    - name: update all packages
      yum: name=* state=latest

Il tutto è molto succinto e funziona sia su distribuzioni derivate da Red Hat, SuSE e Debian derivate.

Per poter aggiornare un sistema dobbiamo essere root. Quindi il nostro utente di servizio, che abbiamo creato su tutti i server da gestire, chiamato ansible_service deve diventare root: questo è possibile perchè lo avevamo aggiunto nel file sudoers (vedi precedente articolo).

Notare anche che abbiamo fatto uso del modulo yum, che permette l’aggiornamento anche di sistemi senza il comando yum, perchè esso non è il comando yum ma corrisponde ad un modulo che fornisce gli strumenti per la gestione degli update su tutti i sistemi.

Il nostro file inventory contiene i seguenti host, il cui nome in parentesi quadre riporta la distribuzione:

[oracle_linux_8]
192.168.217.14
[suse_15]
192.168.217.10
[linux_mint]
192.168.217.11
[opensuse_15]
192.168.217.13

Dopo aver lanciato il playbook ci troviamo con questo risultato, non proprio “roseo”:

Il primo host con IP 192.168.217.10 (SuSE 15) è fallito.

L’host 192.168.217.11 (Linux Mint) e 192.168.217.13 (Opensuse) non sono stati aggiornati. Il motivo si può scoprire analizzando i log sui due server, ma il troubleshooting è semplice: nessun nuovo pacchetto è disponibile perchè le distribuzioni sono state aggiornate recentemente.

L’ultimo host, quello con IP 192.168.217.14 (Oracle Linux), in giallo è stato aggiornato; questo lo si può desumere perchè non è fallito (failed=0) e che è cambiato (changed=1). Il fatto che esso sia changed indica che il comando impartito ha provocato una modifica nel sistema e la cosa è ovvia visto che è stato aggiornato.

Un errore come “FAILED! => {“msg”: “Missing sudo password”}” può stare a significare che l’utente ansible non ha i privilegi di root, verificate di averli assegnati.

Ad ogni modo, vediamo perchè il primo host è fallito.

Nello screenshot sotto si può leggere che il primo host 192.168.217.10 non è stato aggiornato perchè si sono verificati degli errori, in particolare il tool zypper (l’equivalente di yum/dnf per SuSE) non è riuscito a raggiungere i server dove sono riposti gli update col messaggio “Problem retrieving the repository…” e “The requested URL returned error…”.

Forse leggere l’output in console per qualche server non è un grosso problema ma se avessimo anche solo una decina di host sarebbe preferibile avere un file con il log del comando.

Uno dei modi può semplici per fare ciò è quello di utilizzare il comando tee che consente l’output in console e anche l’output su file. Per esempio col comando:

ansible-playbook -i ~/ansible/inventory/inventory.list ~/ansible/playbooks/yum.yaml | tee ~/update.log

viene creato un file chiamato update.log nella home dell’utente. Il file ~/ansible/inventory/inventory.list è l’elenco dei server da aggiornare, ~/ansible/playbooks/yum.yaml è il playbook che abbiamo creato prima.

Come usare Ansible

Primi passi con Ansible

Installare Ansible su un server è alquanto facile, il difficile è usare e configurare Ansible per comandare i server.

Sono numerose le guide che insegnano ad installare Ansible, il software necessario sul “controllore” ma non serve sui controllati, quindi ci limitiamo a dire che spesso è sufficiente un yum install ansible o un apt install ansible per trovarci con un il software pronto all’uso.

La cosa più complicata è rendere il nostro parco macchine eterogeneo amministrabile da Ansible.

La prima cosa da fare è rendere raggiungibile la porta SSH di tutti i nostri server Linux al server Ansible. Questo va verificato a livello rete e sul firewall del sistema operativo.

Successivamente va creato un utente di servizio per accedere via SSH ai server. L’utente di servizio che andremmo usare può avere una password oppure può utilizzare una chiave. L’uso della chiave è semplice e ci esula in futuro a dover gestire la scadenza delle password ma ci impone di distribuire la chiave.

E’ opportuno anche aggiungere l’utente ai sudoers e senza richiedere la password di root. In questo modo saremo in grado di lanciare programmi e servizi privilegati.

Da ciò si evince che se il nostro parco macchine è eterogeneo predisporre il tutto richiede parecchio tempo ma alla fine della creazione degli account, le cose saranno più semplici.

Vediamo all’atto pratico come fare.

Creare un account su un server Linux è facile nel caso di SUSE, Ubuntu/Debian o Red Hat/CentOS e derivate basta usare il comando useradd più il comando di modifica della password.

useradd -u 1079 -m ansible_service -c "Account di gestione Ansible"
echo ansible_service:R8n7c£PQ£pX£4 | chpasswd

Questo comando va lanciato su ogni server. In questo modo creeremo un utente locale chiamato ansible_service con password R8n7c£PQ£pX£4
Per ora creeremo un utente con una password uguale per tutte le macchine, successivamente la password potrà essere disabilitata per usare l’autenticazione più sicura, mediante chiave.

Abbiamo scelto anche di creare un utente con uid e gid uguale su tutti i sistemi (con numero 1079), notare che il numero non deve essere usato da altri utenti sul sistema.

Per abilitare sudo senza password in Ubuntu/Debian e Red Hat, creiamo un file di configurazione sudoers o usiamo l’editor visudo per appendere la linea in fondo al file.

echo -e "ansible_service\tALL=(ALL)\tNOPASSWD: ALL" > /etc/sudoers.d/090_ansible_service

Se scrivere a mano questi 2 comandi su ogni server vi sembra molto impegnativo, è possibile utilizare dei piccoli trucchi per risparmiare tempo. Per esempio è possibile salvare questi 2 comandi in un file di testo (o uno script bash) da copiare e incollare su tutti i server o distribuirlo tramite una share nfs.

Se non vogliamo usare la chiave, è opportuno installare sui sistemi da controllare il pacchetto che fornisce il comando sshpass. Per Ubuntu/Debian è sufficiente un apt install sshpass, tuttavia per i sistemi Red Hat e derivate è necessario attivare i repository EPEL (cosa che non desideriamo fare, per cui useremo la chiave).

Configuriamo Ansible sul server di gestione

Il server, sul quale c’è Ansible, è un server Ubuntu 20.04. In questo server non è necessario creare l’account di servizio. Però è importante creare una cartella dove porre i file di configurazione e i playbook che andremmo a creare.
Se l’utente che gestirà Ansible è unico, questi file possono essere salvati nella home dell’utente.
Nel mio caso ho creato una cartella chimata ansible nella quale ho posto 2 sottocartelle: inventory e playbooks.

mkdir ~/ansible/
mkdir ~/ansible/inventory
mkdir ~/ansible/playbooks

Nel nostro ambiente demo abbiamo 4 server: un server Ubuntu 20.04 dove è installato Ansible, un server Oracle Linux 8, un server SuSE Enterprise Linux 15 e un desktop Linux Mint 20 (con SSH). Per questo abbiamo creato un file di inventory contente gli indirizzi IP degli ultimi 3 server da gestire.

nano ~/ansible/inventory/inventory.list

In playbooks ho creato il primo playbook in YAML che fa un ping verso tutti gli host dell’inventory.

nano ~/ansible/playbooks/ping_all_hosts.yaml

Nel quale ho digitato questo:

- hosts: all
  user: ansible_service
  tasks:
    - name: Ping all hosts
      ping:

Per lanciare il playbook in tutti gli host presenti nel file ~/ansible/inventory/inventory.list basta usare il comando:

ansible-playbook -i ~/ansible/inventory/inventory.list ~/ansible/playbooks/ping_all_hosts.yaml

Note: se compare un messaggio “Could not match supplied host pattern, ignoring:” “[WARNING]: No hosts matched, nothing to do”, verificate di aver usato il percorso completo per i file ossia ~/ansible/inventory/inventory.list e non inventory/inventory.list, per esempio.

Alla prima esecuzione del comando verranno mostrati i fingerprint SSH, le successive connessioni non lo mostreranno se abbiamo importato i codici di hash.

Tuttavia il comando non avrà buon fine in quanto non abbiamo passato la password. Il comando PING non è un ping di rete ma un test di connessione via SSH, quindi la password va inviata.

Dobbiamo passare la password nel nostro utente di servizio ansible_service.

ansible-playbook -i ~/ansible/inventory/inventory.list ~/ansible/playbooks/ping_all_hosts.yaml --ask-pass

Se il pacchetto sshpass non è installato, il comando riuscirà ma ci verrà restituito un esito negativo; infatti per l’host 192.168.217.9 e 192.168.217.10 Ansible non ha trovato il comando sshpass.

Quindi abbiamo 2 possibilità. Installare sshpass o attivare l’autenticazione con chiave pubblica. Dovremmo copiare la nostra chiave su tutti gli host. La prima cosa da fare, però, è generare un chiave.

ssh-keygen

Il comando che si può usare per caricare la chiave su tutti gli host è questo:

ssh-copy-id -i ~/.ssh/id_rsa ansible_service@192.168.217.10

Dove 192.168.217.10 è l’IP del server. Dovremmo inserire tutti gli IP dei nostri server.

Ora rilanciando il nostro comando senza ask-password, dovremmo riuscire a collegarci.

ansible-playbook -i ~/ansible/inventory/inventory.list ~/ansible/playbooks/ping_all_hosts.yaml