Cosa sappiamo sui microservizi

Ciao! Mi chiamo Vadim Madison, e sono responsabile dello sviluppo della System Platform di Avito. Si è parlato diverse volte di come la nostra azienda stia passando da un'architettura monolitica a una microservizio. È ora di condividere come abbiamo trasformato la nostra infrastruttura per massimizzare i benefici dei microservizi senza perderci. In che modo il PaaS ci aiuta, come abbiamo semplificato il deployment e ridotto la creazione di un microservizio a un solo clic — continua a leggere. Non tutto ciò di cui scrivo di seguito è ancora pienamente implementato in Avito, ma è parte del nostro sviluppo della piattaforma.

(E alla fine di questo articolo parlerò della possibilità di partecipare a un seminario di tre giorni tenuto dall'esperto di architettura microservizi Chris Richardson).

Cosa sappiamo sui microservizi

Come siamo arrivati ai microservizi

Avito è uno dei più grandi siti di annunci al mondo, con oltre 15 milioni di nuovi annunci pubblicati ogni giorno. Il nostro backend gestisce oltre 20.000 richieste al secondo. Attualmente abbiamo diverse centinaia di microservizi.

Stiamo costruendo un'architettura microservizi da diversi anni. Come lo facciamo è spiegato dai nostri colleghi nei dettagli hanno raccontato nella nostra sezione su RIT++ 2017. A CodeFest 2017 (vedi. video), Sergey Orlov e Mikhail Prokopchuk hanno spiegato in dettaglio perché sia necessario passare ai microservizi e quale ruolo abbia avuto Kubernetes in questo processo. Ora ci stiamo impegnando a ridurre al minimo i costi di scalabilità tipici di questa architettura.

Inizialmente non abbiamo creato un ecosistema che ci aiutasse in modo completo nello sviluppo e nel lancio di microservizi. Ci limitavamo a raccogliere soluzioni open source utili, a lanciarle internamente e a lasciare ai programmatori il compito di capire come funzionano. Di conseguenza, si ritrovavano a dover navigare tra decine di strumenti (dashboard, servizi interni), rafforzando così la loro tendenza a continuare a scrivere codice nel modo tradizionale, nel monolite. In verde nei diagrammi sottostanti è evidenziato ciò che lo sviluppatore fa manualmente in un modo o nell'altro, mentre in giallo c'è l'automazione.

Cosa sappiamo sui microservizi

Attualmente, nel CLI del PaaS, è possibile creare un nuovo servizio con un solo comando, aggiungere un nuovo database con ulteriori due comandi e procedere con il deployment in Stage.

Cosa sappiamo sui microservizi

Come superare l'era della «frammentazione dei microservizi»

Con un'architettura monolitica, per garantire la coerenza delle modifiche nel prodotto, gli sviluppatori erano costretti a capire cosa stesse succedendo nei servizi vicini. Con la nuova architettura, i contesti dei servizi hanno smesso di dipendere l'uno dall'altro.

Inoltre, affinché l'architettura a microservizi sia efficace, è necessario impostare numerosi processi, ovvero:

• registrazione;
• tracciamento delle richieste (Jaeger);
• aggregazione degli errori (Sentry);
• stati, messaggi ed eventi da Kubernetes (Event Stream Processing);
• limite di race / circuit breaker (si può usare Hystrix);
• controllo della connettività dei servizi (utilizziamo Netramesh);
• monitoraggio (Grafana);
• build (TeamCity);
• comunicazioni e notifiche (Slack, email);
• tracciamento delle attività (Jira);
• creazione della documentazione.

Per garantire che, man mano che il sistema scalava, non perdesse integrità e continuasse a essere efficace, abbiamo ripensato all'organizzazione del lavoro dei microservizi in Avito.

Come gestiamo i microservizi

Aderire a una «politica comune» tra i numerosi microservizi di Avito è supportato da:

  • separazione dell'infrastruttura in strati;
  • concetto di Platform as a Service (PaaS);
  • monitoraggio di tutto ciò che accade con i microservizi.

I livelli di astrazione dell'infrastruttura includono tre strati. Procediamo dall'alto verso il basso.

A. Superiore — service mesh. Inizialmente abbiamo provato Istio, ma ci siamo resi conto che consuma troppe risorse, il che diventa troppo costoso con i nostri volumi. Pertanto, il nostro senior engineer del team di architettura, Aleksandr Luk'jančenko, ha sviluppato una soluzione interna — Netramesh (disponibile in Open Source), che attualmente utilizziamo in produzione e consuma diverse volte meno risorse rispetto a Istio (anche se non fa tutto ciò di cui Istio può vantarsi).
B. Intermedio — Kubernetes. Su cui distribuiamo ed eseguiamo i microservizi.
C. Inferiore — bare metal. Non utilizziamo cloud e cose come OpenStack, ma siamo completamente su bare metal.

Tutti gli strati si uniscono sotto PaaS. E questa piattaforma, a sua volta, è composta da tre parti.

I. Generatori, gestiti tramite uno strumento CLI. È proprio questo strumento che aiuta lo sviluppatore a creare un microservizio in modo corretto e con il minimo sforzo.

II. Collector aggregato con il monitoraggio di tutti gli strumenti tramite un dashboard comune.

III. Archiviazione. Integra con pianificatori che impostano automaticamente i trigger per azioni significative. Grazie a questo sistema, nessun compito viene trascurato solo perché qualcuno ha dimenticato di crearsi un task in Jira. Per questo utilizziamo uno strumento interno chiamato Atlas.

Cosa sappiamo sui microservizi

L'implementazione dei microservizi in Avito segue inoltre uno schema unico, che semplifica il controllo su ciascuna fase di sviluppo e rilascio.

Come funziona un normale pipeline di sviluppo di un microservizio

In linea generale, la catena di creazione di un microservizio appare come segue:

CLI-push → Integrazione Continua → Bake → Deploy → Test artificiali → Test Canary → Squeeze Testing → Produzione → Manutenzione.

Esaminiamo i vari passaggi seguendo esattamente questa sequenza.

CLI-push

• Creazione del microservizio.
Abbiamo lavorato a lungo per insegnare a ciascun sviluppatore come creare microservizi. Abbiamo anche scritto istruzioni dettagliate in Confluence. Ma gli schemi cambiavano e venivano aggiornati. Risultato: si è creato un collo di bottiglia all'inizio del processo: ci voleva molto più tempo del previsto per avviare i microservizi, e si presentavano comunque frequentemente problemi durante la loro creazione.

Alla fine abbiamo realizzato una semplice utilità CLI che automatizza i passaggi fondamentali per creare un microservizio. In effetti, essa sostituisce il primo git push. Ecco cosa fa esattamente.

— Crea un servizio a partire da un modello, passo dopo passo, in modalità ‘wizard’. Abbiamo modelli per i principali linguaggi di programmazione nel backend di Avito: PHP, Golang e Python.

— In un solo comando implementa un ambiente di sviluppo locale su una specifica macchina: Minikube viene avviato, i chart Helm vengono generati e avviati automaticamente nel kubernetes locale.

— Collega il database necessario. Lo sviluppatore non ha bisogno di conoscere l'IP, il login e la password per accedere al database di cui ha bisogno, sia esso locale, in Stage o in produzione. Inoltre, il database viene implementato immediatamente in una configurazione tollerante ai guasti e con bilanciamento.

— Esegue autonomamente il live-build. Supponiamo che lo sviluppatore modifichi qualcosa nel microservizio tramite il proprio IDE. L'utilità rileva le modifiche nel file system e, sulla base di esse, ricompila l'applicazione (per Golang) e la riavvia. Per PHP, semplicemente rimandiamo la directory all'interno del cube e lì il live-reload avviene ‘automagicamente’.

— Genera test automatici. Presentati come modelli, ma del tutto utilizzabili.

• Deploy di microservizi.

Fare il deploy di un microservizio da noi era un po' complicato. Erano sempre necessari:

I. Dockerfile.

II. Configurazione.
III. Helm chart, che di per sé è ingombrante e include:

— i chart stessi;
— i template;
— valori specifici tenendo conto dei diversi ambienti.

Abbiamo eliminato il problema della riscrittura dei manifesti Kubernetes, e ora vengono generati automaticamente. Ma soprattutto, abbiamo semplificato al massimo il deploy. Da ora in poi abbiamo un Dockerfile, e tutta la configurazione viene scritta dallo sviluppatore in un unico e breve file app.toml.

Cosa sappiamo sui microservizi

Inoltre, anche nel file app.toml ora ci vogliono solo un minuto. Scriviamo quante copie del servizio avviare (sul server di sviluppo, su staging, in produzione), specificando le sue dipendenze. Fate attenzione alla riga size = "small" nel blocco [engine]. Questo è il limite che verrà assegnato al servizio tramite Kubernetes.

Successivamente, in base alla configurazione, vengono generati automaticamente tutti i necessari Helm chart e vengono create le connessioni ai database.

• Validazione di base. Anche queste verifiche sono automatizzate.
Occorre tenere d'occhio:
— se c'è un Dockerfile;
— se c'è un app.toml;
— esiste della documentazione;
— le dipendenze sono a posto;
— sono definite le regole degli alert.
Riguardo all'ultimo punto: il proprietario del servizio specifica quali metriche di prodotto monitorare.

• Preparazione della documentazione.
Fino ad ora è un punto critico. Sembra la cosa più ovvia, ma è anche incredibilmente «spesso dimenticata», e quindi un anello vulnerabile della catena.
È necessario avere documentazione per ciascun microservizio. Include i seguenti blocchi.

I. Breve descrizione del servizio. Solo poche frasi su cosa fa e a cosa serve.

II. Link al diagramma architettonico. È importante che possa essere facilmente compreso a colpo d'occhio, ad esempio se state utilizzando Redis per la memorizzazione nella cache o come principale archiviazione dei dati in modalità persistente. Su Avito attualmente è un link su Confluence.

III. Runbook. Una breve guida per avviare il servizio e sulle sue peculiarità.

IV. FAQ, dove sarebbe utile anticipare i problemi con cui i vostri colleghi potrebbero trovarsi ad affrontare durante l'utilizzo del servizio.

V. Descrizione degli endpoint per l'API. Se non hai specificato le destinazioni, è quasi certo che i tuoi colleghi, i cui microservizi sono collegati al tuo, dovranno sostenere i costi. Attualmente utilizziamo Swagger e la nostra soluzione chiamata brief per questo.

VI. Etichette. Oppure marcatori che indicano quale prodotto, funzionalità o dipartimento aziendale riguarda il servizio. Aiutano a comprendere rapidamente, ad esempio, se stai sviluppando una funzionalità già rilasciata una settimana fa da altri colleghi per lo stesso business unit.

VII. Proprietario o proprietari del servizio. Nella maggior parte dei casi, il loro nome può essere determinato automaticamente tramite PaaS, ma per maggiore sicurezza richiediamo agli sviluppatori di specificarli manualmente.

Infine, è una buona pratica eseguire una revisione della documentazione, in analogia con la revisione del codice.

Continuous Integration.

  • Preparazione dei repository.
  • Creazione di un pipeline in TeamCity.
  • Impostazione dei diritti.
  • Ricerca dei proprietari del servizio. Qui c'è uno schema ibrido: etichettatura manuale e automazione minima da PaaS. Uno schema completamente automatico presenta problemi quando i servizi vengono trasferiti al supporto di un'altra squadra di sviluppo o, ad esempio, se lo sviluppatore del servizio si è dimesso.
  • Registrazione del servizio in Atlas (vedi sopra). Con tutti i suoi proprietari e dipendenze.
  • Verifica delle migrazioni. Controlliamo se tra di esse ci sono potenziali pericoli. Ad esempio, in una di esse potrebbe apparire un alter table o qualcosa in grado di compromettere la compatibilità dello schema dati tra diverse versioni del servizio. In tal caso, la migrazione non verrà eseguita, ma messa in attesa — PaaS dovrebbe avvisare il proprietario del servizio quando sarà sicuro applicarla.

Bake

La fase successiva è l'imballaggio dei servizi prima del deploy.

  • Compilazione dell'applicazione. Secondo la tradizione — in un'immagine Docker.
  • Generazione di chart Helm per il servizio stesso e per le risorse correlate. Inclusi database e cache. Vengono creati automaticamente in base a quella configurazione app.toml, che è stata generata nella fase di CLI-push.
  • Creazione di ticket per gli amministratori per l'apertura delle porte (quando necessario).
  • Esecuzione di unit test e calcolo della code coverage. Se la copertura del codice è al di sotto della soglia stabilita, è probabile che il servizio non superi il deploy. Se è al limite del consentito, al servizio sarà assegnato un coefficiente "pessimizzante": in questo caso, se nel tempo non ci sono miglioramenti della metrica, lo sviluppatore riceverà una notifica che indica che non ci sono progressi nei test (e che sarebbe opportuno fare qualcosa al riguardo).
  • Considerazione dei limiti di memoria e CPU. Principalmente scriviamo microservizi in Golang e li eseguiamo in Kubernetes. Da qui una particolarità legata alla lingua Golang: per impostazione predefinita, all'avvio vengono utilizzati tutti i core della macchina, a meno che la variabile GOMAXPROCS non venga impostata esplicitamente, e quando più servizi vengono avviati su una sola macchina, iniziano a competere per le risorse, interferendo tra loro. Nei grafici sottostanti è mostrato come cambia il tempo di esecuzione se l'applicazione viene avviata senza concorrenza e in modalità di corsa per le risorse. (I sorgenti dei grafici sono disponibili) qui).

Cosa sappiamo sui microservizi

Tempo di esecuzione, inferiore è meglio. Massimo: 643 ms, minimo: 42 ms. Foto cliccabile.

Cosa sappiamo sui microservizi

Tempo per operazione, inferiore è meglio. Massimo: 14091 ns, minimo: 151 ns. Foto cliccabile.

Nella fase di preparazione della build, è possibile impostare esplicitamente questa variabile o utilizzare la libreria. automaxprocs da parte dei ragazzi di Uber.

Deploy

• Verifica delle convenzioni. Prima di iniziare a distribuire le build del servizio negli ambienti previsti, è necessario controllare quanto segue:
— API endpoints.
— Conformità delle risposte degli API endpoints allo schema.
— Formato dei log.
— Impostazione delle intestazioni nelle richieste al servizio (attualmente gestito da netramesh).
— Impostazione del marcatore del proprietario quando si inviano messaggi al bus eventi. Questo è necessario per monitorare la coerenza tra i servizi attraverso il bus. Possono essere inviati sia dati idempotenti, che non aumentano la coerenza dei servizi (cosa positiva), sia dati aziendali, che rafforzano la coerenza dei servizi (cosa molto negativa!). E nel momento in cui questa coerenza diventa un problema, comprendere chi scrive e legge dal bus aiuta a separare correttamente i servizi.

Attualmente ci sono poche convenzioni in Avito, ma il loro numero sta aumentando. Più sono gli accordi simili in una forma comprensibile e conveniente per il team, più semplice sarà mantenere la coerenza tra i microservizi.

Test sintetici

• Test in un contesto chiuso. Per questo attualmente utilizziamo l'open source Hoverfly.io. Prima registra il carico reale sul servizio, poi lo emula proprio nel circuito chiuso.

• Test di carico. Cerchiamo di ottimizzare le performance di tutti i servizi. E tutte le versioni di ciascun servizio devono essere sottoposte a test di carico: in questo modo possiamo comprendere le performance attuali e le differenze rispetto alle versioni precedenti dello stesso servizio. Se, dopo un aggiornamento, le performance del servizio calano di 1,5 volte, è un chiaro segnale per i suoi proprietari: è necessario esaminare il codice e risolvere la situazione.
Dai dati raccolti partiamo, per esempio, per implementare correttamente l'auto scaling e, alla fine, comprendere quanto bene possa essere scalato il servizio.

Durante il test di carico verifichiamo se il consumo delle risorse rispetta i limiti impostati. Ci concentriamo principalmente sugli estremi.

a) Guardiamo il carico totale.
— Troppo basso — probabilmente qualcosa non funziona affatto, se il carico è improvvisamente diminuito di diversi fattori.
— Troppo alto — è necessaria un'ottimizzazione.

b) Guardiamo il cutoff per RPS.
Qui confrontiamo la differenza tra la versione attuale e quella precedente, così come il numero totale. Ad esempio, se il servizio produce 100 rps, potrebbe essere male codificato o semplicemente far parte delle sue specifiche, ma in ogni caso, è un motivo valido per esaminare attentamente il servizio.
Se invece l'RPS è troppo alto, potrebbe esserci un bug e uno degli endpoint potrebbe aver smesso di eseguire il carico utile, attivando solo un qualche tipo di allerta. return true;

Test Canary

Dopo aver completato i test sintetici, testiamo il funzionamento del microservizio su un numero ridotto di utenti. Iniziamo con cautela, con una piccola porzione della potenziale audience del servizio — meno dello 0,1%. In questa fase, è molto importante che il monitoraggio disponga di metriche tecniche e di prodotto corrette, in modo che possano mostrare rapidamente eventuali problemi nel servizio. La durata minima del test Canary è di 5 minuti, quella principale di 2 ore. Per i servizi complessi, impostiamo il tempo manualmente.
Analizziamo:
— metriche specifiche per il linguaggio, in particolare per i worker php-fpm;
— errori in Sentry;
— stati delle risposte;
— tempi di risposta (response time), sia esatti che medi;
— latenza;
— eccezioni, sia gestite che non gestite;
— metriche del prodotto.

Squeeze Testing

Lo Squeeze Testing è anche noto come test di "estrusione". Questa metodologia è stata introdotta da Netflix. La sua essenza consiste nel riempire inizialmente un'istanza con traffico reale fino a portarla al collasso, stabilendo così il suo limite. Successivamente, aggiungiamo un'altra istanza e carichiamo entrambe fino al massimo; osserviamo il loro tetto e la delta rispetto al primo "squeeze". E così continuiamo ad aggiungere istanze passo dopo passo, calcolando le regolarità nelle variazioni.
I dati dei test di "estrusione" vengono aggregati in un'unica base di metriche, dove possiamo arricchire i risultati del carico artificiale o addirittura sostituirli con la "sintetica".

Produzione

• Scalabilità. Durante il rollout del servizio in produzione, monitoriamo come si scala. Monitorare solo le metriche CPU, secondo la nostra esperienza, è inefficace. L'auto scaling con il benchmarking RPS funziona in puro, ma solo per singoli servizi, come lo streaming online. Pertanto, ci concentriamo prima sulle metriche del prodotto specifiche per l'applicazione.

Di conseguenza, durante la scalabilità analizziamo:
— le metriche CPU e RAM,
— numero di richieste in coda,
— tempo di risposta,
— previsione basata su dati storici accumulati.

Nella scalabilità del servizio, è anche importante monitorare le sue dipendenze, per evitare che si verifichi il caso in cui stiamo scalando il primo servizio nella catena, ma quelli a cui si rivolge falliscono sotto carico. Per stabilire un carico accettabile per l'intero pool di servizi, osserviamo i dati storici del servizio dipendente 'più vicino' (in base alla combinazione di metriche CPU e RAM insieme a metriche specifiche dell'app) e li confrontiamo con i dati storici del servizio iniziale, e così via lungo tutta la 'catena delle dipendenze', dall'alto verso il basso.

Manutenzione

Dopo che il microservizio è stato messo in funzione, possiamo aggiungere dei trigger.

Ecco situazioni tipiche in cui scattano i trigger.
— Sono state rilevate migrazioni potenzialmente pericolose.
— Sono stati rilasciati aggiornamenti di sicurezza.
— Il servizio non è stato aggiornato da tempo.
— Il carico sul servizio è diminuito significativamente o alcune delle sue metriche di prodotto superano i limiti normali.
— Il servizio ha smesso di soddisfare i nuovi requisiti della piattaforma.

Una parte dei trigger è responsabile della stabilità operativa, mentre un'altra funge da funzione di manutenzione del sistema — per esempio, un servizio che non è stato deployed da tempo e la sua immagine base ha smesso di superare le verifiche di sicurezza.

Dashboard

In breve, la dashboard è il pannello di controllo di tutto il nostro PaaS.

  • Un punto unico di informazioni sul servizio, con dati sulla sua copertura di test, numero delle sue immagini, numero delle copie in produzione, versioni, ecc.
  • Strumento di filtraggio dei dati per servizi e labels (marcatori di appartenenza a business unit, funzionalità di prodotto, ecc.)
  • Strumento di integrazione con strumenti infrastrutturali per tracciamento, logging e monitoraggio.
  • Un punto centrale di documentazione per i servizi.
  • Un punto centrale per la panoramica di tutti gli eventi relativi ai servizi.

Cosa sappiamo sui microservizi
Cosa sappiamo sui microservizi
Cosa sappiamo sui microservizi
Cosa sappiamo sui microservizi

Totale

Prima dell'implementazione del PaaS, un nuovo sviluppatore poteva impiegare diverse settimane per capire tutti gli strumenti necessari per avviare un microservizio in produzione: Kubernetes, Helm, le specifiche interne di TeamCity, la configurazione delle connessioni a database e cache in modalità failover, ecc. Ora sono sufficientore due ore — basta leggere il quickstart e realizzare il servizio stesso.

Ho tenuto una presentazione su questo argomento per HighLoad++ 2018, puoi darci un'occhiata video e una presentazione.

Bonus track per chi arriva fino alla fine

In Avito organizziamo un corso di formazione interno di tre giorni per sviluppatori tenuto da Chris Richardson, esperto di architettura a microservizi. Vogliamo offrire la possibilità di partecipare a qualcuno dei lettori di questo post. Qui Il programma del corso è disponibile.

Il corso si svolgerà dal 5 al 7 agosto a Mosca. Questi sono giorni lavorativi che saranno interamente dedicati al corso. Il pranzo e le lezioni si terranno nel nostro ufficio, mentre il viaggio e l'alloggio saranno a carico del partecipante selezionato.

Puoi candidarti per partecipare in questo modulo Google. Dovrai rispondere a una domanda su perché dovresti partecipare al corso e fornire informazioni su come contattarti. Rispondi in inglese, perché Chris sceglierà il partecipante al corso personalmente.
Annunceremo il nome del partecipante al corso con un aggiornamento di questo post e sui social media di Avito per sviluppatori (AvitoTech su Facebook, Vkontakte, Twitter) entro il 19 luglio.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster