Cosa sappiamo sui microservizi

Ciao! Mi chiamo Vadim Madison e sono responsabile dello sviluppo della System Platform di Avito. È stato detto più volte come la nostra azienda stia passando da un'architettura monolitica a una microservizi. È tempo di condividere come abbiamo trasformato la nostra infrastruttura per estrarre il massimo beneficio dai microservizi e non perderci in essi. Come ci aiuta qui il PaaS, come abbiamo semplificato il deployment e ridotto la creazione di un microservizio a un solo clic — leggi oltre. Non tutto ciò di cui scrivo qui sotto è realizzato completamente in Avito, parte è come stiamo sviluppando la nostra piattaforma.

(E alla fine di questo articolo parlerò della possibilità di partecipare a un seminario di tre giorni con l'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 alcune centinaia di microservizi.

Stiamo costruendo l'architettura a microservizi da diversi anni. Come precisamente — i nostri colleghi hanno dettagliato raccontato nella nostra sezione al RIT++ 2017. Al CodeFest 2017 (vedi video), Sergey Orlov e Mikhail Prokopychuk hanno spiegato in dettaglio perché ci fosse bisogno di passare ai microservizi e quale ruolo ha avuto Kubernetes in questo. Ora stiamo facendo tutto il possibile per ridurre al minimo i costi di scalabilità che questa architettura comporta.

Inizialmente, non abbiamo creato un ecosistema che aiutasse in modo completo nello sviluppo e nel lancio dei microservizi. Abbiamo semplicemente raccolto soluzioni open-source valide, le abbiamo lanciate internamente e abbiamo lasciato che gli sviluppatori imparassero a gestirle. Di conseguenza, dovevano navigare in una decina di posti (dashboard, servizi interni), rafforzando così la loro inclinazione a scrivere codice nel vecchio modo, nel monolite. Nelle diagrammi sottostanti, il verde indica ciò che lo sviluppatore fa direttamente con le proprie mani, il giallo indica l'automazione.

Cosa sappiamo sui microservizi

Attualmente, nella CLI di PaaS, un nuovo servizio viene creato con un solo comando, e con altri due si aggiunge un nuovo database e si esegue il deployment in Stage.

Cosa sappiamo sui microservizi

Come superare l'era della 'frammentazione microservizi'

Con l'architettura monolitica, per garantire la coerenza delle modifiche al prodotto, gli sviluppatori sono stati costretti a capire cosa stava facendo il vicino. Lavorando 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 coordinare numerosi processi, vale a dire:

• registrazione;
• tracciamento delle richieste (Jaeger);
• aggregazione degli errori (Sentry);
• stati, messaggi, eventi da Kubernetes (Event Stream Processing);
• limitazione delle condizioni di corsa / interruttore di circuito (si può usare Hystrix);
• controllo della coerenza dei servizi (noi utilizziamo Netramesh);
• monitoraggio (Grafana);
• build (TeamCity);
• comunicazione e notifiche (Slack, email);
• tracciamento delle attività; (Jira)
• redazione della documentazione.

Affinché il sistema non perda integrità e rimanga efficace man mano che cresce, abbiamo ripensato l'organizzazione del lavoro dei microservizi su Avito.

Come gestiamo i microservizi

Condurre una singola «politica di partito» tra i numerosi microservizi di Avito è facilitato da:

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

I livelli di astrazione dell'infrastruttura comprendono tre strati. Procediamo dallo strato superiore a quello inferiore.

A. Superiore — service mesh. Inizialmente abbiamo provato Istio, ma si è rivelato consumare troppe risorse, risultando troppo costoso per i nostri volumi. Pertanto, l'ingegnere senior nel team di architettura, Aleksandr Luk'yancheko, ha sviluppato una soluzione proprietaria — Netramesh (disponibile in Open Source), che stiamo attualmente utilizzando in produzione e consuma diverse volte meno risorse rispetto a Istio (anche se non fa tutto ciò di cui può vantarsi Istio).
B. Medio — Kubernetes. Su di esso distribuiamo e gestiamo microservizi.
C. Inferiore — bare metal. Non utilizziamo nuvole o tecnologie come OpenStack, ma operiamo interamente su bare metal.

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

I. Generatori, gestiti tramite un'utilità CLI. È proprio questa a permettere agli sviluppatori di creare microservizi in modo corretto e con il minimo sforzo.

II. Collettore aggregato con controllo di tutti gli strumenti tramite una dashboard comune.

III. Archivio. Si integra con i programmatori che impostano automaticamente i trigger per azioni significative. Grazie a questo sistema, nessun compito viene trascurato solo perché qualcuno si è dimenticato di impostare un'attività in Jira. Per questo utilizziamo uno strumento interno chiamato Atlas.

Cosa sappiamo sui microservizi

L'implementazione dei microservizi in Avito avviene secondo uno schema uniforme, che semplifica il controllo su di essi in ogni fase di sviluppo e rilascio.

Come è strutturato il pipeline standard di sviluppo di un microservizio

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

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

Esaminiamola esattamente in questo ordine.

CLI-push

• Creazione di un microservizio.
Abbiamo lottato a lungo per insegnare a ogni sviluppatore a creare microservizi. Abbiamo anche scritto istruzioni dettagliate in Confluence. Ma gli schemi cambiavano e venivano ampliati. Risultato: si era creato un collo di bottiglia all'inizio del processo: il tempo necessario per avviare i microservizi superava di gran lunga il limite accettabile e comunque si presentavano spesso problemi durante la loro creazione.

Alla fine abbiamo creato una semplice utility CLI che automatizza i passaggi principali per 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à «wizard». Abbiamo modelli per i principali linguaggi di programmazione nel backend di Avito: PHP, Golang e Python.

— Con un solo comando, prepara l'ambiente per lo sviluppo locale su una macchina specifica — si avvia Minikube, i chart Helm vengono generati e avviati automaticamente nel kubernetes locale.

— Collega il database necessario. Lo sviluppatore non ha bisogno di conoscere IP, username e password per accedere al database di cui ha bisogno — sia in locale, in Stage o in produzione. Inoltre, il database viene già distribuito in una configurazione altamente affidabile e con bilanciamento.

— Esegue una compilazione live. Supponiamo che lo sviluppatore abbia corretto qualcosa nel microservizio tramite la propria IDE. L’utility rileva le modifiche nel file system e, sulla base di esse, ricompila l'applicazione (per Golang) e la riavvia. Per PHP, semplicemente colleghiamo la directory all'interno del pod e il live-reload avviene ‘in automatico’.

— Genera autotest. In forma di modelli, ma comunque utilizzabili.

• Deploy del microservizio.

Distribuire un microservizio era un po' complicato per noi prima. Era obbligatorio avere:

I. Dockerfile.

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

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

Ci siamo liberati del problema di riscrivere i manifesti Kubernetes, e ora vengono generati automaticamente. Ma soprattutto, abbiamo semplificato all'estremo il deploy. Da ora in poi abbiamo un Dockerfile, e l'intera configurazione è scritta dallo sviluppatore in un unico e breve file app.toml.

Cosa sappiamo sui microservizi

E anche nel proprio app.toml ora si lavora in un minuto. Indichiamo quante copie del servizio avviare (sul server di sviluppo, sullo staging, in produzione), specificando le sue dipendenze. Nota la riga size = "small" nel blocco [engine]. Questo è il limite che verrà assegnato al servizio tramite Kubernetes.

Successivamente, tutti i necessari Helm chart vengono generati automaticamente in base alla configurazione e vengono creati i collegamenti ai database.

• Validazione di base. Anche queste verifiche sono automatizzate.
È necessario controllare:
— se esiste il Dockerfile;
— se esiste l'app.toml;
— se è presente la documentazione;
— se le dipendenze sono in ordine;
— se sono state definite le regole degli avvisi.
Riguardo l'ultimo punto: il proprietario del servizio specifica quali metriche di prodotto monitorare.

• Preparazione della documentazione.
Ancora un punto problematico. Sembra la cosa più ovvia, ma è anche l'anello della catena che dimentichiamo più frequentemente, e quindi vulnerabile.
È necessario che ci sia documentazione per ogni microservizio. Essa include i seguenti blocchi.

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

II. Link al diagramma architetturale. È importante che a colpo d'occhio si possa capire facilmente, ad esempio, se si usa Redis per la cache o come archivio principale dei dati in modalità persistente. Al momento, per Avito, è un link su Confluence.

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

IV. FAQ, dove sarebbe bene anticipare i problemi con cui potrebbero avere a che fare i vostri colleghi nel lavorare con il servizio.

V. Descrizione degli endpoint per l'API. Se non hai specificato i punti di destinazione, è quasi sicuro che a pagare saranno i colleghi i cui microservizi sono collegati al tuo. Attualmente usiamo Swagger e la nostra soluzione chiamata brief per questo.

VI. Etichette. O marcatori che mostrano a quale prodotto, funzionalità o dipartimento dell'azienda appartiene il servizio. Aiutano a capire rapidamente, ad esempio, se stai duplicando una funzionalità che i tuoi colleghi hanno rilasciato per lo stesso business unit una settimana fa.

VII. Proprietario o proprietari del servizio. Nella maggior parte dei casi, il suo — o i loro — può essere determinato automaticamente tramite PaaS, ma per sicurezza richiediamo allo sviluppatore di indicarli anche manualmente.

Infine, una buona pratica è rivedere la documentazione, analogamente a un code review.

Integrazione Continua

  • 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 fallisce quando si trasferiscono i servizi nel supporto di un altro team di sviluppo o, ad esempio, se lo sviluppatore del servizio ha lasciato.
  • Registrazione del servizio in Atlas (vedi sopra). Con tutti i suoi proprietari e dipendenze.
  • Verifica delle migrazioni. Controlliamo se tra di esse ci sono potenzialmente pericolose. Ad esempio, in una di esse compare un alter table o altro che potrebbe compromettere la compatibilità dello schema dei dati tra le diverse versioni del servizio. In tal caso, la migrazione non viene eseguita, ma messa in attesa — PaaS deve avvisare il proprietario del servizio quando diventa sicuro applicarla.

Bake

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

  • Compilazione dell'applicazione. Secondo la tradizione, in un'immagine Docker.
  • Generazione di Helm charts per il servizio stesso e le risorse ad esso collegate. Inclusi database e cache. Vengono creati automaticamente secondo la configurazione app.toml, che è stata formata nella fase CLI-push.
  • Creazione di ticket per gli amministratori per l'apertura delle porte (quando necessario).
  • Esecuzione dei test unitari e conteggio della copertura del codice. Se la copertura del codice è al di sotto della soglia stabilita, è probabile che il servizio non superi il deploy. Se è al limite accettabile, al servizio verrà assegnato un coefficiente "pessimizzante": in questo caso, in assenza di miglioramenti nel tempo, il sviluppatore riceverà una notifica che informa che non ci sono progressi nei test (e sarà necessario prendere provvedimenti).
  • Considerazione dei limiti di memoria e CPU. Principalmente scriviamo microservizi in Golang e li avviamo in Kubernetes. Da qui nasce una sottigliezza legata alle peculiarità del linguaggio Golang: per impostazione predefinita, quando si avvia vengono utilizzati tutti i core della macchina, se non si imposta esplicitamente la variabile GOMAXPROCS e quando su una macchina vengono avviati più servizi di questo tipo, iniziano a competere per le risorse, ostacolandosi a vicenda. Nei grafici qui sotto è mostrato come varia il tempo di esecuzione se si avvia l'applicazione senza competizione e in modalità gara per le risorse. (I sorgenti dei grafici sono disponibili qui).

Cosa sappiamo sui microservizi

Il tempo di esecuzione, più basso è meglio. Massimo: 643ms, minimo: 42ms. La foto è cliccabile.

Cosa sappiamo sui microservizi

Il tempo per operazione, più basso è meglio. Massimo: 14091 ns, minimo: 151 ns. La foto è cliccabile.

Nella fase di preparazione della build si può impostare esplicitamente questa variabile oppure si può utilizzare la libreria automaxprocs dei ragazzi di Uber.

Deploy

• Verifica delle convenzioni. Prima di iniziare a distribuire le build del servizio negli ambienti prestabiliti, è necessario verificare quanto segue:
— API endpoints.
— Conformità delle risposte degli API endpoints allo schema.
— Formato dei log.
— Impostazione degli header nelle richieste al servizio (ora questo viene fatto da netramesh)
— Impostazione del marker del proprietario quando si inviano messaggi al bus (event bus). Questo è necessario per monitorare la coerenza dei servizi tramite il bus. Si possono inviare sia dati idempotenti, che non aumentano la coerenza dei servizi (cosa positiva), sia dati aziendali, che aumentano la coerenza dei servizi (cosa molto negativa!). E nel momento in cui questa coerenza diventa un problema, sapere chi scrive e legge dal bus aiuta a separare correttamente i servizi.

Finora le convenzioni in Avito non sono molte, ma il loro numero è in crescita. Più tali accordi sono in una forma comprensibile e conveniente per il team, più semplice è mantenere la coerenza tra i microservizi.

Test sintetici

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

• Test di carico. Cerchiamo di portare tutti i servizi a prestazioni ottimali. E tutte le versioni di ciascun servizio devono essere sottoposte a test di carico: in questo modo possiamo capire le attuali prestazioni del servizio e la differenza rispetto alle 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 proprietari: c'è bisogno di scavare nel codice e correggere la situazione.
Ci basiamo sui dati raccolti, ad esempio, per implementare correttamente l'auto scaling e, infine, capire quanto il servizio possa essere scalato.

Durante i test di carico, verifichiamo se il consumo di risorse rispetta i limiti impostati. E poniamo particolare attenzione agli estremi.

a) Guardiamo il carico totale.
— Troppo basso - probabilmente qualcosa non funziona affatto, se il carico è improvvisamente sceso di diversi fattori.
— Troppo alto - richiede ottimizzazione.

b) Guardiamo il cutoff per RPS.
Qui verifichiamo sia la differenza tra la versione attuale e quella precedente e il numero totale. Ad esempio, se il servizio fornisce 100 rps, significa che o è mal scritto, o è una sua specificità, ma in ogni caso è un motivo per dare un'occhiata molto attenta al servizio.
Se invece gli RPS sono al contrario troppo alti, allora potrebbe esserci qualche bug e uno degli endpoint ha smesso di eseguire il carico utile, attivando semplicemente qualche return true;

Test Canary

Dopo aver completato i test sintetici, proviamo il funzionamento del microservizio con un numero ridotto di utenti. Iniziamo con cautela, con una minima percentuale della potenziale audience del servizio - meno dello 0,1%. In questa fase è molto importante che nel monitoraggio siano definiti correttamente i metriche tecniche e di prodotto, affinché mostrino il problema del servizio il più rapidamente possibile. Il tempo minimo del test canary è di 5 minuti, quello principale di 2 ore. Per i servizi complessi impostiamo il tempo in modalità manuale.
Analizziamo:
— metriche specifiche per linguaggio, in particolare, worker php-fpm;
— errori in Sentry;
— stati delle risposte;
— tempi di risposta (response time), esatti e medi;
— latenza;
— eccezioni, gestite e non gestite;
— metriche di prodotto.

Squeeze Testing

Il Squeeze Testing è conosciuto anche come test di "compressione". Il termine di questa metodologia è stato introdotto da Netflix. La sua essenza è che inizialmente riempiamo un'istanza con traffico reale fino al guasto, stabilendo così il suo limite. Poi aggiungiamo un'altra istanza e sovraccarichiamo questa coppia — di nuovo fino al massimo; vediamo il loro tetto e la delta con il primo "squeeze". E così continuiamo a connettere un'istanza alla volta e calcoliamo la regolarità nei cambiamenti.
I dati sui test tramite "compressione" si accumulano anche in un'unica base di metriche, dove arricchiamo i risultati del carico artificiale o li sostituiamo completamente con "sintetici".

Produzione

• Scalabilità. Quando rilasciamo un servizio in produzione, monitoriamo come si scalano. Monitorare solo i parametri CPU, per nostra esperienza, non è efficace. L'Auto scaling con benchmarking RPS funziona in solo per alcuni servizi, come lo streaming online. Quindi, ci concentriamo prima su metriche specifiche del prodotto legate all'applicazione.

Di conseguenza, durante la scalabilità analizziamo:
— i parametri CPU e RAM,
— il numero di richieste in coda,
— il tempo di risposta,
— le previsioni basate sui dati storici accumulati.

Durante la scalabilità del servizio è anche importante monitorare le sue dipendenze, per evitare che scalando il primo servizio nella catena, quelli a cui si rivolga crollino sotto il carico. Per stabilire un carico accettabile per l'intero pool di servizi, guardiamo ai dati storici del "più vicino" servizio dipendente (in base alla combinazione di parametri CPU e RAM insieme a metriche specifiche dell'app) e li confrontiamo con i dati storici del servizio inizializzatore, e così via lungo tutta la "catena di dipendenze", dall'alto verso il basso.

Manutenzione

Dopo che il microservizio è stato messo in servizio, possiamo applicare dei trigger.

Ecco le 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 notevolmente o alcune sue metriche di prodotto escono dai limiti normali.
— Il servizio non soddisfa più i nuovi requisiti della piattaforma.

Una parte dei trigger è responsabile della stabilità del sistema, mentre un'altra funge da funzione di manutenzione del sistema: ad esempio, un certo servizio non è stato distribuito da tempo e la sua immagine di base ha smesso di superare il controllo di sicurezza.

Dashboard

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

  • Un punto unico di informazione sul servizio, con dati sulla sua copertura dei test, sul numero delle sue immagini, sul numero delle copie in produzione, sulle versioni, ecc.
  • Strumento per filtrare i dati in base ai servizi e alle etichette (marker di appartenenza alle business unit, funzionalità del prodotto, ecc.)
  • Strumento di integrazione con strumenti infrastrutturali per il tracciamento, la registrazione e il monitoraggio.
  • Un punto unico di documentazione sui servizi.
  • Un punto unico di 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 familiarizzare con tutti gli strumenti necessari per avviare un microservizio in produzione: Kubernetes, Helm, le nostre particolarità interne di TeamCity, la configurazione della connessione a basi di dati e cache in modo fault-tolerant, ecc. Ora ci vuole un paio d'ore: leggere il quickstart e fare il servizio.

Ho tenuto una presentazione su questo tema per HighLoad++ 2018, puoi dare un'occhiata video e alla presentazione.

Traccia bonus per chi ha letto fino alla fine

Noi di Avito organizziamo un corso di formazione interno di tre giorni per sviluppatori con Chris Richardson, esperto di architettura a microservizi. Vogliamo offrire l'opportunità di partecipare a qualcuno dei lettori di questo post. Qui Il programma del corso è stato caricato.

Il corso si svolgerà dal 5 al 7 agosto a Mosca. Questi sono giorni lavorativi, che saranno completamente occupati. Pranzo e formazione si svolgeranno nel nostro ufficio, mentre il viaggio e il soggiorno saranno a carico del partecipante scelto.

È possibile presentare domanda per partecipare in questo modulo Google. Da parte tua, è necessaria una risposta alla domanda sul perché proprio tu dovresti partecipare al corso e le informazioni su come contattarti. Rispondi in inglese, perché Chris sceglierà lui stesso il partecipante che andrà al corso.
Annunceremo il nome del partecipante al corso con un aggiornamento a 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