Cosa sappiamo dei microservizi

Ciao! Mi chiamo Vadim Madison, guido lo sviluppo della piattaforma di sistema Avito. Si è detto più di una volta come noi in azienda stiamo passando da un’architettura monolitica a quella a microservizi. È tempo di condividere come abbiamo trasformato la nostra infrastruttura per ottenere il massimo dai microservizi ed evitare di perderci. In che modo PaaS ci aiuta in questo caso, come abbiamo semplificato l'implementazione e ridotto la creazione di un microservizio a un clic: continua a leggere. Non tutto ciò di cui scrivo di seguito è completamente implementato in Avito, in parte dipende dal modo in cui sviluppiamo la nostra piattaforma.

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

Cosa sappiamo dei microservizi

Come siamo arrivati ​​ai microservizi

Avito è uno dei siti di annunci più grandi al mondo; ogni giorno vengono pubblicati più di 15 milioni di nuovi annunci. Il nostro backend accetta più di 20mila richieste al secondo. Attualmente disponiamo di diverse centinaia di microservizi.

Stiamo costruendo un'architettura di microservizi ormai da diversi anni. Come esattamente: i nostri colleghi in dettaglio detto nella nostra sezione al RIT++ 2017. Al CodeFest 2017 (vedi. video), Sergey Orlov e Mikhail Prokopchuk hanno spiegato in dettaglio perché avevamo bisogno del passaggio ai microservizi e quale ruolo ha giocato Kubernetes in questo contesto. Bene, ora stiamo facendo tutto il possibile per ridurre al minimo i costi di scalabilità inerenti a tale architettura.

Inizialmente, non abbiamo creato un ecosistema che ci aiutasse in modo completo a sviluppare e lanciare microservizi. Hanno semplicemente raccolto soluzioni open source sensate, le hanno lanciate a casa e hanno invitato lo sviluppatore a occuparsene. Di conseguenza, è andato in una dozzina di posti (cruscotti, servizi interni), dopo di che il suo desiderio di tagliare il codice alla vecchia maniera, in un monolite, è diventato più forte. Il colore verde nei diagrammi seguenti indica ciò che lo sviluppatore fa in un modo o nell'altro con le proprie mani e il colore giallo indica l'automazione.

Cosa sappiamo dei microservizi

Ora nell'utilità CLI PaaS viene creato un nuovo servizio con un comando e un nuovo database viene aggiunto con altri due e distribuito in Stage.

Cosa sappiamo dei microservizi

Come superare l'era della "frammentazione dei microservizi"

Con un'architettura monolitica, per motivi di coerenza dei cambiamenti nel prodotto, gli sviluppatori sono stati costretti a capire cosa stava succedendo ai loro vicini. Quando si lavora sulla nuova architettura, i contesti di servizio non dipendono più l'uno dall'altro.

Inoltre, affinché un’architettura a microservizi sia efficace, è necessario stabilire molti processi, vale a dire:

• registrazione;
• richiedere il tracciamento (Jaeger);
• aggregazione errori (Sentry);
• stati, messaggi, eventi da Kubernetes (Event Stream Processing);
• limite di corsa/interruttore automatico (è possibile utilizzare Hystrix);
• controllo della connettività del servizio (usiamo Netramesh);
• monitoraggio (Grafana);
• assemblea (TeamCity);
• comunicazione e notifica (Slack, email);
• monitoraggio delle attività; (Jira)
• preparazione della documentazione.

Per garantire che il sistema non perda la sua integrità e rimanga efficace man mano che cresce, abbiamo ripensato l'organizzazione dei microservizi in Avito.

Come gestiamo i microservizi

I seguenti microservizi aiutano a implementare una "politica dei partiti" unificata tra molti microservizi Avito:

  • dividere l'infrastruttura in strati;
  • Concetto di piattaforma come servizio (PaaS);
  • monitorare tutto ciò che accade con i microservizi.

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

A. Top: rete di servizi. Inizialmente abbiamo provato Istio, ma si è scoperto che utilizza troppe risorse, il che è troppo costoso per i nostri volumi. Pertanto, l'ingegnere senior del team di architettura Alexander Lukyanchenko ha sviluppato la propria soluzione: Netramesh (disponibile in Open Source), che attualmente utilizziamo in produzione e che consuma molte volte meno risorse di Istio (ma non fa tutto ciò di cui Istio può vantarsi).
B. Medio: Kubernetes. Distribuiamo e gestiamo microservizi su di esso.
C. Fondo: metallo nudo. Non utilizziamo cloud o cose come OpenStack, ma ci affidiamo interamente al bare metal.

Tutti i livelli sono combinati da PaaS. E questa piattaforma, a sua volta, è composta da tre parti.

I. Generatori, controllato tramite un'utilità CLI. È lei che aiuta lo sviluppatore a creare un microservizio nel modo giusto e con il minimo sforzo.

II. Collezionista consolidato con controllo di tutti gli strumenti attraverso una dashboard comune.

III. Magazzinaggio. Si connette con pianificatori che impostano automaticamente i trigger per azioni significative. Grazie a un tale sistema, non viene persa una sola attività solo perché qualcuno ha dimenticato di impostare un'attività in Jira. Per questo utilizziamo uno strumento interno chiamato Atlas.

Cosa sappiamo dei microservizi

Anche l'implementazione dei microservizi in Avito viene effettuata secondo un unico schema, che semplifica il controllo su di essi in ogni fase di sviluppo e rilascio.

Come funziona una pipeline di sviluppo di microservizi standard?

In generale, la catena di creazione dei microservizi si presenta così:

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

Esaminiamolo esattamente in questo ordine.

CLI-push

• Creazione di un microservizio.
Abbiamo lottato a lungo per insegnare a ogni sviluppatore come realizzare microservizi. Ciò includeva la scrittura di istruzioni dettagliate in Confluence. Ma gli schemi sono cambiati e sono stati integrati. Il risultato è che all’inizio del viaggio è apparso un collo di bottiglia: ci è voluto molto più tempo per lanciare i microservizi, ma spesso sono sorti problemi durante la loro creazione.

Alla fine, abbiamo creato una semplice utility CLI che automatizza i passaggi di base durante la creazione di un microservizio. Di fatto sostituisce il primo git push. Ecco cosa fa esattamente.

— Crea un servizio secondo un modello — passo dopo passo, in modalità “procedura guidata”. Disponiamo di modelli per i principali linguaggi di programmazione nel backend Avito: PHP, Golang e Python.

- Un comando alla volta distribuisce un ambiente per lo sviluppo locale su una macchina specifica - Viene avviato Minikube, i grafici Helm vengono generati e avviati automaticamente in Kubernetes locali.

— Collega il database richiesto. Lo sviluppatore non ha bisogno di conoscere IP, login e password per accedere al database di cui ha bisogno, sia esso localmente, in Stage o in produzione. Inoltre, il database viene immediatamente distribuito in una configurazione tollerante agli errori e con bilanciamento.

— Esegue autonomamente l'assemblaggio dal vivo. Supponiamo che uno sviluppatore abbia corretto qualcosa in un microservizio tramite il suo IDE. L'utilità rileva le modifiche nel file system e, in base ad esse, ricostruisce l'applicazione (per Golang) e si riavvia. Per PHP, inoltriamo semplicemente la directory all'interno del cubo e lì si ottiene il live-reload “automaticamente”.

— Genera test automatici. Sotto forma di spazi vuoti, ma abbastanza adatti all'uso.

• Distribuzione di microservizi.

La distribuzione di un microservizio era un po' un compito ingrato per noi. Sono stati richiesti:

I.Dockerfile.

II. Configurazione
III. Grafico del timone, che di per sé è ingombrante e include:

— i grafici stessi;
— modelli;
— valori specifici che tengono conto di ambienti diversi.

Abbiamo eliminato la fatica di rielaborare i manifest Kubernetes in modo che ora vengano generati automaticamente. Ma soprattutto, hanno semplificato l'implementazione al limite. D'ora in poi abbiamo un Dockerfile e lo sviluppatore scrive l'intera configurazione in un unico breve file app.toml.

Cosa sappiamo dei microservizi

Sì, e nello stesso app.toml non c'è niente da fare per un minuto. Specifichiamo dove e quante copie del servizio generare (sul server di sviluppo, in staging, in produzione) e indichiamo le sue dipendenze. Notare la dimensione della linea = "piccolo" nel blocco [motore]. Questo è il limite che verrà assegnato al servizio tramite Kubernetes.

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

• Convalida di base. Anche tali controlli sono automatizzati.
È necessario monitorare:
- esiste un Dockerfile;
- c'è app.toml;
— è disponibile la documentazione?
— la dipendenza è in ordine?
— se sono state stabilite norme di allerta.
Fino all'ultimo punto: è il proprietario stesso del servizio a determinare quali metriche del prodotto monitorare.

• Preparazione della documentazione.
Ancora un'area problematica. Sembra essere la cosa più ovvia, ma allo stesso tempo è anche un primato “spesso dimenticato”, e quindi un anello vulnerabile della catena.
È necessario che sia presente documentazione per ciascun microservizio. Include i seguenti blocchi.

I. Breve descrizione del servizio. Letteralmente alcune frasi su cosa fa e perché è necessario.

II. Collegamento allo schema dell'architettura. È importante che con una rapida occhiata sia facile capire, ad esempio, se stai utilizzando Redis per la memorizzazione nella cache o come archivio dati principale in modalità persistente. In Avito per ora questo è un collegamento a Confluence.

III. Runbook. Una breve guida sull'avvio del servizio e le complessità della sua gestione.

IV. FAQ, dove sarebbe bene anticipare i problemi che i tuoi colleghi potrebbero incontrare lavorando con il servizio.

V. Descrizione degli endpoint per l'API. Se all'improvviso non hai specificato le destinazioni, quasi sicuramente ne pagheranno i colleghi i cui microservizi sono correlati ai tuoi. Ora usiamo Swagger e la nostra soluzione chiamata brief per questo.

VI. Etichette. Oppure indicatori che mostrano a quale prodotto, funzionalità o divisione strutturale dell'azienda appartiene il servizio. Ti aiutano a capire rapidamente, ad esempio, se stai tagliando funzionalità che i tuoi colleghi hanno implementato per la stessa business unit una settimana fa.

VII. Titolare o titolari del servizio. Nella maggior parte dei casi, essi (o essi) possono essere determinati automaticamente utilizzando PaaS, ma per essere sicuri, richiediamo allo sviluppatore di specificarli manualmente.

Infine, è buona pratica rivedere la documentazione, in modo simile alla revisione del codice.

Integrazione continua

  • Preparazione dei repository.
  • Creazione di una pipeline in TeamCity.
  • Impostazione dei diritti.
  • Cerca proprietari di servizi. Esiste uno schema ibrido qui: marcatura manuale e automazione minima da PaaS. Uno schema completamente automatico fallisce quando i servizi vengono trasferiti per il supporto a un altro team di sviluppo o, ad esempio, se lo sviluppatore del servizio lascia l'azienda.
  • Registrazione di un servizio in Atlas (vedi sopra). Con tutti i suoi proprietari e dipendenze.
  • Controllo delle migrazioni. Controlliamo se qualcuno di loro è potenzialmente pericoloso. Ad esempio, in uno di essi viene visualizzata una tabella di modifica o qualcos'altro che può interrompere la compatibilità dello schema dei dati tra le diverse versioni del servizio. Quindi la migrazione non viene eseguita, ma inserita in un abbonamento: il PaaS deve segnalare al proprietario del servizio quando è sicuro utilizzarlo.

Infornare

La fase successiva consiste nel creare il pacchetto dei servizi prima della distribuzione.

  • Creazione dell'applicazione. Secondo i classici - in un'immagine Docker.
  • Generazione di grafici Helm per il servizio stesso e risorse correlate. Incluso per database e cache. Vengono creati automaticamente in conformità con la configurazione app.toml generata nella fase push CLI.
  • Creazione di ticket per gli amministratori per aprire le porte (Quando richiesto).
  • Esecuzione di unit test e calcolo della copertura del codice. Se la copertura del codice è inferiore alla soglia specificata, molto probabilmente il servizio non andrà oltre, fino alla distribuzione. Se è al limite dell'accettabile, al servizio verrà assegnato un coefficiente “pessimizzante”: quindi, se non si riscontra alcun miglioramento dell'indicatore nel tempo, lo sviluppatore riceverà una notifica che non ci sono progressi in termini di test ( e bisogna fare qualcosa al riguardo).
  • Tenendo conto delle limitazioni di memoria e CPU. Scriviamo principalmente microservizi in Golang e li eseguiamo in Kubernetes. Da qui una sottigliezza associata alla peculiarità del linguaggio Golang: per impostazione predefinita, all'avvio, vengono utilizzati tutti i core della macchina, se non si imposta esplicitamente la variabile GOMAXPROCS, e quando diversi di questi servizi vengono avviati sulla stessa macchina, iniziano competere per le risorse, interferendo tra loro. I grafici seguenti mostrano come cambia il tempo di esecuzione se si esegue l'applicazione senza conflitti e in modalità corsa per le risorse. (Le fonti dei grafici sono qui).

Cosa sappiamo dei microservizi

Tempo di esecuzione, meno è meglio. Massimo: 643 ms, minimo: 42 ms. La foto è cliccabile.

Cosa sappiamo dei microservizi

È ora dell'intervento chirurgico, meno è meglio. Massimo: 14091 ns, minimo: 151 ns. La foto è cliccabile.

Nella fase di preparazione dell'assieme è possibile impostare esplicitamente questa variabile oppure utilizzare la libreria automaxprocs dai ragazzi di Uber.

Distribuire

• Controllo delle convenzioni. Prima di iniziare a fornire insiemi di servizi agli ambienti previsti, è necessario verificare quanto segue:
- Endpoint API.
— Conformità delle risposte degli endpoint API con lo schema.
— Formato del registro.
— Impostazione delle intestazioni per le richieste al servizio (attualmente questo viene fatto da netramesh)
— Impostazione del token proprietario durante l'invio di messaggi al bus degli eventi. Ciò è necessario per tracciare la connettività dei servizi sul bus. È possibile inviare sul bus sia dati idempotenti, che non aumentano la connettività dei servizi (il che è positivo), sia dati aziendali che rafforzano la connettività dei servizi (il che è pessimo!). E nel momento in cui questa connettività diventa un problema, capire chi scrive e chi legge l’autobus aiuta a separare adeguatamente i servizi.

Non ci sono ancora molte convention ad Avito, ma il loro pool si sta espandendo. Quanto più tali accordi sono disponibili in una forma comprensibile e comprensibile per il team, tanto più facile sarà mantenere la coerenza tra i microservizi.

Test sintetici

• Test a circuito chiuso. Per questo ora utilizziamo l'open source Hoverfly.io. Innanzitutto registra il carico reale sul servizio, quindi, in un ciclo chiuso, lo emula.

• Prove di stress. Cerchiamo di portare tutti i servizi a prestazioni ottimali. Inoltre, tutte le versioni di ciascun servizio devono essere sottoposte a test di carico: in questo modo possiamo comprendere le prestazioni attuali del servizio e la differenza con le versioni precedenti dello stesso servizio. Se, dopo un aggiornamento del servizio, le sue prestazioni sono diminuite di una volta e mezza, questo è un chiaro segnale per i suoi proprietari: è necessario scavare nel codice e correggere la situazione.
Utilizziamo i dati raccolti, ad esempio, per implementare correttamente l'auto scaling e, alla fine, in generale per comprendere quanto sia scalabile il servizio.

Durante il test di carico, controlliamo se il consumo di risorse soddisfa i limiti impostati. E ci concentriamo principalmente sugli estremi.

a) Consideriamo il carico totale.
- Troppo piccolo: molto probabilmente qualcosa non funziona affatto se il carico diminuisce improvvisamente più volte.
- Troppo grande: è necessaria l'ottimizzazione.

b) Osserviamo il cut-off secondo RPS.
Qui vediamo la differenza tra la versione attuale e quella precedente e la quantità totale. Ad esempio, se un servizio produce 100 rps, allora o è scritto male, oppure questa è la sua specificità, ma in ogni caso questo è un motivo per guardare il servizio molto da vicino.
Se, al contrario, ci sono troppi RPS, allora forse c'è qualche tipo di bug e alcuni endpoint hanno smesso di eseguire il payload e qualcun altro viene semplicemente attivato return true;

Test delle canarie

Dopo aver superato i test sintetici, testiamo il microservizio su un numero limitato di utenti. Iniziamo con cautela, con una piccola quota del pubblico previsto dal servizio, inferiore allo 0,1%. In questa fase è molto importante che nel monitoraggio siano incluse le corrette metriche tecniche e di prodotto in modo che mostrino il problema nel servizio il più rapidamente possibile. Il tempo minimo per un test del canarino è di 5 minuti, quello principale è di 2 ore. Per servizi complessi, impostiamo l'ora manualmente.
Analizziamo:
— metriche specifiche della lingua, in particolare, lavoratori php-fpm;
— errori in Sentry;
— stati della risposta;
— tempo di risposta, esatto e medio;
— latenza;
— eccezioni, elaborate e non gestite;
- metriche del prodotto.

Prova di compressione

Il test di compressione è anche chiamato test di “compressione”. Il nome della tecnica è stato introdotto in Netflix. La sua essenza è che prima riempiamo un'istanza con traffico reale fino al punto di fallimento e quindi ne fissiamo il limite. Quindi aggiungiamo un'altra istanza e carichiamo questa coppia, sempre al massimo; vediamo il loro soffitto e il delta con la prima “spremitura”. E quindi colleghiamo un'istanza alla volta e calcoliamo lo schema dei cambiamenti.
I dati dei test attraverso la "spremitura" confluiscono anche in un database di parametri comuni, dove arricchiamo con essi i risultati del carico artificiale o addirittura sostituiamo con essi i "sintetici".

Produzione

• Ridimensionamento. Quando implementiamo un servizio in produzione, ne monitoriamo la scalabilità. Nella nostra esperienza, monitorare solo gli indicatori della CPU è inefficace. La scalabilità automatica con il benchmarking RPS nella sua forma pura funziona, ma solo per determinati servizi, come lo streaming online. Quindi esaminiamo innanzitutto le metriche del prodotto specifiche dell'applicazione.

Di conseguenza, durante il ridimensionamento analizziamo:
- Indicatori CPU e RAM,
— il numero di richieste in coda,
- tempo di risposta,
— previsione basata su dati storici accumulati.

Quando si scala un servizio, è anche importante monitorare le sue dipendenze in modo da non scalare il primo servizio della catena e quelli a cui accede falliscono sotto carico. Per stabilire un carico accettabile per l'intero pool di servizi, esaminiamo i dati storici del servizio dipendente “più vicino” (basato su una combinazione di indicatori CPU e RAM, abbinati a metriche specifiche dell'app) e li confrontiamo con i dati storici del servizio di inizializzazione, e così via lungo tutta la “catena delle dipendenze”, dall’alto verso il basso.

Servizio

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

Ecco le situazioni tipiche in cui si verificano i trigger.
— Rilevate migrazioni potenzialmente pericolose.
— Sono stati rilasciati aggiornamenti di sicurezza.
— Il servizio stesso non è stato aggiornato da molto tempo.
— Il carico sul servizio è notevolmente diminuito o alcuni dei parametri del prodotto sono al di fuori dell'intervallo normale.
— Il servizio non soddisfa più i requisiti della nuova piattaforma.

Alcuni trigger sono responsabili della stabilità del funzionamento, altri, in funzione della manutenzione del sistema, ad esempio alcuni servizi non sono stati implementati per molto tempo e la loro immagine di base non ha più superato i controlli di sicurezza.

Pannello di controllo

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

  • Un unico punto di informazione sul servizio, con dati sulla sua copertura di prova, il numero delle sue immagini, il numero di copie di produzione, versioni, ecc.
  • Uno strumento per filtrare i dati per servizi ed etichette (indicatori di appartenenza a business unit, funzionalità del prodotto, ecc.)
  • Uno strumento per l'integrazione con gli strumenti dell'infrastruttura per la traccia, la registrazione e il monitoraggio.
  • Un unico punto di documentazione del servizio.
  • Un unico punto di vista su tutti gli eventi attraverso i servizi.

Cosa sappiamo dei microservizi
Cosa sappiamo dei microservizi
Cosa sappiamo dei microservizi
Cosa sappiamo dei microservizi

In totale

Prima di introdurre PaaS, un nuovo sviluppatore potrebbe dedicare diverse settimane a comprendere tutti gli strumenti necessari per lanciare un microservizio in produzione: Kubernetes, Helm, le nostre funzionalità interne di TeamCity, l'impostazione delle connessioni ai database e alle cache con tolleranza agli errori, ecc. ci vogliono un paio d'ore per leggere la guida rapida e creare il servizio stesso.

Ho tenuto un rapporto su questo argomento per HighLoad++ 2018, puoi guardarlo video и presentazione.

Bonus track per chi legge fino alla fine

Noi di Avito stiamo organizzando una formazione interna di tre giorni per gli sviluppatori di Chris Richardson, un esperto di architettura di microservizi. Vorremmo dare l'opportunità di parteciparvi ad uno dei lettori di questo post. Qui Il programma della formazione è stato pubblicato.

La formazione si svolgerà dal 5 al 7 agosto a Mosca. Questi sono giorni lavorativi che saranno completamente occupati. Il pranzo e la formazione si svolgeranno presso la nostra sede e il partecipante selezionato pagherà personalmente viaggio e alloggio.

È possibile presentare domanda di partecipazione in questo modulo di Google. Da te: la risposta alla domanda sul motivo per cui devi frequentare la formazione e informazioni su come contattarti. Rispondi in inglese, perché Chris sceglierà lui stesso il partecipante che parteciperà alla formazione.
Annunceremo il nome del partecipante alla formazione in un aggiornamento di questo post e sui social network Avito per sviluppatori (AvitoTech in Фейсбуке, VKontakte, cinguettio) entro il 19 luglio.

Fonte: habr.com

Aggiungi un commento