Test automatizzati dei microservizi in Docker per l'integrazione continua

Nei progetti legati allo sviluppo di architetture a microservizi, CI/CD si sposta dalla categoria di piacevole opportunità alla categoria di urgente necessità. I test automatizzati sono parte integrante dell'integrazione continua, un approccio competente che può regalare al team molte piacevoli serate con la famiglia e gli amici. Altrimenti il ​​progetto rischia di non essere mai portato a termine.

È possibile coprire l'intero codice del microservizio con test unitari con oggetti fittizi, ma questo risolve solo parzialmente il problema e lascia molte domande e difficoltà, soprattutto quando si testa il lavoro con i dati. Come sempre, i più urgenti sono testare la coerenza dei dati in un database relazionale, testare il lavoro con i servizi cloud e fare ipotesi errate durante la scrittura di oggetti fittizi.

Tutto questo e altro ancora può essere risolto testando l'intero microservizio in un contenitore Docker. Un indubbio vantaggio per garantire la validità dei test è che vengono testate le stesse immagini Docker che entrano in produzione.

L'automazione di questo approccio presenta una serie di problemi, la cui soluzione verrà descritta di seguito:

  • conflitti di attività parallele nello stesso host docker;
  • conflitti di identificatori nel database durante le iterazioni del test;
  • in attesa che i microservizi siano pronti;
  • unire e inviare registri a sistemi esterni;
  • testare le richieste HTTP in uscita;
  • test del socket web (utilizzando SignalR);
  • testare l'autenticazione e l'autorizzazione OAuth.

Questo articolo è basato su il mio discorso al SECR 2019. Quindi, per coloro che sono troppo pigri per leggere, ecco la registrazione del discorso.

Test automatizzati dei microservizi in Docker per l'integrazione continua

In questo articolo ti spiegherò come utilizzare uno script per eseguire il servizio in test, un database e i servizi Amazon AWS in Docker, quindi eseguire i test su Postman e, una volta completati, arrestare ed eliminare i contenitori creati. I test vengono eseguiti ogni volta che il codice cambia. In questo modo, ci assicuriamo che ogni versione funzioni correttamente con il database e i servizi AWS.

Lo stesso script viene eseguito sia dagli stessi sviluppatori sui loro desktop Windows sia dal server Gitlab CI sotto Linux.

Per essere giustificata, l'introduzione di nuovi test non dovrebbe richiedere l'installazione di strumenti aggiuntivi né sul computer dello sviluppatore né sul server su cui vengono eseguiti i test su un commit. Docker risolve questo problema.

Il test deve essere eseguito su un server locale per i seguenti motivi:

  • La rete non è mai completamente affidabile. Su mille richieste, una può fallire;
    In questo caso il test automatico non funzionerà, il lavoro si fermerà e bisognerà cercarne il motivo nei log;
  • Le richieste troppo frequenti non sono consentite da alcuni servizi di terze parti.

Inoltre, non è auspicabile utilizzare il supporto perché:

  • Una posizione può essere compromessa non solo da codice errato in esecuzione su di essa, ma anche da dati che il codice corretto non è in grado di elaborare;
  • Non importa quanto duramente proviamo a ripristinare tutte le modifiche apportate dal test durante il test stesso, qualcosa può andare storto (altrimenti, perché testare?).

Informazioni sull'organizzazione del progetto e del processo

La nostra azienda ha sviluppato un'applicazione Web di microservizi in esecuzione in Docker nel cloud Amazon AWS. Nel progetto erano già stati utilizzati unit test, ma spesso si verificavano errori che gli unit test non rilevavano. È stato necessario testare un intero microservizio insieme al database e ai servizi Amazon.

Il progetto utilizza un processo di integrazione continua standard, che include il test del microservizio con ogni commit. Dopo aver assegnato un'attività, lo sviluppatore apporta modifiche al microservizio, lo testa manualmente ed esegue tutti i test automatizzati disponibili. Se necessario, lo sviluppatore modifica i test. Se non vengono rilevati problemi, viene effettuato un commit sul ramo di questo problema. Dopo ogni commit, i test vengono eseguiti automaticamente sul server. L'unione in un ramo comune e l'avvio di test automatici su di esso avviene dopo una revisione riuscita. Se i test sul ramo condiviso superano, il servizio viene aggiornato automaticamente nell'ambiente di test su Amazon Elastic Container Service (bench). Il supporto è necessario per tutti gli sviluppatori e tester e non è consigliabile romperlo. I tester in questo ambiente controllano una correzione o una nuova funzionalità eseguendo test manuali.

Architettura del progetto

Test automatizzati dei microservizi in Docker per l'integrazione continua

L'applicazione è composta da più di dieci servizi. Alcuni di essi sono scritti in .NET Core e altri in NodeJs. Ogni servizio viene eseguito in un container Docker in Amazon Elastic Container Service. Ognuno ha il proprio database Postgres e alcuni hanno anche Redis. Non esistono database comuni. Se più servizi necessitano degli stessi dati, questi dati, quando cambiano, vengono trasmessi a ciascuno di questi servizi tramite SNS (Simple Notification Service) e SQS (Amazon Simple Queue Service) e i servizi li salvano nei propri database separati.

SQS e SNS

SQS ti consente di inserire messaggi in una coda e leggere messaggi dalla coda utilizzando il protocollo HTTPS.

Se più servizi leggono una coda, ogni messaggio arriva solo a uno di essi. Ciò è utile quando si eseguono più istanze dello stesso servizio per distribuire il carico tra di loro.

Se desideri che ogni messaggio venga recapitato a più servizi, ciascun destinatario deve avere la propria coda e SNS è necessario per duplicare i messaggi in più code.

In SNS crei un argomento e ti iscrivi ad esso, ad esempio, una coda SQS. È possibile inviare messaggi all'argomento. In questo caso, il messaggio viene inviato a ciascuna coda iscritta a questo argomento. SNS non dispone di un metodo per leggere i messaggi. Se durante il debug o il test hai bisogno di scoprire cosa viene inviato a SNS, puoi creare una coda SQS, iscriverla all'argomento desiderato e leggere la coda.

Test automatizzati dei microservizi in Docker per l'integrazione continua

API Gateway

La maggior parte dei servizi non sono direttamente accessibili da Internet. L'accesso avviene tramite API Gateway, che controlla i diritti di accesso. Questo è anche il nostro servizio e ci sono anche dei test per questo.

Notifiche in tempo reale

L'applicazione utilizza Segnale Rper mostrare notifiche in tempo reale all'utente. Questo è implementato nel servizio di notifica. È accessibile direttamente da Internet e funziona con OAuth, poiché si è rivelato poco pratico integrare il supporto per i socket Web nel Gateway, rispetto all'integrazione di OAuth e del servizio di notifica.

Approccio di test ben noto

I test unitari sostituiscono cose come il database con oggetti fittizi. Se un microservizio, ad esempio, tenta di creare un record in una tabella con una chiave esterna e il record a cui fa riferimento quella chiave non esiste, la richiesta non può essere eseguita. I test unitari non sono in grado di rilevarlo.

В articolo di Microsoft Si propone di utilizzare un database in memoria e implementare oggetti simulati.

Il database in memoria è uno dei DBMS supportati da Entity Framework. È stato creato appositamente per i test. I dati in tale database vengono archiviati solo fino al termine del processo che lo utilizza. Non richiede la creazione di tabelle e non verifica l'integrità dei dati.

Gli oggetti fittizi modellano la classe che stanno sostituendo solo nella misura in cui lo sviluppatore del test ne comprende il funzionamento.

Come fare in modo che Postgres si avvii automaticamente ed esegua le migrazioni quando si esegue un test non è specificato nell'articolo Microsoft. La mia soluzione fa questo e, inoltre, non aggiunge alcun codice specifico per i test al microservizio stesso.

Passiamo alla soluzione

Durante il processo di sviluppo, è diventato chiaro che i test unitari non erano sufficienti per individuare tutti i problemi in modo tempestivo, quindi si è deciso di affrontare la questione da una prospettiva diversa.

Impostazione di un ambiente di test

Il primo compito è distribuire un ambiente di test. Passaggi necessari per eseguire un microservizio:

  • Configurare il servizio in test per l'ambiente locale, specificare i dettagli per la connessione al database e ad AWS nelle variabili di ambiente;
  • Avvia Postgres ed esegui la migrazione eseguendo Liquibase.
    Nei DBMS relazionali, prima di scrivere i dati nel database, è necessario creare uno schema di dati, in altre parole, delle tabelle. Quando si aggiorna un'applicazione, le tabelle devono essere riportate nella forma utilizzata dalla nuova versione e, preferibilmente, senza perdere dati. Questa si chiama migrazione. La creazione di tabelle in un database inizialmente vuoto è un caso speciale di migrazione. La migrazione può essere integrata nell'applicazione stessa. Sia .NET che NodeJS dispongono di framework di migrazione. Nel nostro caso, per motivi di sicurezza, i microservizi sono privati ​​del diritto di modificare lo schema dei dati e la migrazione viene eseguita utilizzando Liquibase.
  • Avvia Amazon LocalStack. Questa è un'implementazione dei servizi AWS da eseguire a casa. È disponibile un'immagine già pronta per LocalStack su Docker Hub.
  • Esegui lo script per creare le entità necessarie in LocalStack. Gli script di shell utilizzano AWS CLI.

Utilizzato per testare il progetto Postino. Esisteva prima, ma veniva lanciato manualmente e testato un'applicazione già presente allo stand. Questo strumento consente di effettuare richieste HTTP(S) arbitrarie e verificare se le risposte corrispondono alle aspettative. Le query vengono combinate in una raccolta ed è possibile eseguire l'intera raccolta.

Test automatizzati dei microservizi in Docker per l'integrazione continua

Come funziona il test automatico?

Durante il test, tutto funziona in Docker: il servizio in prova, Postgres, lo strumento di migrazione e Postman, o meglio la sua versione console - Newman.

Docker risolve una serie di problemi:

  • Indipendenza dalla configurazione dell'host;
  • Installazione delle dipendenze: Docker scarica le immagini da Docker Hub;
  • Riportare il sistema allo stato originale: semplicemente rimuovendo i contenitori.

Docker-componi unisce i contenitori in una rete virtuale, isolata da Internet, in cui i contenitori si trovano tra loro tramite nomi di dominio.

Il test è controllato da uno script di shell. Per eseguire il test su Windows utilizziamo git-bash. Pertanto, uno script è sufficiente sia per Windows che per Linux. Git e Docker sono installati da tutti gli sviluppatori del progetto. Quando si installa Git su Windows, viene installato git-bash, quindi tutti hanno anche quello.

Lo script esegue i seguenti passaggi:

  • Creazione di immagini della finestra mobile
    docker-compose build
  • Avvio del database e LocalStack
    docker-compose up -d <контейнер>
  • Migrazione del database e preparazione di LocalStack
    docker-compose run <контейнер>
  • Avvio del servizio in prova
    docker-compose up -d <сервис>
  • Esecuzione del test (Newman)
  • Arresto di tutti i contenitori
    docker-compose down
  • Pubblicazione dei risultati in Slack
    Abbiamo una chat in cui vanno i messaggi con un segno di spunta verde o una croce rossa e un collegamento al registro.

Le seguenti immagini Docker sono coinvolte in questi passaggi:

  • Il servizio in fase di test ha la stessa immagine della produzione. La configurazione per il test avviene tramite variabili d'ambiente.
  • Per Postgres, Redis e LocalStack vengono utilizzate immagini già pronte da Docker Hub. Ci sono anche immagini già pronte per Liquibase e Newman. Costruiamo il nostro sul loro scheletro, aggiungendo lì i nostri file.
  • Per preparare LocalStack, utilizzi un'immagine AWS CLI già pronta e crei un'immagine contenente uno script basato su di essa.

Utilizzo volumi, non è necessario creare un'immagine Docker solo per aggiungere file al contenitore. Tuttavia, i volumi non sono adatti al nostro ambiente perché le attività CI di Gitlab vengono eseguite in contenitori. Puoi controllare Docker da un contenitore di questo tipo, ma i volumi montano solo cartelle dal sistema host e non da un altro contenitore.

Problemi che potresti incontrare

In attesa di prontezza

Quando un contenitore con un servizio è in esecuzione, ciò non significa che sia pronto ad accettare connessioni. È necessario attendere che la connessione continui.

Questo problema a volte viene risolto utilizzando uno script aspetta-che.sh, che attende un'opportunità per stabilire una connessione TCP. Tuttavia, LocalStack potrebbe generare un errore 502 Bad Gateway. Inoltre si compone di tanti servizi, e se uno di essi è pronto, questo non dice nulla degli altri.

Soluzione: script di provisioning LocalStack che attendono una risposta 200 sia da SQS che da SNS.

Conflitti di attività parallele

È possibile eseguire più test contemporaneamente sullo stesso host Docker, quindi i nomi dei contenitori e della rete devono essere univoci. Inoltre, i test provenienti da rami diversi dello stesso servizio possono anche essere eseguiti contemporaneamente, quindi non è sufficiente scrivere i loro nomi in ogni file di composizione.

Soluzione: lo script imposta la variabile COMPOSE_PROJECT_NAME su un valore univoco.

Funzionalità di Windows

Ci sono una serie di cose che desidero sottolineare quando utilizzo Docker su Windows, poiché queste esperienze sono importanti per comprendere il motivo per cui si verificano gli errori.

  1. Gli script di shell in un contenitore devono avere terminazioni di riga Linux.
    Il simbolo CR della shell è un errore di sintassi. È difficile capire dal messaggio di errore che sia così. Quando si modificano tali script su Windows, è necessario un editor di testo adeguato. Inoltre, il sistema di controllo della versione deve essere configurato correttamente.

Ecco come è configurato git:

git config core.autocrlf input

  1. Git-bash emula le cartelle Linux standard e, quando chiama un file exe (incluso docker.exe), sostituisce i percorsi Linux assoluti con percorsi Windows. Tuttavia, ciò non ha senso per percorsi non presenti sul computer locale (o percorsi in un contenitore). Questo comportamento non può essere disabilitato.

Soluzione: aggiungi un'ulteriore barra all'inizio del percorso: //bin invece di /bin. Linux comprende tali percorsi; per questo, più barre sono uguali a una. Ma git-bash non riconosce tali percorsi e non tenta di convertirli.

Uscita del registro

Durante l'esecuzione dei test, vorrei vedere i log sia di Newman che del servizio in fase di test. Poiché gli eventi di questi registri sono interconnessi, combinarli in un'unica console è molto più conveniente rispetto a due file separati. Newman viene lanciato tramite esecuzione docker-compose, e quindi il suo output finisce nella console. Non resta che assicurarsi che anche l’output del servizio arrivi lì.

La soluzione originale era fare docker-comporre up nessuna bandiera -d, ma utilizzando le funzionalità della shell, invia questo processo in background:

docker-compose up <service> &

Ciò ha funzionato finché non è stato necessario inviare i log da Docker a un servizio di terze parti. docker-comporre up ha smesso di inviare log alla console. La squadra comunque ha funzionato allegato docker.

Soluzione:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &

Conflitto di identificatore durante le iterazioni del test

I test vengono eseguiti in diverse iterazioni. Il database non viene cancellato. I record nel database hanno ID univoci. Se scriviamo ID specifici nelle richieste, otterremo un conflitto alla seconda iterazione.

Per evitarlo, gli ID devono essere univoci oppure tutti gli oggetti creati dal test devono essere eliminati. Alcuni oggetti non possono essere eliminati a causa dei requisiti.

Soluzione: genera GUID utilizzando gli script Postman.

var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);

Quindi utilizzare il simbolo nella query {{myUUID}}, che verrà sostituito con il valore della variabile.

Collaborazione tramite LocalStack

Se il servizio da testare legge o scrive su una coda SQS, per verificarlo, anche il test stesso deve funzionare con questa coda.

Soluzione: richieste da Postman a LocalStack.

L'API dei servizi AWS è documentata e consente di effettuare query senza SDK.

Se un servizio scrive su una coda, lo leggiamo e controlliamo il contenuto del messaggio.

Se il servizio invia messaggi a SNS, nella fase di preparazione LocalStack crea anche una coda e si iscrive a questo argomento SNS. Quindi tutto si riduce a quanto descritto sopra.

Se il servizio deve leggere un messaggio dalla coda, nella fase precedente del test scriviamo questo messaggio nella coda.

Test delle richieste HTTP provenienti dal microservizio sottoposto a test

Alcuni servizi funzionano su HTTP con qualcosa di diverso da AWS e alcune funzionalità AWS non sono implementate in LocalStack.

Soluzione: in questi casi può aiutare MockServer, che contiene un'immagine già pronta Hub Docker. Le richieste previste e le risposte ad esse sono configurate da una richiesta HTTP. L'API è documentata, quindi inviamo richieste a Postman.

Test dell'autenticazione e dell'autorizzazione OAuth

Usiamo OAuth e Token Web JSON (JWT). Il test richiede un provider OAuth che possiamo eseguire localmente.

Tutta l'interazione tra il servizio e il provider OAuth si riduce a due richieste: innanzitutto viene richiesta la configurazione /.noto/configurazione-openid, quindi viene richiesta la chiave pubblica (JWKS) all'indirizzo dalla configurazione. Tutto questo è contenuto statico.

Soluzione: il nostro provider OAuth di prova è un server di contenuti statici e due file al suo interno. Il token viene generato una volta e impegnato su Git.

Funzionalità del test SignalR

Postman non funziona con i websocket. È stato creato uno strumento speciale per testare SignalR.

Un client SignalR può essere più di un semplice browser. È disponibile una libreria client in .NET Core. Il client, scritto in .NET Core, stabilisce una connessione, viene autenticato e attende una sequenza specifica di messaggi. Se viene ricevuto un messaggio inaspettato o la connessione viene persa, il client esce con il codice 1. Se viene ricevuto l'ultimo messaggio previsto, il client esce con il codice 0.

Newman lavora simultaneamente con il cliente. Vengono avviati diversi client per verificare che i messaggi vengano recapitati a tutti coloro che ne hanno bisogno.

Test automatizzati dei microservizi in Docker per l'integrazione continua

Per eseguire più client utilizzare l'opzione --scala sulla riga di comando docker-compose.

Prima dell'esecuzione, lo script Postman attende che tutti i client stabiliscano le connessioni.
Abbiamo già riscontrato il problema dell'attesa di una connessione. Ma c'erano i server ed ecco il client. È necessario un approccio diverso.

Soluzione: il client nel contenitore utilizza il meccanismo Controllo sanitarioper informare lo script sull'host del suo stato. Il client crea un file in un percorso specifico, ad esempio /healthcheck, non appena viene stabilita la connessione. Lo script HealthCheck nel file docker è simile al seguente:

HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi

Squadra la finestra mobile ispeziona Mostra lo stato normale, lo stato di integrità e il codice di uscita del contenitore.

Dopo il completamento di Newman, lo script verifica che tutti i contenitori con il client siano terminati, con il codice 0.

La felicità esiste

Dopo aver superato le difficoltà sopra descritte, abbiamo effettuato una serie di test di funzionamento stabile. Nei test, ogni servizio funziona come una singola unità, interagendo con il database e Amazon LocalStack.

Questi test proteggono un team di oltre 30 sviluppatori da errori in un'applicazione con interazione complessa di oltre 10 microservizi con distribuzioni frequenti.

Fonte: habr.com

Aggiungi un commento