None

Semplificare i processi IT: un esempio di automazione con Ansible

In Nephila adottiamo la metodologia DevOps, che combina l’approccio lean e agile con sviluppo software e operazioni di infrastruttura. Questo ci permette di essere più reattivi, di rilasciare software in modo continuo ma anche di snellire tutte quelle operazioni legate alla configurazione di macchine, server e reti.

Tra le tecnologie che utilizziamo c’è Ansible, un software open source che consente di automatizzare le procedure di configurazione di macchine e sistemi rendendole meno dispendiose in termini di energie e tempo. Usando Ansible ci si può focalizzare sulla gestione dello stato della macchina più che sulle azioni da eseguire, eliminando script ad hoc ed errori dovuti a stati inattesi della macchina. Una vera svolta per rendere ancora più snello il lavoro e semplificare i flussi in area DevOps e Infrastruttura.

In questo blog post, vogliamo raccontarti come lo abbiamo utilizzato e come può essere impiegato anche in caso di configurazioni piuttosto semplici, come nel nostro caso.

Buona lettura! Ricordati di lasciare un commento alla fine dell’articolo per farci sapere che ne pensi, se hai mai utilizzato Ansible e se hai dei consigli da suggerire. Ti aspettiamo!

Ansible: come lo abbiamo utilizzato

Nel nostro scenario abbiamo avuto la necessità di configurare una macchina sulla quale dovevano essere presenti e configurati questi servizi:

  • radius e mariadb
  • phpmyadmin
  • zabbix, postgres
  • aigor

Radius è un protocollo usato per l'autenticazione di utenti di rete e implementare politiche di autorizzazione e accounting (attività conosciute con le sigla “AAA”). Come tutte le applicazioni che manipolano dati, ha bisogno di un database per funzionare: per compatibilità con l'installazione che stavamo migrando abbiamo scelto MariaDB.

Un altro pezzo fondamentale dell’architettura è rappresentato da Zabbix, uno strumento che consente di monitorare host di diverso genere, raccogliendo informazioni specifiche e degli alert in caso qualcosa non funzioni. È altamente configurabile ed è una soluzione utilizzata da molte compagnie, anche grazie al fatto che è flessibile ed è open source: le aziende quindi possono contribuire liberamente al progetto. Zabbix per funzionare ha bisogno di un database: questa volta abbiamo utilizzato Postgres perché, non essendo costretti a preferire una tecnologia piuttosto che un'altra per motivi di retrocompatibilità, abbiamo avuto libertà di scelta.

Per l'accesso ai dati del server radius, abbiamo optato per usare PHPMyAdmin: con tutti i suoi difetti, è un ottimo strumento per accedervi, senza aver bisogno di accedere manualmente al database.

L’ultimo componente che volevamo venisse ospitato su quella macchina è la nostra applicazione: aigor, una piattaforma di ticketing interna utilizzata anche dai nostri clienti.

Possiamo schematizzare l’intera infrastruttura in questo modo:

schema di un'architettura realizzata con Ansible

Ansible: perché lo abbiamo scelto

Ansible è risultato lo strumento ideale per gestire tutte le nostre configurazioni: innanzitutto permette di non scrivere una riga di codice bash andando a ridurre molto la complessità generale del sistema. Scrivere codice bash complesso e articolato rappresenterebbe un ostacolo nello sviluppo, derivante innanzitutto dalla familiarità del team di sviluppo con il linguaggio, e successivamente dalla moltitudine di casi limite da gestire (input non previsti, eccezioni…).

C'è però un’altra ragione fondamentale: la necessità di replicare la stessa configurazione in fase di sviluppo. Poter sviluppare e testare processi di automazione in repliche locali / distinte delle infrastrutture reali è fondamentale per assicurare una buona integrazione fra tutte le persone che ci lavorano.
Con l’ausilio di una Virtual Machine, Ansible ci ha aiutato a replicare la configurazione comune su ogni VM in maniera rapida e automatica: nessuno di noi ha dovuto scrivere configurazioni per la propria macchina, poiché lavorando in VM era la medesima.
Inoltre, la gestione dell’ambiente, a prescindere dalla produzione o dallo sviluppo, con Ansible permette di replicare questa stessa configurazione in caso di necessità (es. guasti sul server di produzione, migrazione degli applicativi su un’altra macchina), consentendo quindi di ripristinare lo stato su un altro luogo.

Un’insieme di vantaggi indiscutibili che hanno facilitato il nostro lavoro di configurazione dell’infrastruttura. Ma data la nostra esperienza, quali sono gli elementi da non sottovalutare quando scegliamo di utilizzare Ansible per le nostre automazioni?

Modularità: scrivere playbook Ansible

Scrivere un playbook Ansible unico, a meno che la macchina non sia piccola e non richieda pochissime operazioni, non è generalmente considerata una buona idea: l’effort di chi legge il playbook è molto alto e il tempo che impiega potrebbe essere benissimo destinato ad altro. Molto meglio invece prediligere approcci che sfruttano altri file, in modo tale che il testing e la manutenzione di ogni parte dell’infrastruttura possano avvenire in totale comodità e senza aver bisogno di spendere tanto tempo per capire nello specifico le attività in carico ad un playbook molto articolato.

In poche parole, usare più playbook che a loro volta hanno task importati tramite direttive come `include_tasks` o `import_tasks`, permette di effettuare cambiamenti in maniera rapida senza dover approfondire nel dettaglio le attività di ogni playbook ansible.

Modularità: l’utilizzo dei ruoli online

Essere modulari non significa solamente spezzettare il lavoro in più file, ma anche utilizzare ruoli personalizzati o eventualmente importati da altri. Ansible dispone di un ricco hub comunitario chiamato “Ansible Galaxy”: chiunque può creare un ruolo e metterlo a disposizione degli altri. Prima di decidere se integrare o meno un ruolo esterno, è bene scegliere con criterio, andando a controllare:

  • ultimo aggiornamento: è bene verificare che il modulo sia aggiornato costantemente e manutenuto per poter contare sulla sua disponibilità e funzionalità nel tempo;
  • quanto viene utilizzato: un ruolo utilizzato da molte persone ha più possibilità di essere manutenuto e supportato nel tempo rispetto ad un ruolo utilizzato da pochi;
  • quanto è semplice da mantenere e personalizzare: è possibile che un ruolo non venga aggiornato per un po' di tempo, e ciò potrebbe causare un'incompatibilità quando Ansible viene aggiornato a una nuova versione. In queste situazioni, è fondamentale valutare la facilità con cui è possibile apportare modifiche al ruolo. È sempre meglio prediligere ruoli che espongono variabili esterne per poterli personalizzare, invece di optare per soluzioni temporanee che risolvono solo momentaneamente eventuali problemi. Questo approccio consente di adattare il ruolo a esigenze specifiche, senza andare ad adottare soluzioni temporanee.
  • codice: fare un audit per escludere problemi di sicurezza.

Più in generale, i ruoli scaricati dall'hub devono essere gestiti con gli stessi criteri e attenzione di una dipendenza applicativa Python o Javascript, perché ci si sta affidando a codice scritto e manutenuto da altri.

Ansibile Galaxy sfrutta il file requirements.yml per dichiarare le dipendenze da ruoli Ansible e le risorse da scaricare. Questo file viene utilizzato con il comando ansible-galaxy per installare automaticamente i ruoli e le dipendenze necessari per un progetto Ansible.

Ecco il nostro:

collections:
 - name: community.general
   version: 7.5.0
   type: galaxy
 - name: community.postgresql
   version: 2.4.3
   type: galaxy
 - name: community.mysql
   version: 3.7.2
   type: galaxy
 - name: community.zabbix
   version: 2.1.0
   type: galaxy
 - name: ansible.posix
   version: 1.5.4
   type: galaxy
roles:
 - src: nginxinc.nginx
   version: 0.24.1
 - src: geerlingguy.ntp
   version: 2.3.3
 - src: geerlingguy.docker
   version: 6.2.0
 - src: geerlingguy.certbot
   version: 5.1.0
 - src: geerlingguy.redis
   version: 1.8.0
 - src: geerlingguy.supervisor
   version: 3.1.1
 - src: geerlingguy.postgresql
   version: 3.5.0
 - src: geerlingguy.pip
   version: 2.2.0
 - src: geerlingguy.mysql
   version: 4.3.3

In questo esempio, stiamo dichiarando dipendenze da ruoli e collection. In particolare, vogliamo farti notare due cose:

  • quasi tutti i ruoli sono presi da Jeff “geerlingguy”: la scelta non è affatto banale, Jeff Geerling è un famoso sviluppatore molto attivo nella community Ansible per l’enorme quantità di ruoli da lui sviluppati e mantenuti, e i suoi ruoli hanno un’ottima reputazione poiché altamente personalizzabili;
  • abbiamo scelto di utilizzare la collection di Zabbix (insieme ad altre collection) della community: le collection sono un insieme di ruoli, e poiché avevamo bisogno di utilizzare le componenti di Zabbix, come l’agent o l’interfaccia web su un unico server, aveva poco senso andare a creare un ruolo da zero per ognuno di questi componenti, soprattutto perché utilizzando la collection della comunità saremmo andati solamente a modificare delle variabili, anziché definire dall’inizio tutti i passaggi per l’installazione e la configurazione da riga di comando.

Ecco un estratto del nostro group_vars.yml:

# Zabbix common variables
zabbix_web_vhost_location: /etc/nginx/sites-enabled/zabbix

zabbix_server_dbname: zabbix_server
zabbix_server_database: pgsql
zabbix_server_database_long: postgresql
zabbix_server_dbport: 5432
zabbix_server_dbuser: zabbix
# Zabbix Agent
zabbix_agent2: true
zabbix_agent2_server: "127.0.0.1"
zabbix_agent2_server_active: "127.0.0.1"
zabbix_agent2_listen_ip: "0.0.0.0"
# Zabbix UI
zabbix_timezone: Europe/Rome

Come possiamo notare, ci sono tante variabili e in particolare i ruoli all'interno delle collection andranno a:

  • impostare l’agent con l’indirizzo specificato: nel nostro caso abbiamo tutto su una macchina
  • impostare la timezone di Zabbix nel server
  • scrivere la configurazione del virtual host di Nginx, in modo tale che Zabbix abbia la sua configurazione scritta nel percorso specificato

Non abbiamo scritto nessun file: ci hanno pensato i ruoli per noi!

Ritornando a parlare più in generale di ruoli, abbiamo anche la possibilità di andare a specificare il percorso di un ruolo qualora lo avessimo scaricato manualmente o lo stessimo sviluppando. Ad esempio:

- name: ruolo_custom
   src: ./roles/ruolo_custom


Dopo aver definito il file requirements.yml, basta utilizzare il comando qui sotto per scaricare i ruoli specificati:

ansible-galaxy install -r requirements.yml


Verranno installati tutti i ruoli e le collection elencati nel file requirements.yml nella directory predefinita di Ansible, che di solito è roles/, che si trova nella directory in cui viene eseguito il comando.

In questo modo, Ansible Galaxy semplifica la gestione delle dipendenze e consente agli utenti di utilizzare facilmente i ruoli Ansible sviluppati dalla community.

Per ulteriori informazioni sui ruoli, rimandiamo alla documentazione ufficiale.

Vagrant: macchine virtuali, che comodità!

All’inizio, quando è stata descritta l’architettura, è stato fatto riferimento alle macchine virtuali e ai vari ambienti: abbiamo utilizzato Vagrant per la gestione e la creazione delle Virtual Machine. Non solo ci permette di eseguire un provider da linea di comando, molto utile quando qualcosa è già in esecuzione, ma questo tool mette a disposizione dei file (Vagrantfile) per descrivere macchine virtuali ed avere quindi la stessa configurazione di test sulle proprie macchine.

Un conto è fare i test sulla propria macchina fisica, un conto è farli in VM: chiaro che se si sbaglia qualcosa basta creare la VM da capo ed eseguire di nuovo il playbook principale. Se si configura in maniera impropria il sistema, soprattutto quando si copiano chiavi SSH o si effettua la creazione di utenti, si rischia di rendere il proprio sistema inutilizzabile impedendo l’accesso alla macchina.

Riportiamo qui di seguito alcuni frammenti significativi del nostro Vagrantfile.

In questo frammento, stiamo specificando che la nostra macchina virtuale monterà Ubuntu versione 22.04 (Jammy) che è la versione LTS più recente al momento della scrittura dell’articolo. Usare una versione LTS garantisce un ambiente affidabile e stabile perché testato e verificato prima del rilascio.

config.vm.define "stage1" do |stage1|
    stage1.vm.box = "ubuntu/jammy64"
    stage1.vm.network "private_network", ip: "192.168.56.2"
    stage1.vm.hostname = "test"
 end

La direttiva “Network” stabilisce che la macchina virtuale è raggiungibile all’interno della rete privata all’indirizzo ip 192.168.56.2 e la macchina si chiamerà “test”.

In questo altro frammento, invece, copiamo la propria chiave pubblica SSH all’interno della macchina virtuale per permettere l’accesso dal proprio terminale a quello della VM.

config.vm.provision "file", source: "~/.ssh/nephila_main.pub", destination: "~/.ssh/authorized_keys2"


In altre parole stiamo andando a “dare il permesso” di connetterci dal nostro computer alla macchina virtuale in SSH.

Le macchine virtuali sono comode: usiamole!

Crea sempre (almeno) due ambienti

Ora che abbiamo spiegato tutti gli elementi essenziali e come abbiamo configurato il nostro ambiente di test, è bene fare un’ultima precisazione: per distinguere tra ambiente di test e ambiente di produzione, i nostri playbook e i nostri ruoli dovrebbero essere customizzabili attraverso delle semplici variabili. Il nostro progetto quindi dovrà avere due configurazioni al suo interno, una per la produzione e una per il test (VM), in maniera tale da poter distinguere tra i due ambienti andando semplicemente a cambiare i file relativi all’ambiente.

Oltre alle necessità di sviluppo descritte prima, questo ci serve perché se in futuro vogliamo estendere l’ambiente, ad esempio introducendo un server intermedio tra produzione e sviluppo locale, basterà creare una gerarchia di file e cartelle identica a quella che già si utilizza per sviluppo o produzione e cambiare un semplice file.

Ecco una possibile gerarchia di cartelle:

```
progetto/
	ansible/
		group_vars/
			all/	<- configurazioni comuni a tutti gli ambienti (universali)
		inventories/
			dev/
				hosts.ini	<- gli host per lo sviluppo in locale
				group_vars/ 	<- configurazioni solo per dev
			prod/
				hosts.ini
				group_vars/	<- configurazioni solo per produzione
		files/	
		roles/
		templates/
```

L’ambiente `dev` e l’ambiente `prod` sono pressoché identici, e se volessimo aggiungere un ambiente aggiuntivo (es. `beta`) basterebbe replicare la stessa gerarchia di uno dei due ambienti.

***

Prima di lasciarti, ti ricordiamo di lasciare un commento e farci sapere cosa ne pensi. Hai mai utilizzato Ansible? Qual è la tua esperienza? Hai dei consigli di utilizzo?
Scrivici nei commenti qui sotto, oppure sui social. Ci trovi su X, Facebook, Mastodon e Linkedin.

Buona automazione! Alla prossima!