Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

I projekter relateret til udviklingen af ​​mikroservicearkitektur bevæger CI/CD sig fra kategorien en behagelig mulighed til kategorien af ​​en presserende nødvendighed. Automatiseret test er en integreret del af kontinuerlig integration, og en kompetent tilgang til det kan give teamet mange hyggelige aftener med familie og venner. Ellers risikerer projektet aldrig at blive afsluttet.

Det er muligt at dække hele mikroservicekoden med enhedstests med mock-objekter, men dette løser kun delvist problemet og efterlader mange spørgsmål og vanskeligheder, især ved testarbejde med data. Som altid er de mest presserende at teste datakonsistens i en relationel database, teste arbejde med cloud-tjenester og lave forkerte antagelser, når du skriver mock-objekter.

Alt dette og lidt mere kan løses ved at teste hele mikroservicen i en Docker-container. En utvivlsom fordel for at sikre validiteten af ​​tests er, at de samme Docker-billeder, der går i produktion, testes.

Automatisering af denne tilgang giver en række problemer, løsningen til hvilke vil blive beskrevet nedenfor:

  • konflikter mellem parallelle opgaver i den samme docker-vært;
  • identifikatorkonflikter i databasen under testiterationer;
  • venter på, at mikrotjenester er klar;
  • flette og udlæse logfiler til eksterne systemer;
  • test af udgående HTTP-anmodninger;
  • web socket test (ved hjælp af SignalR);
  • test af OAuth-godkendelse og -autorisation.

Denne artikel er baseret på min tale på SECR 2019. Så for dem, der er for dovne til at læse, her er en optagelse af talen.

Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

I denne artikel vil jeg fortælle dig, hvordan du bruger et script til at køre tjenesten under test, en database og Amazon AWS-tjenester i Docker, derefter tester på Postman og, efter at de er afsluttet, stopper og sletter de oprettede containere. Test udføres hver gang koden ændres. På denne måde sikrer vi, at hver version fungerer korrekt med AWS-databasen og -tjenesterne.

Det samme script køres både af udviklerne selv på deres Windows-desktops og af Gitlab CI-serveren under Linux.

For at være berettiget, bør introduktion af nye test ikke kræve installation af yderligere værktøjer hverken på udviklerens computer eller på serveren, hvor testene køres på en commit. Docker løser dette problem.

Testen skal køre på en lokal server af følgende årsager:

  • Netværket er aldrig helt pålideligt. Ud af tusinde anmodninger kan én mislykkes;
    I dette tilfælde vil den automatiske test ikke fungere, arbejdet stopper, og du bliver nødt til at lede efter årsagen i logfilerne;
  • For hyppige anmodninger er ikke tilladt af nogle tredjepartstjenester.

Derudover er det uønsket at bruge stativet, fordi:

  • Et stativ kan brydes ikke kun af dårlig kode, der kører på det, men også af data, som den korrekte kode ikke kan behandle;
  • Uanset hvor hårdt vi prøver at vende tilbage til alle de ændringer, som testen har foretaget under selve testen, kan noget gå galt (hvorfor testes ellers?).

Om projekt- og procesorganisationen

Vores virksomhed udviklede en mikroservice-webapplikation, der kører i Docker i Amazon AWS-skyen. Der blev allerede brugt enhedstester på projektet, men der opstod ofte fejl, som enhedstestene ikke opdagede. Det var nødvendigt at teste en hel mikrotjeneste sammen med databasen og Amazon-tjenester.

Projektet bruger en standard kontinuerlig integrationsproces, som inkluderer test af mikrotjenesten med hver commit. Efter at have tildelt en opgave, foretager udvikleren ændringer i mikrotjenesten, tester den manuelt og kører alle tilgængelige automatiserede tests. Om nødvendigt ændrer udvikleren testene. Hvis der ikke findes nogen problemer, forpligtes der til grenen af ​​dette problem. Efter hver commit køres test automatisk på serveren. Sammenlægning til en fælles gren og lancering af automatiske test på den sker efter en vellykket gennemgang. Hvis testene på den delte filial består, opdateres tjenesten automatisk i testmiljøet på Amazon Elastic Container Service (bænk). Stativet er nødvendigt for alle udviklere og testere, og det er ikke tilrådeligt at bryde det. Testere i dette miljø kontrollerer en rettelse eller en ny funktion ved at udføre manuelle tests.

Projekt arkitektur

Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

Applikationen består af mere end ti tjenester. Nogle af dem er skrevet i .NET Core og nogle i NodeJs. Hver service kører i en Docker-container i Amazon Elastic Container Service. Hver har sin egen Postgres-database, og nogle har også Redis. Der er ingen fælles databaser. Hvis flere tjenester har brug for de samme data, så overføres disse data, når de ændres, til hver af disse tjenester via SNS (Simple Notification Service) og SQS (Amazon Simple Queue Service), og tjenesterne gemmer dem i deres egne separate databaser.

SQS og SNS

SQS giver dig mulighed for at sætte beskeder i en kø og læse beskeder fra køen ved hjælp af HTTPS-protokollen.

Hvis flere tjenester læser én kø, kommer hver besked kun til én af dem. Dette er nyttigt, når du kører flere forekomster af den samme tjeneste for at fordele belastningen mellem dem.

Hvis du ønsker, at hver besked skal leveres til flere tjenester, skal hver modtager have sin egen kø, og SNS er nødvendig for at duplikere beskeder i flere køer.

I SNS opretter du et emne og abonnerer på det, for eksempel en SQS-kø. Du kan sende beskeder til emnet. I dette tilfælde sendes beskeden til hver kø, der abonnerer på dette emne. SNS har ikke en metode til at læse beskeder. Hvis du under fejlfinding eller test skal finde ud af, hvad der sendes til SNS, kan du oprette en SQS-kø, abonnere på det ønskede emne og læse køen.

Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

API-gateway

De fleste tjenester er ikke direkte tilgængelige fra internettet. Adgang sker via API Gateway, som kontrollerer adgangsrettigheder. Dette er også vores service, og det er der også test for.

Notifikationer i realtid

Applikationen bruger SignalRfor at vise meddelelser i realtid til brugeren. Dette er implementeret i notifikationstjenesten. Den er tilgængelig direkte fra internettet og fungerer i sig selv med OAuth, fordi det viste sig at være upraktisk at bygge support til web-sockets ind i Gateway sammenlignet med at integrere OAuth og notifikationstjenesten.

Velkendt testmetode

Enhedstest erstatter ting som databasen med falske objekter. Hvis en mikrotjeneste, for eksempel, forsøger at oprette en post i en tabel med en fremmednøgle, og posten, som denne nøgle refererer til, ikke eksisterer, kan anmodningen ikke fuldføres. Enhedstest kan ikke detektere dette.

В artikel fra Microsoft Det foreslås at bruge en database i hukommelsen og implementere mock-objekter.

In-memory database er en af ​​de DBMS'er, der understøttes af Entity Framework. Det blev skabt specielt til test. Data i en sådan database lagres kun, indtil processen, der bruger den, afsluttes. Det kræver ikke oprettelse af tabeller og kontrollerer ikke dataintegriteten.

Mock-objekter modellerer kun den klasse, de erstatter, i det omfang testudvikleren forstår, hvordan det fungerer.

Hvordan man får Postgres til automatisk at starte og udføre migreringer, når du kører en test, er ikke specificeret i Microsoft-artiklen. Min løsning gør dette og tilføjer derudover ikke nogen kode specifikt til test til selve mikrotjenesten.

Lad os gå videre til løsningen

Under udviklingsprocessen blev det klart, at enhedstests ikke var nok til at finde alle problemer rettidigt, så det blev besluttet at gribe dette spørgsmål an fra en anden vinkel.

Opsætning af et testmiljø

Den første opgave er at implementere et testmiljø. Trin, der kræves for at køre en mikrotjeneste:

  • Konfigurer tjenesten under test til det lokale miljø, angiv detaljerne for tilslutning til databasen og AWS i miljøvariablerne;
  • Start Postgres og udfør migreringen ved at køre Liquibase.
    I relationelle DBMS'er, før du skriver data ind i databasen, skal du oprette et dataskema, med andre ord tabeller. Ved opdatering af en applikation skal tabeller bringes til den form, som den nye version bruger, og helst uden at miste data. Dette kaldes migration. Oprettelse af tabeller i en oprindeligt tom database er et særligt tilfælde af migrering. Migrering kan indbygges i selve applikationen. Både .NET og NodeJS har migrationsrammer. I vores tilfælde er mikrotjenester af sikkerhedsmæssige årsager frataget retten til at ændre dataskemaet, og migreringen udføres ved hjælp af Liquibase.
  • Start Amazon LocalStack. Dette er en implementering af AWS-tjenester til at køre derhjemme. Der er et færdiglavet billede til LocalStack på Docker Hub.
  • Kør scriptet for at oprette de nødvendige entiteter i LocalStack. Shell-scripts bruger AWS CLI.

Anvendes til test på projektet Postman. Det eksisterede før, men det blev lanceret manuelt og testede en applikation, der allerede var installeret på standen. Dette værktøj giver dig mulighed for at lave vilkårlige HTTP(S)-anmodninger og kontrollere, om svarene svarer til forventningerne. Forespørgsler kombineres til en samling, og hele samlingen kan køres.

Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

Hvordan fungerer den automatiske test?

Under testen fungerer alt i Docker: Tjenesten under test, Postgres, migreringsværktøjet og Postman, eller rettere sagt dens konsolversion - Newman.

Docker løser en række problemer:

  • Uafhængighed af værtskonfiguration;
  • Installation af afhængigheder: Docker downloader billeder fra Docker Hub;
  • Returnering af systemet til dets oprindelige tilstand: blot at fjerne beholderne.

docker-komponer forener containere til et virtuelt netværk, isoleret fra internettet, hvor containere finder hinanden ved hjælp af domænenavne.

Testen styres af et shell-script. For at køre testen på Windows bruger vi git-bash. Således er ét script nok til både Windows og Linux. Git og Docker er installeret af alle udviklere på projektet. Når du installerer Git på Windows, er git-bash installeret, så det har alle også.

Scriptet udfører følgende trin:

  • Bygning docker billeder
    docker-compose build
  • Start af databasen og LocalStack
    docker-compose up -d <контейнер>
  • Databasemigration og klargøring af LocalStack
    docker-compose run <контейнер>
  • Lancering af tjenesten under test
    docker-compose up -d <сервис>
  • Kører testen (Newman)
  • Stopper alle containere
    docker-compose down
  • Indsender resultater i Slack
    Vi har en chat, hvor beskeder med et grønt flueben eller et rødt kryds og et link til loggen går.

Følgende Docker-billeder er involveret i disse trin:

  • Tjenesten, der testes, er det samme billede som for produktionen. Konfigurationen for testen er gennem miljøvariabler.
  • Til Postgres, Redis og LocalStack bruges færdige billeder fra Docker Hub. Der er også færdige billeder til Liquibase og Newman. Vi bygger vores på deres skelet og tilføjer vores filer der.
  • For at forberede LocalStack bruger du et færdiglavet AWS CLI-billede og opretter et billede, der indeholder et script baseret på det.

Brug mængder, behøver du ikke bygge et Docker-billede bare for at tilføje filer til containeren. Volumen er dog ikke egnet til vores miljø, fordi Gitlab CI-opgaver i sig selv kører i containere. Du kan styre Docker fra sådan en container, men volumener monterer kun mapper fra værtssystemet og ikke fra en anden container.

Problemer du kan støde på

Venter på beredskab

Når en container med en tjeneste kører, betyder det ikke, at den er klar til at acceptere forbindelser. Du skal vente på, at forbindelsen fortsætter.

Dette problem løses nogle gange ved hjælp af et script vente-på-det.sh, som venter på en mulighed for at etablere en TCP-forbindelse. LocalStack kan dog give en 502 Bad Gateway-fejl. Derudover består den af ​​mange tjenester, og hvis en af ​​dem er klar, siger det ikke noget om de andre.

beslutning: LocalStack-klargøringsscripts, der venter på et 200 svar fra både SQS og SNS.

Parallelle opgavekonflikter

Flere test kan køre samtidigt på den samme Docker-vært, så container- og netværksnavne skal være unikke. Desuden kan test fra forskellige grene af den samme tjeneste også køre samtidigt, så det er ikke nok at skrive deres navne i hver compose-fil.

beslutning: Scriptet sætter variablen COMPOSE_PROJECT_NAME til en unik værdi.

Windows-funktioner

Der er en række ting, jeg vil påpege, når jeg bruger Docker på Windows, da disse erfaringer er vigtige for at forstå, hvorfor der opstår fejl.

  1. Shell-scripts i en container skal have Linux-linjeendelser.
    Shell CR-symbolet er en syntaksfejl. Det er svært at se ud fra fejlmeddelelsen, at dette er tilfældet. Når du redigerer sådanne scripts på Windows, har du brug for en ordentlig teksteditor. Derudover skal versionskontrolsystemet konfigureres korrekt.

Sådan er git konfigureret:

git config core.autocrlf input

  1. Git-bash emulerer standard Linux-mapper og erstatter, når du kalder en exe-fil (inklusive docker.exe), absolutte Linux-stier med Windows-stier. Dette giver dog ikke mening for stier, der ikke er på den lokale maskine (eller stier i en container). Denne adfærd kan ikke deaktiveres.

beslutning: tilføje en ekstra skråstreg til begyndelsen af ​​stien: //bin i stedet for /bin. Linux forstår sådanne stier; for det er flere skråstreger det samme som én. Men git-bash genkender ikke sådanne stier og forsøger ikke at konvertere dem.

Log output

Når jeg kører test, vil jeg gerne se logs fra både Newman og tjenesten blive testet. Da begivenhederne i disse logfiler er indbyrdes forbundne, er det meget mere praktisk at kombinere dem i en konsol end to separate filer. Newman lancerer via docker-compose kørsel, og så dens output ender i konsollen. Tilbage er blot at sikre, at outputtet af tjenesten også går dertil.

Den oprindelige løsning var at gøre docker-compose up intet flag -d, men ved hjælp af shell-funktionerne, send denne proces til baggrunden:

docker-compose up <service> &

Dette virkede, indtil det var nødvendigt at sende logfiler fra Docker til en tredjepartstjeneste. docker-compose up stoppede med at udsende logfiler til konsollen. Men holdet arbejdede docker vedhæftning.

beslutning:

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

Identifikatorkonflikt under testgentagelser

Tests køres i flere iterationer. Databasen er ikke ryddet. Records i databasen har unikke ID'er. Hvis vi skriver specifikke ID'er ned i anmodninger, får vi en konflikt ved anden iteration.

For at undgå det skal enten ID'erne være unikke, eller også skal alle objekter, der er oprettet af testen, slettes. Nogle objekter kan ikke slettes på grund af krav.

beslutning: generer GUID'er ved hjælp af Postman-scripts.

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

Brug derefter symbolet i forespørgslen {{myUUID}}, som vil blive erstattet med værdien af ​​variablen.

Samarbejde via LocalStack

Hvis tjenesten, der testes, læser eller skriver til en SQS-kø, så skal selve testen også fungere med denne kø for at verificere dette.

beslutning: anmodninger fra Postman til LocalStack.

AWS services API er dokumenteret, hvilket gør det muligt at foretage forespørgsler uden et SDK.

Hvis en tjeneste skriver til en kø, så læser vi den og tjekker indholdet af beskeden.

Hvis tjenesten sender beskeder til SNS, opretter LocalStack på forberedelsesstadiet også en kø og abonnerer på dette SNS-emne. Så kommer det hele til det, der blev beskrevet ovenfor.

Hvis tjenesten skal læse en besked fra køen, så skriver vi i det forrige testtrin denne besked til køen.

Test af HTTP-anmodninger, der stammer fra den mikrotjeneste, der testes

Nogle tjenester fungerer over HTTP med noget andet end AWS, og nogle AWS-funktioner er ikke implementeret i LocalStack.

beslutning: i disse tilfælde kan det hjælpe MockServer, som har et færdigt billede i Docker-hub. Forventede anmodninger og svar på dem konfigureres af en HTTP-anmodning. API'et er dokumenteret, så vi laver forespørgsler fra Postman.

Test af OAuth-godkendelse og -autorisation

Vi bruger OAuth og JSON Web Tokens (JWT). Testen kræver en OAuth-udbyder, som vi kan køre lokalt.

Al interaktion mellem tjenesten og OAuth-udbyderen kommer ned til to anmodninger: For det første anmodes om konfigurationen /.velkendt/openid-konfiguration, og derefter anmodes om den offentlige nøgle (JWKS) på adressen fra konfigurationen. Alt dette er statisk indhold.

beslutning: Vores test OAuth-udbyder er en statisk indholdsserver og to filer på den. Tokenet genereres én gang og forpligtes til Git.

Funktioner ved SignalR-testning

Postmand arbejder ikke med websockets. Et særligt værktøj blev skabt til at teste SignalR.

En SignalR-klient kan være mere end blot en browser. Der er et klientbibliotek til det under .NET Core. Klienten, skrevet i .NET Core, etablerer en forbindelse, godkendes og venter på en bestemt sekvens af meddelelser. Hvis en uventet besked modtages, eller forbindelsen afbrydes, afsluttes klienten med en kode på 1. Hvis den sidste forventede besked modtages, afsluttes klienten med en kode på 0.

Newman arbejder samtidigt med klienten. Flere klienter lanceres for at kontrollere, at beskeder leveres til alle, der har brug for dem.

Automatiseret test af mikrotjenester i Docker til kontinuerlig integration

Brug muligheden for at køre flere klienter --vægt på kommandolinjen i docker-compose.

Før Postman-scriptet kører, venter på, at alle klienter etablerer forbindelser.
Vi har allerede stødt på problemet med at vente på en forbindelse. Men der var servere, og her er klienten. Der er brug for en anden tilgang.

beslutning: klienten i containeren bruger mekanismen Sundhedstjekat informere scriptet på værten om dets status. Klienten opretter en fil på en bestemt sti, f.eks. /healthcheck, så snart forbindelsen er etableret. HealthCheck-scriptet i docker-filen ser sådan ud:

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

Team havnearbejder eftersyn Viser normal status, sundhedsstatus og udgangskode for beholderen.

Når Newman er færdig, kontrollerer scriptet, at alle containere med klienten er afsluttet, med kode 0.

Happinnes eksisterer

Efter at vi overvandt vanskelighederne beskrevet ovenfor, havde vi et sæt stabile løbetests. I test fungerer hver tjeneste som en enkelt enhed, der interagerer med databasen og Amazon LocalStack.

Disse test beskytter et team på 30+ udviklere mod fejl i en applikation med kompleks interaktion af 10+ mikrotjenester med hyppige implementeringer.

Kilde: www.habr.com

Tilføj en kommentar