Automatisch testen van microservices in Docker voor continue integratie

In projecten die verband houden met de ontwikkeling van microservices-architecturen, evolueert CI/CD van een handige mogelijkheid naar een dringende noodzaak. Automatische tests zijn een essentieel onderdeel van continue integratie, waarbij een slimme aanpak het team vele aangename avonden met familie en vrienden kan schenken. In het andere geval loopt het project het risico nooit voltooid te worden.

Het is mogelijk om de volledige code van een microservice te dekken met unit tests met mock-objects, maar dat lost het probleem slechts gedeeltelijk op en laat veel vragen en complicaties over, vooral bij het testen van dataverwerking. Zoals altijd zijn de meest kritieke punten – het testen van de consistentie van gegevens in een relationele database, het testen van de interactie met cloudservices en valse aannames bij het schrijven van mock-objects.

Al deze zaken en nog meer worden opgelost door het testen van een volledige microservice in een Docker-container. Een onbetwistbaar voordeel voor het waarborgen van de validiteit van de tests is dat dezelfde Docker-images die in productie gaan, ook onder de tests worden gebracht.

De automatisering van deze aanpak brengt een aantal problemen met zich mee, waarvan de oplossingen hieronder worden beschreven:

  • conflicten tussen parallelle taken op één Docker-host;
  • conflicten van identificatoren in de database tijdens testiteraties;
  • wachten op de beschikbaarheid van microservices;
  • samenvoegen en verzenden van logs naar externe systemen;
  • testen van uitgaande HTTP-verzoeken;
  • testen van websockets (met behulp van SignalR);
  • testen van OAuth-authenticatie en autorisatie.

Dit artikel is geïnspireerd op mijn presentatie op SECR 2019. Dus voor degenen die het te veel moeite vinden om te lezen, hier is de opname van de presentatie..

Automatisch testen van microservices in Docker voor continue integratie

In dit artikel zal ik uitleggen hoe je met een script een te testen service, een database en Amazon AWS-services in Docker kunt opstarten, vervolgens tests uitvoeren met Postman en, na hun voltooiing, de gemaakte containers stoppen en verwijderen. De tests worden uitgevoerd bij elke wijziging van de code. Op deze manier verzekeren we ons ervan dat elke versie correct werkt met de database en AWS-services.

Dezelfde script wordt zowel door de ontwikkelaars op hun Windows-desktops als door de Gitlab CI-server onder Linux uitgevoerd.

Om de implementatie van nieuwe tests gerechtvaardigd te maken, mag het geen extra tools vereisen op de computer van de ontwikkelaar, noch op de server waar de tests worden uitgevoerd bij een commit. Docker lost dit probleem op.

De test moet op de lokale server werken om de volgende redenen:

  • Een netwerk is nooit 100% betrouwbaar. Van de duizend aanvragen kan er één mislukken;
    Een automatische test zal in dat geval niet slagen, het proces stopt en je moet de oorzaak in de logs zoeken;
  • Te frequente aanvragen worden door sommige externe diensten niet toegestaan.

Bovendien is het niet wenselijk om een testomgeving in te schakelen, omdat:

  • Niet alleen slechte code die erop draait kan de testomgeving breken, maar ook gegevens die de goede code niet kan verwerken;
  • Hoe hard we ook ons best doen om alle wijzigingen die door de test zijn gedaan terug te draaien, kan er tijdens de test iets misgaan (anders, waarom testen?).

Over het project en de organisatie van het proces

Ons bedrijf ontwikkelde een microservices-webapplicatie die draait in Docker in de Amazon AWS-cloud. Op dit project werden al unit tests gebruikt, maar er traden vaak fouten op die de unit tests niet ontdekten. Het was nodig om de gehele microservice samen met de database en Amazon-diensten te testen.

Op het project is een standaardproces voor continue integratie van toepassing, dat omvat dat de microservice bij elke commit wordt getest. Na toewijzing van een taak past de ontwikkelaar de microservice aan, test deze handmatig en voert alle beschikbare automatische tests uit. Indien nodig past de ontwikkelaar de tests aan. Als er geen problemen zijn, wordt er gecommit naar de tak van deze taak. Na elke commit worden de tests automatisch op de server uitgevoerd. De merge naar de hoofdtak en het uitvoeren van automatische tests daarop gebeurt na een succesvolle review. Als de tests op de hoofdtak geslaagd zijn, wordt de service automatisch bijgewerkt in de testomgeving op de Amazon Elastic Container Service (testomgeving). De testomgeving is noodzakelijk voor alle ontwikkelaars en testers, en het is niet wenselijk deze te breken. Testers controleren in deze omgeving een fix of een nieuwe functie door handmatige tests uit te voeren.

Architectuur van het project

Automatisch testen van microservices in Docker voor continue integratie

De applicatie bestaat uit meer dan tien diensten. Sommige zijn geschreven in .NET Core en andere in NodeJs. Elke dienst draait in een Docker-container binnen de Amazon Elastic Container Service. Elke dienst heeft zijn eigen Postgres-database, en sommige hebben ook Redis. Er zijn geen gedeelde databases. Als meerdere diensten dezelfde gegevens nodig hebben, worden deze gegevens op het moment van wijziging naar elke van deze diensten verzonden via SNS (Simple Notification Service) en SQS (Amazon Simple Queue Service), en de diensten slaan ze op in hun afzonderlijke databases.

SQS en SNS

SQS stelt je in staat om via HTTPS berichten in een wachtrij te plaatsen en berichten uit de wachtrij te lezen.

Als meerdere diensten dezelfde wachtrij lezen, komt elk bericht alleen bij een van hen aan. Dit is nuttig bij het starten van meerdere exemplaren van dezelfde dienst voor belastingverdeling.

Als elk bericht aan meerdere diensten moet worden afgeleverd, moet elke ontvanger zijn eigen wachtrij hebben, en voor het dupliceren van berichten naar verschillende wachtrijen is SNS nodig.

In SNS maak je een topic aan en abonneer je bijvoorbeeld een SQS-wachtrij op dit topic. Berichten kunnen naar het topic worden verzonden. Elk bericht wordt verzonden naar elke wachtrij die op dit topic is geabonneerd. In SNS is er geen methode om berichten te lezen. Als het nodig is om tijdens het debuggen of testen te weten wat er naar SNS wordt verzonden, kan een SQS-wachtrij worden aangemaakt, deze kan op het benodigde topic worden geabonneerd en dan kan de wachtrij worden gelezen.

Automatisch testen van microservices in Docker voor continue integratie

API Gateway

De meeste diensten zijn niet direct toegankelijk vanuit het internet. Toegang gebeurt via de API Gateway, die de toegangsrechten controleert. Dit is ook onze dienst, en er zijn ook tests voor.

Realtime meldingen

De applicatie maakt gebruik van SignalR, om de gebruiker realtime meldingen te tonen. Dit is geïmplementeerd in de meldingsdienst. Deze is rechtstreeks toegankelijk vanuit het internet en werkt zelf met OAuth, omdat het niet praktisch bleek om ondersteuning voor websockets in de Gateway te integreren vergeleken met de integratie van OAuth en de meldingsdienst.

Een bekende benadering voor testen

Unit tests vervangen dingen zoals databases door mock-objecten. Als een microdienst bijvoorbeeld probeert een record aan te maken in een tabel met een externe sleutel, en die record waarnaar deze sleutel verwijst bestaat niet, kan de aanvraag niet worden uitgevoerd. Unit tests kunnen dit niet detecteren.

In in een artikel van Microsoft wordt voorgesteld om een in-memory database te gebruiken en mock-objecten te injecteren.

De in-memory database is een van de databases die door Entity Framework worden ondersteund. Het is speciaal ontworpen voor tests. Gegevens in zo'n database worden alleen opgeslagen totdat het proces dat deze gebruikt, wordt beëindigd. Het is niet nodig om tabellen te maken en de gegevensintegriteit wordt niet gecontroleerd.

Mock-objecten simuleren de vervangbare klasse slechts in de mate waarin de ontwerper van de test de werking ervan begrijpt.

Hoe je Postgres automatisch kunt starten en migraties kunt uitvoeren bij het starten van de test, wordt niet vermeld in het artikel van Microsoft. Mijn oplossing doet dit en voegt bovendien geen enkele code speciaal voor tests toe aan de microservice zelf.

Laten we naar de oplossing gaan

Tijdens de ontwikkeling werd duidelijk dat unit tests niet genoeg waren om tijdig alle problemen te vinden, daarom werd besloten om het probleem vanuit een andere hoek te benaderen.

Configuratie van de testomgeving

De eerste taak is om de testomgeving op te zetten. De stappen die nodig zijn om de microservice te starten:

  • Configureer de te testen service voor de lokale omgeving, in de omgevingsvariabelen worden de referenties voor de database en AWS opgegeven;
  • Start Postgres en voer de migratie uit door Liquibase te starten.
    In relationele databases moet je, voordat je gegevens in de database opslaat, een gegevensschema aanmaken, eenvoudig gezegd, tabellen. Bij het bijwerken van de applicatie moeten de tabellen worden aangepast aan het format dat door de nieuwe versie wordt gebruikt, en bij voorkeur zonder gegevensverlies. Dit wordt migratie genoemd. Het aanmaken van tabellen in een oorspronkelijk lege database is een speciaal geval van migratie. Migratie kan in de applicatie zelf worden ingebouwd. Zowel in .NET als in NodeJS zijn er frameworks voor migraties. In ons geval is het voor de veiligheid niet toegestaan voor microservices om het gegevensschema te wijzigen, en migratie wordt uitgevoerd via Liquibase.
  • Start Amazon LocalStack. Dit is een implementatie van AWS-services om lokaal uit te voeren. Voor LocalStack is er een kant-en-klaar beeld op Docker Hub.
  • Start het script om de noodzakelijke entiteiten in LocalStack aan te maken. Shell-scripts gebruiken AWS CLI.

Voor het testen van het project wordt gebruikgemaakt van Postman. Het was er eerder ook, maar het werd handmatig uitgevoerd en de applicatie die al was uitgerold op de stand werd getest. Dit hulpmiddel maakt willekeurige HTTP(S)-verzoeken mogelijk en controleert of de antwoorden voldoen aan de verwachtingen. Verzoeken worden in een collectie samengevoegd, en de hele collectie kan in één keer worden uitgevoerd.

Automatisch testen van microservices in Docker voor continue integratie

Hoe de automatische test is opgebouwd

Tijdens de test werkt alles in Docker: de te testen service, Postgres, het migratietool en Postman, of beter gezegd, de consoleversie ervan - Newman.

Docker lost een aantal problemen op:

  • Onafhankelijkheid van de hostconfiguratie;
  • Installatie van afhankelijkheden: Docker downloadt de afbeeldingen van Docker Hub;
  • Terugkeer van het systeem naar de originele staat: we verwijderen gewoon de containers.

Docker-compose verbindt containers in een virtueel netwerk, afgescheiden van het internet, waarin containers elkaar vinden via domeinnamen.

De test wordt aangestuurd door een shell-script. Voor het uitvoeren van de test onder Windows gebruiken we git-bash. Zo is slechts één script nodig voor zowel Windows als Linux. Git en Docker zijn geïnstalleerd bij alle ontwikkelaars in het project. Bij de installatie van Git onder Windows wordt git-bash ook geïnstalleerd, dus dat hebben ze allemaal.

Het script voert de volgende stappen uit:

  • Bouwen van Docker-afbeeldingen
    docker-compose build
  • Starten van de database en LocalStack
    docker-compose up -d
  • Migratie van de database en voorbereiden van LocalStack
    docker-compose run
  • Starten van de te testen service
    docker-compose up -d
  • Starten van de test (Newman)
  • Stoppen van alle containers
    docker-compose down
  • Posten van de resultaten in Slack
    We hebben een chat waar berichten met een groene vink of een rode kruis en een link naar de log binnenkomen.

In deze stappen zijn de volgende Docker-afbeeldingen betrokken:

  • De te testen service is dezelfde afbeelding als die voor productie. De configuratie voor de test gebeurt via omgevingsvariabelen.
  • Voor Postgres, Redis en LocalStack worden kant-en-klare afbeeldingen uit Docker Hub gebruikt. Ook voor Liquibase en Newman zijn er kant-en-klare afbeeldingen. We bouwen onze afbeeldingen op hun basis, met onze bestanden.
  • Voor de voorbereiding van LocalStack wordt een kant-en-klare afbeelding van AWS CLI gebruikt, en op basis daarvan wordt een afbeelding gemaakt die het script bevat.

Door gebruik te maken van volumes, je hoeft geen Docker-afbeelding te bouwen alleen maar om bestanden aan de container toe te voegen. Echter, volumes zijn niet geschikt voor onze omgeving, omdat de Gitlab CI-taken zelf in containers draaien. Vanuit zo'n container kun je Docker beheren, maar volumes monteren alleen mappen van het host-systeem en niet uit een andere container.

Problemen waarmee je kunt worden geconfronteerd

Wachten op gereedheid

Wanneer de container met de service draait, betekent dat nog niet dat deze klaar is om verbindingen te accepteren. We moeten wachten op de verbinding om door te gaan.

Deze taak wordt soms opgelost met behulp van een script wait-for-it.sh, dat wacht op de mogelijkheid om een TCP-verbinding tot stand te brengen. Echter, LocalStack kan een foutmelding 502 Bad Gateway geven. Bovendien bestaat het uit verschillende services, en als één daarvan klaar is, betekent dat niets voor de anderen.

Oplossing: voorbereidingsscripts van LocalStack, die wachten op een 200-respons van zowel SQS als SNS.

Conflicten van gelijktijdige taken

Meerdere tests kunnen gelijktijdig op één Docker-host draaien, dus de namen van containers en netwerken moeten uniek zijn. Bovendien kunnen tests van verschillende takken van één service ook gelijktijdig draaien, dus het is niet voldoende om in elk compose-bestand zijn eigen namen op te geven.

Oplossing: het script stelt een unieke waarde in voor de variabele COMPOSE_PROJECT_NAME.

Bijzonderheden van Windows

Bij het gebruik van Docker op Windows zijn er een aantal zaken waar ik uw aandacht op wil vestigen, omdat deze ervaringen belangrijk zijn voor het begrijpen van de oorzaken van fouten.

  1. Shell-scripts in de container moeten Linux-regels hebben.
    Het CR-teken voor de shell is een syntaxisfout. Aan de foutmelding is moeilijk te zien dat dit het probleem is. Bij het bewerken van dergelijke scripts op Windows is een geschikte teksteditor nodig. Bovendien moet het versiebeheersysteem goed zijn ingesteld.

Zo stel je git in:

git config core.autocrlf input

  1. Git-bash emuleert standaard Linux-mappen en vervangt bij het aanroepen van een exe-bestand (inclusief docker.exe) absolute Linux-paden door Windows-paden. Echter, dit heeft geen zin voor paden die niet op de lokale machine staan (of paden in de container). Dit gedrag kan niet worden uitgeschakeld.

Oplossing: een extra schuine streep aan het begin van het pad toevoegen: \/\/bin in plaats van \/bin. Linux begrijpt dergelijke paden, voor hem zijn meerdere schuine strepen hetzelfde als één. Maar git-bash herkent dergelijke paden niet en probeert ze niet te converteren.

Loguitvoer

Bij het uitvoeren van tests wil ik graag zowel de logs van Newman als die van de geteste service zien. Aangezien de gebeurtenissen in deze logs met elkaar samenhangen, is het veel handiger om ze in één console te combineren dan in twee aparte bestanden. Newman wordt gestart via docker-compose run, waardoor de uitvoer in de console terechtkomt. Het moet nog worden geregeld dat ook de uitvoer van de service daar naartoe komt.

De oorspronkelijke oplossing bestond uit het doen van docker-compose up zonder vlag -d, maar door gebruik te maken van shell-mogelijkheden, dit proces op de achtergrond te sturen:

docker-compose up <service> &

Dit werkte totdat het nodig was om logs van Docker naar een externe service te sturen. docker-compose up stopt met het weergeven van logs in de console. Maar het commando werkte docker attach.

Oplossing:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}__1 &

Conflict van identificatoren tijdens testiteraties

Tests worden uitgevoerd met meerdere iteraties. De database wordt daarbij niet geleegd. Records in de database hebben unieke ID's. Als specifieke ID's in de verzoeken worden opgenomen, krijgen we bij de tweede iteratie een conflict.

Om dit te voorkomen, moeten de ID's uniek zijn, of moeten alle objecten die door de test zijn aangemaakt, worden verwijderd. Het is niet toegestaan om sommige objecten te verwijderen, volgens de vereisten.

Oplossing: genereer GUID's met scripts in Postman.

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

Gebruik vervolgens het symbool in het verzoek {{myUUID}}, dat zal worden vervangen door de waarde van de variabele.

Interactie via LocalStack

Als de te testen service SQS-berichten leest of erin schrijft, moet de test ook met deze wachtrij werken om dit te verifiëren.

Oplossing: verzoeken uit Postman naar LocalStack.

De API van AWS-services is gedocumenteerd, waardoor verzoeken zonder SDK kunnen worden gedaan.

Als de service in de wachtrij schrijft, lezen we deze en controleren we de inhoud van het bericht.

Als de service berichten naar SNS verzendt, wordt in de voorbereidende fase LocalStack nog een wachtrij aangemaakt en geregistreerd voor dit SNS-topic. Daarna is alles zoals hierboven beschreven.

Als de service een bericht uit de wachtrij moet lezen, schrijven we dit bericht in de wachtrij in de vorige stap van de test.

Testing van HTTP-verzoeken die afkomstig zijn van de te testen microservice

Sommige services werken via HTTP met iets anders dan AWS, en sommige AWS-functies zijn niet geïmplementeerd in LocalStack.

Oplossing: in deze gevallen kan helpen MockServer, dat een kant-en-klare afbeelding heeft in Docker Hub. Verwachte verzoeken en antwoorden daarop worden ingesteld via HTTP-verzoeken. De API is gedocumenteerd, dus we doen verzoeken vanuit Postman.

Testen van authenticatie en autorisatie met OAuth

We gebruiken OAuth en JSON Web Tokens (JWT). Voor de test hebben we een OAuth-provider nodig die we lokaal kunnen draaien.

Alle interactie van de service met de OAuth-provider bestaat uit twee verzoeken: eerst wordt de configuratie opgevraagd /.well-known/openid-configuration, en daarna wordt de openbare sleutel (JWKS) opgevraagd op het adres uit de configuratie. Dit alles is statische inhoud.

Oplossing: onze test OAuth-provider is een server voor statische inhoud en twee bestanden daarop. De token is één keer gegenereerd en in Git vastgelegd.

Bijzonderheden van het testen van SignalR

Postman werkt niet met websockets. Voor het testen van SignalR is er een speciale tool ontwikkeld.

De SignalR-client hoeft niet alleen een browser te zijn. Er is ook een clientbibliotheek beschikbaar voor .NET Core. Een client, geschreven in .NET Core, maakt verbinding, doorloopt de authenticatie en wacht op een specifieke volgorde van berichten. Als er een onverwacht bericht wordt ontvangen of als de verbinding wordt verbroken, sluit de client af met code 1. Bij ontvangst van het laatste verwachte bericht sluit de client af met code 0.

Gelijktijdig met de client draait Newman. Meerdere clients worden opgestart om te controleren of de berichten bij iedereen terechtkomen die ze moeten ontvangen.

Automatisch testen van microservices in Docker voor continue integratie

Om meerdere clients op te starten, wordt de optie gebruikt —scale in de commandoregel docker-compose.

Voor de opstart van Postman wacht het script tot alle clients verbinding hebben gemaakt.
We hebben eerder al eens de verbindingen geduld. Maar daar waren het servers, en hier is het een client. Er moet een andere aanpak komen.

Oplossing: de client in de container gebruikt het mechanisme HealthCheck, om het script op de host zijn status door te geven. De client maakt een bestand aan op een bepaalde locatie, bijvoorbeeld /healthcheck, zodra de verbinding tot stand is gebracht. Het HealthCheck-script in het Docker-bestand ziet er als volgt uit:

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

Opdracht docker inspect toont voor de container de normale status, health-status en exitcode.

Na de afronding van Newman controleert het script of alle containers met de client zijn beëindigd, en dit met exitcode 0.

Geluk is er

Nadat we de hierboven beschreven complicaties hebben overwonnen, hebben we een set stabiel werkende tests. In de tests functioneert elke service als een geheel, interactie met de database en met Amazon LocalStack.

Deze tests beschermen een team van meer dan 30 ontwikkelaars tegen fouten in de applicatie met complexe interacties tussen meer dan 10 microservices bij frequente implementaties.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster