Come rendere un’applicazione web scalabile e fault tolerance con Amazon Web Services

In questi ultimi anni grazie a servizi cloud come Amazon Web Services (AWS), Google Cloud Platform e Microsoft Azure (è arrivato anche Alibaba Cloud in particolare per il mercato asiatico) sono diventati accessibili ad ogni sviluppatore una serie di servizi per garantire livelli di erogazione del servizio una volta impensabili per certe realtà e con costi proporzionati alla dimensione del progetto (pay-per-use).

Cerchiamo quindi di sintetizzare alcuni concetti chiave e quali servizi utilizzare per deployare un’applicazione web su AWS che risponda a requisiti minimi di scalabilità e di fault tolerance.

Questo post è basato sulla mia personale esperienza di deploy di Django che si presta facilmente per sviluppare un’applicazione su questo tipo di stack, lo stesso stack in parte ridotto l’ho utilizzato per progetti in php ma con un maggiore effort di implementazione. Ognuno deve trovare il “vestito” che calza meglio alle proprie esigenze scegliendo quali servizi o meno integrare. Anche se non vi occupate di progetti per clienti enterprise con SLA definiti, vi consiglio comunque di prendere in considerazione questo approccio o parti di esso, i vantaggi sono numerosi senza un incremento considerevole di costi o di complessità.

Lo stack AWS analizzato in questo post

Caching con Cloudfront

L’entry point dell’applicazione web è Cloudfront uno dei servizi che ritengo più vantaggiosi di AWS in termini di rapporto tra la facilità di implementazione e di vantaggio che ne deriva. Non si tratta solo di una CDN geograficamente distribuita (89 nodi nel mondo) per erogare contenuti statici ma anche di un sistema di caching per contenuti dinamici, un proxy e una protezione contro gli attacchi ddos.

Cloudfront ci consente di salvare in cache, nel nodo più vicino all’utente, contenuti dinamici che dobbiamo restituire uguali per più utenti e quindi consentirci di abbassare drasticamente il time to first byte (TTFB) per esempio da 400/500 ms (se il vostro TTFB supera il secondo avete dei problemi da risolvere!) a 40/50 ms. e soprattutto evitare chiamate al server per contenuti già prodotti.

Se la vostra applicazione prevede interazioni con l’utente (come il 99% di siti web), non avete comunque scuse, dovreste pensare già in fase di sviluppo di prevedere via ajax quelle parti di pagina che dipendono dallo stato dell’utente (loggato o meno, prezzi che dipendono dal singolo utente, generazione correlati, carrello, faceting, ecc ecc). Questo approccio consente di sfruttare anche sistemi di caching lato applicativo (es: salvando in cache parti di template già elaborate). Non sprechiamo risorse del server a ricalcolare un intero listing o una scheda prodotto uguale per tutti gli utenti! Meno chiamate al server -> meno risorse necessarie -> meno costi.

Oltre a questo Cloudfront ci assicura una prima protezione dagli attacchi DDoS grazie a AWS Shield Standard, senza costi aggiuntivi e senza configurazione offre una protezione dai più comuni attacchi DDoS.

Nel diagramma, collegato a Cloudfront potete vedere il Certificate Manager, ci tenevo ad inserirlo anche se non è certamente un servizio di primaria importanza, per sottolineare la facilità con cui grazie a Cloudfront è possibile distribuire la nostra applicazione web in https su protocollo http2. Per creare e installare il certificato ssl domain-validated (DV) bastano pochi minuti. Non mi dilungo sulla necessità di passare all’https ma vi rimando al post di un mio collega che spiega perché anche tu (e aggiungerei ora) dovresti migrare ad HTTPS (https://goo.gl/gkukLo).

Storage su S3

Per ogni distribuzione di Cloudfront in base a regole basate sull’url le richieste vengono redirette a differenti origini, per le risorse statiche senza dubbio la scelta ricade su Simple Storage Service ovvero S3. Come dice il nome stesso si occupa dello storage di file con il 99.999999999% (eleven nines) di durability (per proteggervi dall’errore materiale umano potete attivare il versioning). Anche in questo caso evitiamo di “sprecare” risorse del server web per servire dei file statici quando c’è qualcuno che lo fa meglio.

Adesso passiamo al cuore di calcolo dell’applicazione ovvero le istanze Elastic Compute Cloud (EC2) che non sono nient’altro che delle macchine virtuali di svariate dimensioni di risorse da scegliere a seconda delle proprie necessità. Sono uno dei primi servizi lanciati da AWS e sicuramente uno dei più comuni e semplici pertanto non mi dilungherò molto su questo. La mia attenzione la voglio focalizzare su due servizi a corredo delle istanze che ci fornisce AWS per rendere le nostre applicazioni realmente e facilmente scalabili e fault tolerance.

Auto scaling

Il primo è l’Auto Scaling ovvero la possibilità di scalare il numero delle istanze EC2 (scaling orizzontale) o la loro dimensione (scaling verticale) a seconda di regole predefinite. Questo consente di gestire picchi di carico inaspettato dell’applicazione o di risparmiare risorse e quindi soldi in caso di calo del traffico o di ciclicità temporale (es: periodi notturni, week-end ecc). Le regole per lo scaling possono essere basate sia su indicatori delle performance dei server come cpu e memoria sia sul numero di istanze sane collegate all’ELB (vedi sotto) che su regole temporali come nel caso si possa prevedere a priori un incremento del traffico per attivazione di nuove campagne di advertising o di ricorrenze particolari come il black friday. Tutto questo chiaramente ci consente di salvaguardare i costi allocando solo il necessario e di evitare disservizi in caso di picchi inaspettati di traffico.

Nel definire le regole di auto scaling teniate sempre a mente il buon principio di scalare velocemente all’aumento delle necessità anche al costo di allocare più risorse del necessario perchè non si può prevedere dove arrivi il picco e diminuire le risorse più lentamente al calare delle richieste.

Il secondo punto non è un vero proprio servizio ma un aspetto dell’architettura di AWS. Come già saprete AWS è suddiviso in regioni ovvero in differenti data center sparsi per il mondo, all’interno di queste regioni ci sono differenti availability zones isolate fisicamente che consentono, se sfruttate correttamente, di rendere le applicazioni fault tolerance in caso di guasti anche hardware. L’ideale è sempre avere due differenti auto scaling group in due differenti availability zones come nell’immagine che segue.

Elastic Load Balancing

L’anello di congiunzione tra le istanze EC2 che servono le risorse dinamiche e Cloudfront è l’Elastic Load Balancing (ELB) che si occupa di dividere il carico di lavoro tra diverse istanze magari in differenti availability zones. Le nuove istanze EC2 create o rimosse in caso di autoscaling vengono automaticamente collegate o scollegate dall’ELB che comunque prima di inoltrare le richieste alla singola istanza ne valuterà lo stato di servizio effettuando delle chiamate ad un url di test. Consiglio l’utilizzo del ELB anche nel caso non venga utilizzato l’autoscaling delle istanze EC2 o sia presente anche una sola istanza in quanto ci si svincola dal legame tra l’entry point dell’applicazione e le istanze EC2 consentendo lo swap di differenti istanze per la manutenzione di un server o per altre necessità come nuovi rilasci senza interruzioni di servizio.

L’ELB può anche essere utilizzato come entry point al posto di Cloudfront, utile per applicazioni di backend dove il numero di utenti è limitato a priori e pertanto non siamo interessati al caching di contenuti, anche questo servizio supporta l’https sempre tramite il certificate manager.

Recentemente è stato rilasciato l’Application Load Balancing che smista le richieste http a differenti istanze EC2 in base alla richiesta. Questo è utile per gestire con un unico entry point differenti applicazioni servite su istanze differenti.

Con ElastiCache ci occupiamo delle sessioni

Avendo la nostra applicazione su diverse istanze EC2 è importante fare attenzione alla gestione delle sessioni degli utenti. Il load balancer potrebbe indirizzare differenti richieste dello stesso utente a differenti server pertanto le istanze devono essere indipendenti rispetto alla singola sessione. In realtà questo è evitabile attivando la funzionalità stickness del load balancer che in base ad un cookie suo o dell’applicativo è possibile indirizzare tutte le richieste di un utente allo stesso server. Ma la soluzione ideale sarebbe portare i dati di sessione al di fuori del server così da non avere problemi in caso di problemi del server o di autoscaling. La scelta potrebbe ricadere sul database relazionale o su caching system come redis o memcache che troviamo nel servizio di AWS denominato ElastiCache.

RDS per non doversi preoccupare del DB

Infine arriviamo al database, non c’è nulla di meglio che potersi affidare ad un database managed senza doversi preoccupare della manutenzione di un altro server o peggio ancora di avere il db sullo stesso server dell’applicazione. A questo scopo AWS ci offre RDS un servizio managed di database relazionali, anche in questo caso è possibile scegliere tra differenti dimensioni di istanza e tra differenti sistemi db. Ultimamente sto testando Aurora il database engine di AWS compatibile con MySql ma che promette di esserne fino a cinque volte più veloce. Non ho ottenuto questi risultati in termini di miglioramento delle perfomance ma in quanto alla compatibilità non ho riscontrato problemi. Nonostante sia completamente managed con RDS comunque non si rinuncia alla possibilità di poter configurare i parametri del db senza problemi.

Anche per il database, sempre nell’ottica di ottenere un’applicazione veramente fault tolerance risulta importante evitare single point of failure e attivare la funzionalità multi-A-Z che gestisce in automatico il failover ad una istanza in standby in un’altra availability zone in caso di problemi dell’istanza. Il multi-A-Z consente anche di non avere downtime durante lo scaling verticale di un’istanza RDS. Per gestire invece uno scaling orizzontale è possibile istanziare delle replica in sola lettura del master db suddivise nelle differenti availability zone.