mercoledì 20 aprile 2016

Costruire un sistema di monitoring scalabile

Salve o miei due lettori (suppongo che la lunga pausa abbia fatto calare drasticamente il numero di lettori di questo blog) in questo articolo tratterò per sommi capi di un argomento squisitamente tecnico dimezzando ulteriormente il numero di lettori. Tale argomento sarà la costruzione di un sistema di monitoraggio scalabile.

Per monitoraggio si intende l'attività di raccolta, archiviazione, costruzione di statistiche, visualizzazione (ed eventualmente allerta in caso di anomalie) di uno o più host (computer connessi tramite una rete TCP/IP) eventualmente distribuiti su più siti geografici. In breve: pornografia per ingegneri di reti (network engineers per gli anglofili).

L'aggettivo scalabile presuppone che la soluzione proposta sia in grado di trattare altrettanto bene uno, dieci, cento, mille, diecimila o più host e che quindi si adatti al serverino domestico come alla rete di datacenter di MEGA_CORPORAZIONE_A_CASO.

Si badi bene che le tecniche che andrò a descrivere possono essere utilizzate con gli opportuni aggiustamenti anche in altre situazioni in cui occorre raccogliere ed elaborare delle misure da un numero imprecisato di entità.

Come potete immaginare il problema è estremamente complesso e particolarmente sentito (specialmente all'aumentare del numero di host da monitorare) e quindi vi sono un numero non indifferente di soluzioni sia a sorgente aperto che proprietarie che sviluppate ad-hoc per una determinata infrastruttura/applicazione (a.k.a. LA collezione di script costruita nel corso di anni dal guru di turno che nessun altro sa come far funzionare). Non andrò ad elencarle tutte (del resto non mi è possibile compilare un elenco esaustivo perché non ho modo di venire a conoscenza di TUTTE le soluzioni ad-hoc) ma mi limiterò a dare delle linee guida generali che potrete adoperare per valutare le varie proposte pre-esistenti ed eventualmente decidere se imbarcarvi nell'ennesima reinvenzione del treno merci (un sistema di monitoring è troppo complesso per la metafora della ruota).

Le premesse

Come prima cosa occorre stabilire esattamente cosa si vuole monitorare, quali sono i parametri in gioco e quali statistiche si vogliono raccogliere. Senza queste informazioni il problema è talmente vago e aperto che una soluzione vale l'altra perché qualsiasi prodotto o pila di prodotti sceglierete vi renderà scontenti per un motivo o per l'altro.

L'unica cosa da tenere a mente è che se vogliamo che la soluzione scali e sia affidabile dobbiamo tenere a mente due principii fondamentali:

  1. Distribuzione del carico.
  2. Ridondanza.

In virtù di questi principii eviteremo categoricamente le soluzioni in cui c'è un ente centrale che contatta direttamente le entità da monitorare: è un'infrastruttura che introduce una debolezza fatale perché accentra il carico (violando il primo principio) ed elimina la ridondanza (per definizione un sistema ridondante ha più componenti di quante strettamente necessarie al suo funzionamento ordinario in modo tale da compensare i guasti) creando un single point of failure (leggasi: se va giù lui va giù tutto).

Basandoci sui due principii possiamo tracciare delle linee guida da seguire:

  • La raccolta dei dati sarà decentralizzata e si procederà a pre-processare quanto più possibile sui nodi remoti.
  • Dove possibile si adotteranno tecniche di funnelling (dall'inglese funnel ovvero imbuto) dei dati raccolti.
  • A seconda della volontà di salvare i dati monitorati a medio e lungo termine, delle dimensioni del carico e delle previsioni di crescita del carico stesso sarà necessario scegliere il tipo di database da utilizzare per il salvataggio dei dati (od optare per nessun database se non si desidera salvare i dati).

Raccogliere i dati

Basta con la fuffa generica! Adesso ci andiamo giù pesante con la fuffa (un po' più) specifica!

Abbiamo deciso che i dati saranno raccolti in maniera decentrata da tante piccole entità che chiameremo agenti (ma possiamo benissimo chiamarli in qualsiasi altro modo purché non sia protetto da Copyright). È fondamentale che gli agenti siano piccoli perché non vogliamo che impattino negativamente sui dati che vogliamo raccogliere, tuttavia (grazie anche agli smartphone) al giorno d'oggi è possibile comprare per poche decine di euro al pezzo macchine piccole quanto pacchetto di sigarette con una dotazione di RAM ed un processore più che adeguati per far girare un sistema GNU/Linux da dedicare alla raccolta dati e i processori multicore e multithread ci assicurano che (se non siamo particolarmente idioti nello scrivere i nostri agenti) solo nelle condizioni di carico estreme non ci sarà la possibilità di far girare il nostro agente su un server da monitorare per cui le scelte possibili sono più ampie che in passato (nei limiti del ragionevole).

Un solo appunto: mi dispiace per i fan di JRuby, ma avere una Virtual Machine che esegue codice Ruby ricompilato in Java non è una soluzione leggera: fatevene una ragione.

Detto questo vediamo quali sono le alternative per i nostri agenti.

Shell script

La cara buona vecchia shell che troviamo in (quasi) ogni installazione di GNU/Linux è il primo strumento a cui dovrebbe pensare un sistemista quando si tratta di scrivere un tool che faccia monitoraggio dei log. Aggiungiamo awk e netcat (anche noto come nc) ed avremo un potente strumento nelle nostre mani. Giusto? Purtroppo in realtà non è così semplice...

Partiamo dalla prima pecca della shell: se scrivo uno script bash che usa awk e nc dovrò installare questi tre programmi in ogni macchina da monitorare. Idem dicasi per qualsiasi altra dipendenza di cui posso aver bisogno.

Seconda pecca della shell: avvia un processo per ogni tool che chiama all'infuori dei suoi builtin. Una riga come questa avvia ben tre processi (a cui si aggiunge la shell):

grep espressione $miofile | tail -n 100 | cut -d ',' -f 3

E sebbene sia possibile fare tutto con awk (anche il tail delle ultime 100 righe) il risultato è alquanto ostico per i non iniziati:

awk -F',' '/espressione/{ o[NR % 100] = $3 } \
END{ i=(NR < 100 ?  0 : NR); \
do print o[++i % 100]; while(i % 100 != NR % 100)}' $miofile

So cosa state pensando: le lettere sono quelle del nostro alfabeto ma la lingua è quella di Mordor. Non posso darvi torto.

Questo ci porta alla terza pecca della shell: per quanto sia potente la sintassi e le idiosincrasie di certi tool sono tali per cui sono in pochi a poterci lavorare con profitto senza impazzire.

Python

Python ha conquistato sempre più proseliti nel corso degli ultimi 15 anni per cui deve essere una via percorribile per la scrittura del nostro agente. Ed in effetti Python è una buona proposta sotto molti punti di vista:

  • Ha una sintassi comprensibile (il più delle volte).
  • Possiede una nutrita comunità di utenti che hanno sviluppato codice di ogni genere e per ogni scopo.
  • Batte la shell in quanto a velocità di esecuzione.

Quindi dichiariamo Python vincitore e chiudiamo la questione? Ni.

Il primo problema contro cui ci scontriamo è: "quale versione di Python posso/devo utilizzare?". La risposta a questa domanda non è banale. A tutt'oggi ci sono due versioni del linguaggio attivamente utilizzate: la versione 2 e la versione 3. La prima è la versione storica di Python, presente in numerose distribuzioni ed utilizzata in un ampio raggio di progetti con diverse estensioni per l'interprete di riferimento (CPython), la seconda è la versione attuale del linguaggio e presenta parecchi cambiamenti rispetto alla precedente sia in termini di sorgenti Python che di interfaccia dell'interprete. Per aiutare chi deve migrare i propri sorgenti Python esiste il tool 2to3, disgraziatamente il tool automatico non copre tutte le possibili amenità a cui si può andare incontro. Per chi volesse approfondire l'argomento il mio consiglio è di leggere l'ottimo articolo di Peter A. Donis ed Eric S. Raymond: Practical Python porting for systems programmers.

CPython inoltre non è l'unica implementazione di Python disponibile (sebbene sia la più completa, stabile e maggiormente supportata).

Tcl

Qui tocchiamo un tasto dolente. Io sono un fan del Tcl, ma sono anche dolorosamente cosciente del fatto che il Tcl ha perso la guerra dei linguaggi di programmazione una ventina di anni fa e se n'è reso conto circa una decina di anni fa.

L'interprete Tcl è meno esoso di memoria rispetto a CPython, il linguaggio è molto espressivo (con dei tocchi di LISP qui e là) ed ha un'ottima astrazione per i socket di TCP/IP. Peccato che quelli che lo conoscano si dividano in due categorie:

  • Barbuti sistemisti con qualche anno sulle spalle che lo associano a lentissime e buggate GUI per tool da riga di comando.
  • Fan duri a morire che cercano di infilarlo in ogni progetto a cui hanno accesso.

Se volete imbarcarvi in una battaglia stile Don Chisciotte contro i mulini a vento usate pure il Tcl, sappiate però che sarete gli unici a capirci qualcosa del codice che scriverete (il che potrebbe essere un vantaggio per la conservazione del posto di lavoro).

Perl

Discorso simile al Tcl con in più una certa tendenza del linguaggio a dare eccessiva libertà al programmatore. Il Perl non è stato definito un linguaggio di programmazione "write only" a caso.

Se avete sufficiente disciplina, non vi spaventano le espressioni regolari e vedete un pregio nella facoltà di poter ridefinire TUTTO a runtime allora potreste pensare di scrivere il vostro agente in Perl.

Un punto a vantaggio del Perl è che anche più ubiquo di Python in ambito UNIX data la sua età.

C o C++

Siete tra coloro i quali reputano che i linguaggi di scripting siano solo fumo negli occhi?

Non vi spaventa il dover gestire da soli la memoria? Anzi il guadagno in prestazioni giustifica l'occasionale memory leak o use after free che sarà comunque rilevato da Valgrind e debitamente eliminato.

Volete avvicinarvi il più possibile alla macchina fisica senza essere costretti ad impare l'assembly?

Il C e/o il C++ potrebbero fare per voi! Se non fosse che il trattamento del testo in C e in C++ sia una pratica sadomasochistica ai limiti della tortura...

Detto questo vi sono librerie che semplificano le cose, ma introducono dipendenze e complicano la compilazione dei propri programmi.

In definitiva il consiglio che mi sento di darvi è scegliete il C o il C++ se non ci sono altre alternative o se le alternative sono troppo pesanti o troppo lente per gestire il flusso di dati che devono gestire. Nella mia esperienza personale simili casi sono piuttosto rari.

Java

Devo proprio scrivere perché non dovreste usare Java?

Scala

Non conosco Scala ma credo che lo userei per lo step successivo (funnelling) ma non per la raccolta diretta dei dati.

Erlang, Haskell, Racket/Common LISP/Scheme, OCaml

Vedi Scala.

Go, Rust, D

Questi potrebbero essere delle valide alternative se non fossero ancora dei Work in progress.

Fateci degli esperimenti ma non usateli in produzione senza averli testati estensivamente.

Ruby

No.

Javascript/ECMAscript

No. Davvero, non fatelo.

PHP

VADE RETRO!!!

Altri linguaggi di programmazione

Se non ho già elencato il vostro linguaggio di programmazione preferito significa che ricade in una di queste categorie:

  • La categoria dei linguaggi poco diffusi. Con questi dovete fare uno sforzo attivo per convincere chi prende le decisioni ad utilizzare qualcosa che nessun altro usa. Qui ci metto anche i linguaggi di cui non sono a conoscenza.
  • La categoria dei linguaggi morti o morenti. Chi scrive più in COBOL o in FORTRAN al di fuori di certi ambiti?
  • La categoria dei linguaggi inadatti al compito. Verilog e PL/SQL sono ottimi nel loro dominio di applicazione, perché piegarli ad altro?
  • Brainfuck, LOLCODE, whitespace e simili. Devo aggiungere altro?

Funnelling

Avrei voluto inserire qui un bel grafico, ma le ricerche con Google Immagini mi hanno portato ad un punto morto pieno di ragazzotti intenti a tracannare birra mediante imbuti... Ah la gioventù!

Se siete arrivati fino a qui vuol dire che siete davvero interessati all'argomento, oppure avete saltato buona parte di quanto ho scritto nella speranza che la finissi con le opinioni oppure semplicemente non avete niente di meglio da fare e state ammazzando il tempo.

Ad ogni modo qui le cose si fanno interessanti. Mentre gli agenti devono essere leggeri ed hanno accesso solo ai dati provenienti dall'entità in cui risiedono (limitando la quantità di pre-processamento che possono effettuare sui dati che raccolgono) i nostri imbuti che ricevono e reinviano i dati possono effettuare diverse operazioni sui dati medesimi:

  • Tipizzare i dati se non è già stato fatto in fase di raccolta.
  • Aggregare i dati ricevuti da diversi agenti.
  • Filtrare i dati.

Ed ovviamente possono inviare i dati ad altri imbuti che a loro volta potranno aggregare, filtrare ed inviare dati nell'eterno ciclo del data mining.

Liberi dai vincoli dettati agli agenti i nostri imbuti sono allo stesso tempo il cervello, il sistema circolatorio e i muscoli della nostra infrastruttura di monitoraggio.

Qui è dove si distingue il dilettante (come il sottoscritto) dal professionista.

Qui è dove si trova la frontiera del data mining e dove si sperimentano tecniche di analisi avanzata e diagnosi precoce mediante il machine learning.

Qui purtroppo è anche dove la mia conoscenza ha le lacune più grandi. L'unica dritta che posso darvi è che, come avrete intuito, questa parte è la più importante di tutta l'infrastruttura ed è quindi quella che va progettata al meglio per potersi adattare alle esigenze di chi dovrà poi utilizzare l'infrastruttura stessa.

Visualizzazione, Reportistica e Allarmistica

Vi ricordate quando vi ho detto che nella fase intermedia ho le lacune maggiori? Ecco nemmeno in questo sono molto ferrato.

La visualizzazione dei dati è una scienza ed è tutt'ora oggetto di ricerca in diverse università in giro per il Mondo.

Questo è anche il campo in cui le soluzioni ad-hoc spuntano come funghi dopo una pioggia autunnale proprio in virtù della necessità di adattare il risultato delle analisi alle necessità di comprensione dell'utente finale (che non necessariamente sarà un essere umano).

Nel caso in cui si decida di sperimentare con il machine learning bisogna anche mettere in conto che si dovrà monitorare anche il livello di accuratezza delle analisi e delle previsioni che ci arrivano dall'infrastruttura stessa.

I sistemi di allarmistica poi dovranno essere particolarmente attenti ad evitare falsi negativi (che portano ad ignorare situazioni pericolose) e falsi positivi (che spingono l'utente a spegnere i sistemi stessi a causa dell'eccessivo rumore di fondo).

La reportistica è opzionale nel momento in cui decidiamo che non vogliamo conservare a lungo termine i dati raccolti ed elaborati. Normalmente non è così e quindi entrano in gioco tutta una serie di scelte su come conservare i dati e sulle eventuali post-elaborazioni da effettuarsi prima di archiviare i dati stessi.

Tutto questo ovviamente va poi adattato alle normative vigenti sul trattamento dei dati sensibili nel Paese (o nei Paesi) in cui si va ad operare.

Insomma ce n'è di che divertirsi! Alla prossima!