Indice dei contenuti

1. Introduzione

Taking a look at the infrastructure in my first few days of working at Xoxoday, I quickly realized that the infrastructure was relatively nascent. The multiple loose ends I noticed were potential indicators of technical debt, strictly speaking in terms of Infrastructure setup. The loose ends included the design, architecture, tooling, monitoring, and logging, along with various processes & practices.

Considering the nature of business, Xoxoday is a user-facing web service that could typically be categorized under Software as a Service (SaaS). That puts a lot of pressure on specific technical requirements such as availability, uptime, reliability, robustness & scalability.

A typical e-commerce website such as Xoxoday with high global traffic necessitates that we adapt to the latest and unique design patterns, architecture, tools & practices. This is to ensure that we keep up with the quality of service expected by the user base and ideally exceed the same.

Infine, un altro requisito critico che emerge per motivi di conformità alla privacy e alla sicurezza è che la maggior parte dei clienti si preoccupa dell'ubicazione dei dati. L'aspettativa normale è che i dati risiedano e non lascino mai il paese o la regione da loro richiesti.

Ciò ha creato la necessità di considerare la distribuzione di più ambienti di produzione in parallelo, con distribuzioni atomiche indipendenti l'una dall'altra. Noi lo chiamiamo policloud, anche se il termine può essere discutibile. Questo requisito ci imponeva di disporre di un'automazione all'avanguardia, poiché ora dovevamo replicare la gestione e la manutenzione dell'infrastruttura in più regioni.

Quindi, dopo un'attenta riflessione, ci siamo subito resi conto che dovevamo avere il meglio dei processi e delle pratiche di automazione dell'infrastruttura insieme a un portafoglio di moderne tecnologie basate sul cloud per diventare cloud-nativi e fornire il miglior servizio e la migliore esperienza ai nostri utenti.

L'aspetto positivo per noi è che gli sviluppatori avevano già suddiviso i loro servizi in microservizi. Questo ci ha aiutato a muoverci rapidamente in termini di adozione delle ultime tendenze e pratiche dell'infrastruttura.

2. Necessità di automazione

L'automazione dei servizi nel cloud è stato un tema caldo negli ultimi anni, che ha portato alla nascita di molti strumenti e tecnologie eccellenti. Le nostre esigenze erano tipicamente guidate dalla natura dell'azienda e dalle sue richieste. Per noi sono stati determinanti i seguenti fattori:

  • Alta disponibilità
  • Ridimensionamento automatico
  • Autoguarigione

Inoltre, l'automazione offre l'opportunità di realizzare l'infrastruttura come codice, consentendoci di interagire con l'infrastruttura sfruttando i fantastici vantaggi dei sistemi di controllo delle versioni.

Si è trattato di un grande cambiamento di paradigma, in quanto ci ha permesso di esaminare l'infrastruttura in modo proattivo e di ridurre le interazioni reattive (imperative). Ha reso più semplice il rollback e ci ha aiutato a tenere traccia in modo efficiente delle modifiche nell'infrastruttura. Inoltre, l'automazione ha fatto sì che i nostri team DevOps potessero riposare in pace nelle ore più strane.

Un altro vantaggio aggiunto dell'automazione è che apre diverse strade per modernizzare e migliorare la produttività degli sviluppatori. Approfondiremo questo particolare argomento nella sezione successiva sulla continuous delivery.

3. Integrazione, consegna e implementazioni continue

I rilasci possono diventare un incubo doloroso se non si dispone di pipeline, strumenti e linee guida adeguati. Ciò significa che la spina dorsale fondamentale di DevOps è costituita dalle pipeline CI/CD che ci aiutano a testare, integrare, costruire e distribuire il nostro codice in produzione.

Volevamo essere in grado di eseguire più distribuzioni di produzione in un giorno e di farlo senza soluzione di continuità. Se si rompeva, volevamo ripristinare gli aggiornamenti senza problemi e in modo rapido.

Come accennato in precedenza, i nostri sviluppatori avevano fatto un ottimo lavoro per scomporre i servizi monolitici in microservizi, aprendo così le porte al mondo nativo-cloud. Tuttavia, questo ha comportato una serie di sfide. La sfida più grande che abbiamo affrontato è stata quella dei test di integrazione.

Inoltre, la nostra architettura e alcuni eccellenti strumenti cloud-native ci hanno permesso di affrontare lo sviluppo del software in modo innovativo. Stavamo stravolgendo le convenzioni tradizionali per adattarci al nuovo ed entusiasmante mondo dei microservizi.

Iniziare con la pratica consolidata di avere più ambienti ci ha permesso di testare e analizzare il nostro portafoglio di funzionalità prima di distribuirlo in produzione. Una volta terminato il lavoro su particolari funzionalità, bug o miglioramenti, i nostri sviluppatori hanno aperto una richiesta di pull nel ramo "dev" di git.

Una volta approvata la PR, i commit sono stati uniti al ramo dev. Abbiamo previsto un meccanismo di attivazione manuale per distribuire il ramo dev agli sviluppatori. (che esploreremo in seguito)

La base di codice di questo ramo è stata distribuita nell'ambiente per sviluppatori, un ambiente dedicato agli sviluppatori per testare le loro funzionalità all'avanguardia. C'erano due ambienti di sviluppo, uno per gli sviluppatori per testare le loro funzionalità e i microservizi e l'altro per i test di integrazione.

Una volta testata la base di codice e approvata dal team QA, i commit sono stati ulteriormente uniti nel ramo "staging o uat". Da qui, la base di codice è stata distribuita (attivata manualmente) nell'ambiente di staging, che abbiamo cercato di mantenere il più vicino possibile alla produzione.

C'era un altro gruppo di occhi su Staging prima di considerare quali funzionalità e microservizi sarebbero stati inviati alla produzione. Avevamo un ambiente diverso, l'ambiente demo, riservato esclusivamente alla dimostrazione dei nostri servizi ai potenziali clienti interessati ad esplorarli.

Abbiamo esaminato e analizzato i migliori strumenti possibili per il CI/CD e alla fine abbiamo scelto Github Actions. In breve, Github Actions ci ha fornito un ambiente integrato che si integra bene con il flusso di lavoro di GitHub e che prevede un onboarding minimo per i team di sviluppatori.

Oltre alla stretta integrazione, ci ha fornito anche lo stato dell'arte dei meccanismi CI/CD. È superveloce, conveniente, facile da configurare e personalizzare. Inoltre, la sintassi basata su YAML per definire i lavori è molto semplice e ci consente di eseguire il commit della definizione CI/CD con la base di codice e di tenerne traccia con le meraviglie di git.

a. Integrazione continua

Abbiamo esplorato alcuni modi per attivare automaticamente le build, ma abbiamo capito subito che è meglio lasciare che siano gli sviluppatori a controllare quando costruire. Ci possono essere molte ragioni a favore o contro la distribuzione automatica e quella manuale.

È meglio allineare le aspettative dei team e prendere questa decisione in base alla convenienza, poiché un'automazione eccessiva presenta potenziali insidie.

Per i nostri ambienti non di produzione, avevamo un trigger manuale per costruire e distribuire le ultime build nei rispettivi ambienti. Nei nostri ambienti di produzione, le build venivano attivate automaticamente in seguito al push di un tag git. I tag Git ci hanno permesso di attenerci a uno schema di versioning e di rendere conveniente il rollback delle versioni in produzione, se necessario.

Tralasciando i test unitari e i test funzionali che gli sviluppatori hanno eseguito sui loro commit, concentriamoci sul lato infrastrutturale delle cose nel nostro CI/CD. Il job di compilazione, una volta attivato, si autentica con ECR, gestisce la cache delle immagini ed esegue il comando docker build per creare un'immagine del container.

L'immagine del contenitore viene inviata al rispettivo repository ECR. Si tratta di una configurazione semplice ma estremamente potente e flessibile, in quanto ci consente di affidare il trigger agli sviluppatori automatizzando l'intero processo.

b. Distribuzioni continue

Abbiamo fornito agli sviluppatori un trigger manuale per distribuire le ultime build nei rispettivi ambienti. Abbiamo usato i tag delle immagini Docker per distinguere tra le immagini dei container per il singolo ramo git e l'ambiente.

I nostri microservizi girano su Kubernetes. Un tipico lavoro di deploy esegue le seguenti operazioni:

  1. Aggiornamento dei file di configurazione e delle variabili: cancelliamo le configmap esistenti e le ricreiamo, poiché Kubernetes non aggiorna una configmap esistente. I nostri file di configurazione sono inseriti in un repository git privato e le informazioni sensibili sono memorizzate su AWS Secrets Manager. Pertanto, git ci consente di tenere traccia dei file di configurazione, rendendo più semplice l'eliminazione e la ricreazione delle configmap in Kubernetes.
  2. Aggiornare la distribuzione e il servizio su Kubernetes (nel caso in cui ci siano modifiche ai file Yaml)
  3. Infine, eseguire un rollout del deployment di Kubernetes, perché Kubernetes non aggiorna automaticamente i deployments/pods solo per le modifiche ai file di configurazione (configmaps).

Abbiamo utilizzato le seguenti risorse della comunità di azioni GitHub per aiutarci a connetterci, autenticarci e comunicare facilmente con EKS/ECR e Docker.

4. L'architettura

Abbiamo utilizzato AWS per le nostre esigenze di infrastruttura. AWS ci ha fornito un ricco ecosistema di servizi che gestisce in modo efficiente il nostro parco di microservizi e il relativo backend e middleware.

These microservices are running atop Kubernetes managed by AWS EKS. AWS EKS offering saves us a lot of time and effort that Xoxoday would otherwise spend on managing the lifecycle of the Kubernetes cluster itself. That allows us to take advantage of the cloud native ecosystem while focusing on our services and working on them.

Kubernetes porta con sé circa un decennio di esperienza nella gestione dell'infrastruttura globale di Google. Ci fornisce diverse funzionalità. Per citarne alcune: Auto-healing, autoscaling, alta disponibilità, consolidando e aumentando l'efficienza con cui utilizziamo la nostra infrastruttura sottostante. Utilizziamo l'ingress per esporre i nostri servizi al mondo esterno attraverso un mix di bilanciatori di carico applicativi e classici e l'automazione dell'inserimento DNS di route53.

Questi servizi si collegano al nostro backend, che è ospitato in un mix di servizi gestiti da AWS come Kafka gestito, RDS e istanze EC2 autonome. Per la configurazione automatizzata di EKS sfruttiamo fortemente Terraform, un mix unico di Terraform e AWS Launch Templates per creare le istanze EC2 e gestirle automaticamente.

Inoltre, SaltStack viene utilizzato per le complesse manovre di gestione della configurazione automatizzata del parco istanze EC2. Ci permette di gestire, aggiornare e mantenere automaticamente il sistema operativo e i servizi che vi girano sopra. SaltStack dispone di un robusto set di funzionalità e di un'architettura brillantemente progettata, flessibile e collegabile. Provvediamo quindi automaticamente al provisioning dei servizi e prendiamo le impostazioni del backend appena configurate (indirizzi IP ecc.) e popoliamo i file di configurazione di kubernetes, aggiorniamo le configmap e le distribuiamo nell'ambiente.

Questo ci permette di configurare e riconfigurare i servizi stateless che girano nell'ambiente Kubernetes, in modo che vengano automaticamente forniti e configurati con le modifiche dinamiche alla configurazione del backend. Le nostre immagini dei container sono archiviate su AWS ECR, che ci fornisce un altro fantastico servizio che si integra perfettamente con il nostro set di tecnologie e la nostra architettura.

5. Registrazione, monitoraggio, allerta e APM

Saremmo ciechi se non avessimo i meccanismi di feedback appropriati per capire cosa sta succedendo nella nostra infrastruttura, soprattutto se stiamo parlando di uno scenario Polycloud.

a. Elasticsearch/Fluentd/Kibana

Utilizziamo l'esclusivo stack EFK per i nostri problemi di registrazione. Questo ci permette di creare e mantenere un insieme dinamico di microservizi in esecuzione su container e macchine virtuali, conservando i log in una pipeline centralizzata.

Questo ci consente di accedere ai registri delle istanze/contenitori che vengono distrutti per vari motivi. In questo modo è più facile accedere in modo sensato e risolvere diversi problemi in produzione. Ecco altre alternative a elasticsearch da scegliere.

b. Grafana/Prometheus

La dashboard Grafana, insieme al database di serie temporali Prometheus, ci permette di conservare vari dettagli sulla nostra infrastruttura, come CPU/RAM/Storage, ecc. e ci tiene aggiornati sullo stato attuale dell'infrastruttura in tempo quasi reale.

Questo ci permette di implementare meccanismi di allerta, rendendo più semplice per il nostro team DevOps la gestione degli incidenti nella nostra infrastruttura. Nonostante tutti i preparativi e la migliore architettura e progettazione, le cose possono ancora rompersi. I sistemi Prometheus di Grafana, insieme ad Alertmanager, consentono di mitigare gli incidenti in produzione.

Inoltre, questo stack beneficia dell'Application Programming Management, che ci fornisce una ricca serie di metriche che ci permettono di avere una visione critica dei prodotti, del loro utilizzo e altro ancora.

6. Disturbo degli ambienti di sviluppo

Since we were walking on the path of microservices, we had to manage over 40 microservices which when orchestrated together to form our web service www.xoxoday.com. This implies a lot of communication between these microservices, and the sheer number of them makes it impossible and impractical to run it locally on the developer’s machine.

Per questo motivo, abbiamo dovuto essere creativi e tornare nelle acque profonde del mondo native-cloud per trovare una possibile soluzione. Ne abbiamo viste molte, per citarne alcune:

- Telepresenza

- Skaffold

- Kubefwd

Al momento, il nostro preferito e il più utile per le nostre esigenze è Kubefwd, ma potremmo esplorare altri strumenti in futuro. Kubefwd ci permette di rendere accessibili i servizi Kubernetes sulla workstation locale utilizzando lo stesso DNS come se la macchina dello sviluppatore locale si trovasse all'interno del cluster Kubernetes!

Questo significa un incredibile aumento della produttività che rende gli sviluppatori più efficienti, migliorando al contempo l'esperienza complessiva e riducendo il tempo necessario per introdurre una funzionalità nel mondo.

7. Conclusione

Dopo aver esaminato per mesi le nostre esigenze infrastrutturali e averle allineate con i requisiti aziendali, la crescita futura e le tendenze attuali nel mondo delle infrastrutture e del cloud-native, siamo finalmente giunti a un punto in cui possiamo guardare con fiducia ai giorni che ci attendono.

La nostra infrastruttura non solo ha compiuto un salto evolutivo di dieci anni, ma è anche riuscita a ridurre il carico sui team DevOps, aumentando drasticamente la produttività degli sviluppatori. Infine, la nostra configurazione è più economica, fa di più e lo fa meglio!

Questa configurazione ci permette di replicare facilmente l'intero set di servizi, frontend e backend e di configurarsi automaticamente in pochi minuti. Questo ci permetterà di creare più ambienti di produzione atomici e indipendenti, di adattarli alle nostre pipeline CI/CD e di gestire e mantenere automaticamente la complessa configurazione con facilità.

Inoltre, il vantaggio più importante di tutto questo è che i nostri team DevOps possono rimanere piccoli e scalare linearmente, fornendo al contempo una scala globale esponenziale per i nostri servizi. E la ciliegina sulla torta è che il team DevOps può trascorrere serate tranquille e weekend fantastici senza dover combattere i problemi di produzione su base reattiva, poiché il nostro design e la nostra architettura ci consentono di affrontare queste sfide in modo proattivo.

Uno degli aspetti più interessanti della nostra configurazione è che la natura automatizzata ci consente di utilizzare istanze spot per i nostri sistemi non di produzione e non critici. Questo ci aiuta a ottimizzare e consolidare ulteriormente i costi, pur avendo a disposizione un'ampia potenza di calcolo. Questo significa configurazioni altamente efficienti dal punto di vista dei costi e, nel caso in cui AWS decida di spegnere le nostre istanze spot, non c'è problema: in pochi minuti, autoscaleremo le nostre istanze dedicate e otterremo il meglio da entrambi i mondi!

Le attuali tendenze nel mondo delle infrastrutture, in particolare guardando agli sviluppi del Cloud Native, rendono il decennio che ci attende entusiasmante per DevOps; sembra che siamo vicini a raggiungere il nirvana. L'enorme numero di strumenti, soluzioni, servizi ed ecosistemi che stanno nascendo può essere a volte schiacciante. Tuttavia, in retrospettiva, ci stiamo dirigendo verso un'era entusiasmante di servizi web solidi, robusti e sempre disponibili, che offrono un'esperienza fantastica agli utenti.

Pranav Salunke