Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

A mikroszolgáltatás-architektúrával kapcsolatos projektekben a CI/CD a kellemes lehetőségből éles szükséggé válik. Az automatikus tesztelés elengedhetetlen része a folyamatos integrációnak, amelyhez helyes megközelítés rengeteg kellemes estét biztosíthat a csapatnak a családdal és barátokkal. Ellenkező esetben a projekt veszélybe kerülhet, hogy soha nem fejeződik be.

Az egész mikroszolgáltatás kódját le lehet fedni egységtesztekkel, amelyeken mock-objektumok alapulnak, de ez csak részben oldja meg a problémát, és számos kérdést és nehézséget hagy nyitva, különösen az adatokkal való munka tesztelésekor. Mint mindig, a legélesebb problémák az adatok konzisztenciájának tesztelése a relációs adatbázisban, a felhőszolgáltatásokkal való munka tesztelése, és a mock-objektumok írásakor tett téves feltételezések.

Mindez és még sokkal több megoldható a teljes mikroszolgáltatás Docker-konténerben való tesztelésével. Egyértelmű előny, hogy a tesztek ugyanazokat a Docker-képeket használják, amelyek a termelési környezetbe kerülnek.

E megközelítés automatizálása számos problémát jelent, amelynek megoldását hamarosan leírom:

  • konfliktusok párhuzamos feladatok között egy docker-hoszton;
  • azonosítókonfliktusok az adatbázisban a teszt iterációk során;
  • a mikroszolgáltatások készenléti állapotának várakozása;
  • naplók egyesítése és külső rendszerekbe történő kiírása;
  • kimenő HTTP-kérelmek tesztelése;
  • websocketek tesztelése (SignalR segítségével);
  • OAuth azonosítás és jogosultság tesztelése.

Ez a cikk egy beszédem alapján készült a SECR 2019-en. Tehát azoknak, akik lusták olvasni, itt van a beszéd felvétele..

Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

A cikkben elmondom, hogyan indítható el egy tesztelhető szolgáltatás, egy adatbázis és az Amazon AWS szolgáltatások Docker segítségével egy script segítségével, majd a tesztek Postman-en végrehajtása után leállítani és törölni a létrehozott konténereket. A tesztek minden kódbeli változtatáskor futnak. Így biztosítjuk, hogy minden verzió helyesen működik az adatbázissal és az AWS szolgáltatásokkal.

Ugyanaz a script futtatja a fejlesztőket a Windows-asztalaikon és a Linux alatt futó Gitlab CI szervert is.

Ahhoz, hogy az új tesztek bevezetése indokolt legyen, nem szabad további eszközök telepítését igényelnie sem a fejlesztői gépen, sem azon a szerveren, ahol a teszteket futtatják a commit során. A Docker megoldja ezt a feladatot.

A tesztnek a helyi szerveren kell futnia a következő okok miatt:

  • A hálózat sosem lehet teljesen megbízható. Ezer kérésből egy esetleg nem megy át;
    Automatikus teszt ilyen esetben nem fog működni, a folyamat megáll, és meg kell keresni az okot a naplókban;
  • Túl gyakori kéréseket egyes külső szolgáltatások nem engednek meg.

Ezen kívül, a stand használata nem javasolt, mert:

  • A standot nemcsak a rajta futó hibás kód, hanem olyan adatok is megsérthetik, amelyeket a helyes kód nem tud kezelni;
  • Bárhogy is próbálkozunk, hogy visszavonjuk a teszt során végrehajtott összes változtatást, a teszt során valami mindig rosszul sülhet el (máskülönben miért lenne szükség tesztre?).

A projektről és a folyamat szervezéséről

Cégünk egy mikroszolgáltatás alapú webalkalmazást fejlesztett, amely Dockerben működik az Amazon AWS felhőben. A projekt során már használtak egységteszteket, azonban gyakran előfordultak olyan hibák, amelyeket az egységtesztek nem észleltek. Szükséges volt tesztelni az egész mikroszolgáltatást az adatbázissal és az Amazon szolgáltatásaival együtt.

A projekten a folyamatos integráció standard folyamata van alkalmazva, amely tartalmazza a mikroszolgáltatás tesztelését minden egyes commit során. A feladat kijelölése után a fejlesztő módosításokat végez a mikroszolgáltatáson, manuálisan teszteli azt, és futtatja az összes elérhető automatikus tesztet. Ha szükséges, a fejlesztő módosítja a teszteket. Ha problémák nem merülnek fel, akkor commitál a feladat ága alatt. Minden commit után a szerveren automatikusan elindulnak a tesztek. A közös ágba történő merge és az automatikus tesztek futtatása sikeres review után történik. Ha a közös ágon a tesztek sikeresen teljesülnek, a szolgáltatás automatikusan frissül a tesztkörnyezetben az Amazon Elastic Container Service-en (stand). A stand minden fejlesztő és tesztelő számára szükséges, így nem szabad tönkretenni. A tesztelők ezen a környezeten ellenőrzik a javítást vagy az új funkciót, manuális teszteket végezve.

A projekt architektúrája

Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

Az alkalmazás több mint tíz szolgáltatásból áll. Néhány .NET Core-on, míg mások NodeJs-en vannak írva. Minden szolgáltatás Docker-konténerben fut az Amazon Elastic Container Service-ben. Mindegyiknek saját Postgres adatbázisa van, néhányuknak pedig Redis is. Nincs közös adatbázis. Ha több szolgáltatásnak szüksége van azonos adatokra, akkor ezek az adatok a módosításuk pillanatában SNS (Simple Notification Service) és SQS (Amazon Simple Queue Service) révén kerülnek átadásra minden egyes szolgáltatásnak, és a szolgáltatások eltárolják őket saját elkülönített adatbázisaikban.

SQS és SNS

Az SQS lehetővé teszi, hogy HTTPS protokollon keresztül üzeneteket helyezzen el egy sorban és olvasson üzeneteket a sorból.

Ha több szolgáltatás olvassa egy sor tartalmát, akkor minden üzenet csak egynek a szolgáltatásnak jut el. Ez hasznos, ha több példányt indítunk el egy szolgáltatásból, hogy elosszuk a terhelést közöttük.

Ha szükséges, hogy minden üzenet eljusson több szolgáltatáshoz, akkor minden címzettnek saját sorra van szüksége, és az üzenetek több sorba való másolásához SNS szükséges.

Az SNS-ben topicot hoz létre, és például egy SQS-sort feliratkoztat rá. A topicra üzeneteket lehet küldeni, így az üzenet minden, a topicra feliratkozott sorba eljut. Az SNS-ben nincs üzenetek olvasására szolgáló metódus. Ha hibakeresés vagy tesztelés során szükséges tudni, hogy mi kerül az SNS-be, akkor létrehozhat egy SQS-sort, amelyet a szükséges topicra feliratkoztat, és olvashatja a sort.

Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

API Gateway

A legtöbb szolgáltatás nem elérhető közvetlenül az internetről. A hozzáférés az API Gateway-en keresztül történik, amely ellenőrzi a hozzáférési jogokat. Ez is a mi szolgáltatásunk, és ennek is vannak tesztjei.

Valós idejű értesítések

Az alkalmazás használja SignalR, hogy valós időben értesítéseket jelenítsen meg a felhasználónak. Ez az értesítési szolgáltatásban van megvalósítva. Ez közvetlenül elérhető az internetről, és magával feküdt a OAuth támogatással, mert nem volt célszerű a Web Socket támogatását beépíteni a Gateway-be az OAuth és az értesítési szolgáltatás integrálásához képest.

Ismert megközelítés a teszteléshez

Az egységtesztek átkonfigurálják a mock objektumokkal az olyan dolgokat, mint az adatbázis. Ha egy mikro-szolgáltatás például megpróbál egy rekordot létrehozni egy külső kulccsal rendelkező táblában, és a hivatkozott rekord nem létezik, akkor a kérés nem teljesíthető. Az egységtesztek ezt nem képesek észlelni.

A a Microsoft cikkében javasolják az in-memory adatbázis használatát és a mock objektumok bevezetését.

Az in-memory adatbázis az egyik olyan DBMS, amelyet az Entity Framework támogat. Kifejezetten tesztelés céljából készült. Az ilyen adatbázisban az adatok csak a felhasználó folyamatának befejezéséig tárolódnak. Itt nem szükséges táblázatokat létrehozni, és az adatok integritását nem ellenőrzik.

A mock-objektumok csak annyira modellezik a helyettesíthető osztályt, amennyire a tesztelő fejlesztő ezt megérti.

A Postgres automatikus indításának és a migráció végrehajtásának módja a teszt indításakor nem szerepel a Microsoft cikkében. Az én megoldásom ezt végrehajtja, és emellett nem ad hozzá semmilyen kódot a mikroszolgáltatáshoz, amely kifejezetten tesztelés céljára szolgál.

Térjünk át a megoldásra

A fejlesztés folyamán világossá vált, hogy a unit tesztek nem elegendőek a problémák időben való észlelésére, ezért úgy döntöttünk, hogy más megközelítést alkalmazunk.

A tesztkörnyezet beállítása

Az első feladat a tesztkörnyezet telepítése. A mikroszolgáltatás indításához szükséges lépések:

  • A tesztelésre kiszolgáltatott szolgáltatást be kell állítani a helyi környezetre, a környezeti változókban meg kell adni az adatbázishoz és az AWS-hez való kapcsolódás részleteit;
  • Indítsa el a Postgres-t és hajtsa végre a migrációt a Liquibase elindításával.
    A relációs DBMS-ekben az adatok adatbázisba írása előtt létre kell hozni az adatmodell sémát, más szóval a táblázatokat. Az alkalmazás frissítésekor a táblázatokat az új verzió által használt formához kell igazítani, lehetőleg adatvesztés nélkül. Ezt migrációnak hívják. A táblázatok létrehozása egy eredetileg üres adatbázisban a migráció speciális esete. A migrációt be lehet építeni magába az alkalmazásba. A .NET és a NodeJS esetében is léteznek migrációs keretrendszerek. A mi esetünkben a biztonság érdekében a mikroszolgáltatások nem jogosultak az adatmodellek sémájának megváltoztatására, és a migrációt a Liquibase végzi.
  • Indítsa el az Amazon LocalStack-et. Ez az AWS szolgáltatások helyi futtatásának megvalósítása. A LocalStack-hez kész Docker Hub képmás áll rendelkezésre.
  • Indítsa el a skriptet a szükséges entitások létrehozásához a LocalStack-ben. A Shell-skriptek az AWS CLI-t használják.

A projekt teszteléséhez a következőt használják Postman. Ez korábban is létezett, de manuálisan indították, és a már a tesztkörnyezetre telepített alkalmazást tesztelték. Ez az eszköz lehetővé teszi tetszőleges HTTP(S) kérések küldését és a válaszok elvárásokkal való összehasonlítását. A kérések egy gyűjteménybe összegyűjtve futtathatók együtt.

Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

Hogyan működik az automatikus teszt

A teszt során a Dockerben minden működik: a tesztelendő szolgáltatás, a Postgres, a migrációs eszköz, és a Postman, vagyis annak konzolos verziója – Newman.

A Docker számos problémát megold:

  • Függetlenség a host konfigurációjától;
  • Függőségek telepítése: a Docker letölti a képeket a Docker Hubról;
  • A rendszer visszaállítása az alapállapotra: egyszerűen eltávolítjuk a konténereket.

Docker-compose összekapcsolja a konténereket egy virtuális hálózatban, amely elkülönített az internettől, ahol a konténerek domainnevek alapján találják meg egymást.

A tesztet shell-skript irányítja. Windows alatt git-bash-t használunk a teszt futtatásához. Így elegendő egyetlen skript Windowsra és Linuxra is. A Git és a Docker telepítve van minden fejlesztőnél a projektben. A Git Windowsra történő telepítésekor git-bash is telepítve van, tehát az is mindenkinek rendelkezésére áll.

A skript a következő lépéseket hajtja végre:

  • Docker képek építése
    docker-compose build
  • Az adatbázis és a LocalStack indítása
    docker-compose up -d
  • Adatbázis migrálása és a LocalStack előkészítése
    docker-compose run
  • A tesztelendő szolgáltatás indítása
    docker-compose up -d
  • A teszt futtatása (Newman)
  • Minden konténer leállítása
    docker-compose down
  • Eredmények közzététele a Slack-en
    Van egy chatünk, ahová a zöld pipa vagy a piros kereszt és a naplólink üzenetei érkeznek.

Ezekben a lépésekben a következő Docker képek szerepelnek:

  • A tesztelendő szolgáltatás – ugyanaz a kép, mint a produkcióban. A teszt konfigurációja környezeti változókon keresztül történik.
  • A Postgres, a Redis és a LocalStack számára kész képeket használunk a Docker Hubról. A Liquibase és a Newman számára is léteznek kész képek. Azokat építjük, hozzáadva a saját fájljainkat.
  • A LocalStack előkészítéséhez egy kész AWS CLI képet használunk, amely alapján egy képet hozunk létre, amely tartalmaz egy skriptet.

Használva volumes, nem szükséges Docker képet készíteni csupán fájlok hozzáadásához a konténerhez. Azonban a volumes nem megfelelő a mi környezetünkhöz, mert a Gitlab CI feladatok maguk is konténerekben futnak. Ebből a konténerből kezelhetjük a Dockert, de a volumes csak a host rendszer mappáit csatolják, nem egy másik konténerből.

Problémák, amelyekkel szembesülhetünk

Készültség várakoztatása

Amikor a szolgáltatás konténer indul, az még nem jelenti azt, hogy készen áll a kapcsolatok fogadására. Várni kell a kapcsolatra, hogy folytathassuk.

Ezt a feladatot néha skript segítségével oldják meg wait-for-it.sh, ami várja a TCP kapcsolat létrehozásának lehetőségét. Azonban a LocalStack 502 Bad Gateway hibát adhat. Továbbá, sok szolgáltatásból áll, és ha az egyik készen áll, az nem mond semmit a többiről.

Megoldás: a LocalStack előkészítő szkriptjei, amelyek a 200-as választ várják az SQS-től és az SNS-től.

Párhuzamos feladatok ütközései

Több teszt is párhuzamosan futhat egy Docker-hoszton, ezért a konténerek és hálózatok neveinek egyedinek kell lenniük. Továbbá, a különböző ágakból származó tesztek is párhuzamosan futhatnak, ezért nem elég a saját neveiket megadni minden compose fájlban.

Megoldás: a szkript egyedi értéket állít be a COMPOSE_PROJECT_NAME változónak.

Windows sajátosságai

A Docker Windows alatti használatakor számos dologra szeretném felhívni a figyelmet, mivel ez a tapasztalat fontos a hibák megértéséhez.

  1. A konténerben lévő shell szkripteknek Linux végeken kell lennie.
    A CR karakter a shellben szintaktikai hibát jelent. A hibaüzenetből nehéz kideríteni, hogy erről van szó. Ezeknek a szkripteknek a Windows alatti szerkesztésekor megfelelő szövegszerkesztő szükséges. Ezenkívül a verziókezelő rendszert helyesen kell beállítani.

Így állítható be a git:

git config core.autocrlf input

  1. A Git-bash emulálja a Linux szabványos mappáit, és amikor exe fájlt (beleértve a docker.exe-t is) hívnak meg, az abszolút Linux utak helyett Windows utakat cserél. Ez azonban nincs értelme a nem helyi gépen található útvonalakra (vagy konténerbeli utakra). Ez a viselkedés nem kapcsolható ki.

Megoldás: kiegészíteni az útvonal elejét további per jel: \/\/bin helyett \/bin. A Linux érti az ilyen utakat, számára a több per jel is egyet jelent. De a git-bash nem ismeri fel ezeket az utakat és nem próbálja átalakítani őket.

Napló kimenet

A tesztek futtatása során örömmel látnám a naplókat a Newmantól és a tesztelt szolgáltatástól is. Mivel ezeknek a naplóeseményeknek a kapcsolataik van egymással, azok egy konzolon való egyesítése sokkal kényelmesebb, mint két külön fájl. A Newmant a következő paranccsal indítják: docker-compose run, és ezért a kimenete a konzolba kerül. Azonban meg kell oldani, hogy a szolgáltatás kimenete is oda kerüljön.

Az eredeti megoldás az volt, hogy docker-compose up flag nélkül -d, de a shell lehetőségeit felhasználva ezt a folyamatot a háttérbe küldtük:

docker-compose up <service> &

Ez működött, amíg nem kellett a Docker naplókat egy külső szolgáltatásba küldeni. docker-compose up megszüntette a naplózást a konzolon. Azonban a parancs működött docker attach.

Megoldás:

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

Azonosító ütközés a teszt iterációk során

A tesztek több iteráción keresztül futnak. A database nem kerül törlésre. Az adatbázisban lévő bejegyzések egyedi azonosítókkal rendelkeznek. Ha konkrét azonosítókat írunk a lekérdezésekbe, a második iterációnál ütközést kapunk.

Ennek elkerüléséhez, vagy az azonosítóknak egyedinek kell lenniük, vagy el kell törölni az összes, a teszt által létrehozott objektumot. Néhány objektum törlése nem megengedett, az előírások szerint.

Megoldás: GUID-ok generálása scriptel Postman-ban.

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

Ezután a lekérdezésben használja a karaktert {{myUUID}}, amely a változó értékére cserélődik.

Interakció a LocalStack-en keresztül

Ha a tesztelt szolgáltatás SQS sorozatot olvas vagy ír, ahhoz, hogy ezt ellenőrizzük, a tesztnek is kell, hogy dolgozzon ezzel a sorral.

Megoldás: lekérdezések Postman-ból a LocalStack-hez.

Az AWS szolgáltatásaik API-ja dokumentált, ami lehetővé teszi a lekérdezések végrehajtását SDK nélkül.

Ha a szolgáltatás ír a sorba, akkor olvassuk is, és ellenőrizzük az üzenet tartalmát.

Ha a szolgáltatás SNS-be küld üzeneteket, a LocalStack előkészítésekor egy sor is létrejön, és feliratkozik erre az SNS témára. Minden további lépés a fentieknek megfelelően zajlik.

Ha a szolgáltatásnak olvasnia kell egy üzenetet a sorból, akkor a teszt előző lépésében ezt az üzenetet írjuk a sorba.

HTTP lekérdezések tesztelése a tesztelt mikroszolgáltatásból

Egyes szolgáltatások HTTP-n dolgoznak valami mással, mint az AWS, és néhány AWS funkció nincs implementálva LocalStack-en.

Megoldás: ilyen esetekben segítséget nyújthat a MockServer, amelynek van egy kész képe a Docker Hub. A várt lekérdezéseket és válaszokat HTTP lekérdezéssel állítjuk be. Az API dokumentált, így a Postmanból végezzük a lekérdezéseket.

OAuth azonosítási és engedélyezési tesztelés

Használunk OAuth-ot és JSON Web Tokens (JWT). A teszthez szükség van egy OAuth szolgáltatóra, amelyet helyben el tudunk indítani.

Az összes interakció a szolgáltatás és az OAuth szolgáltató között két lekérdezésre korlátozódik: először a konfigurációt kérjük le /.well-known/openid-configuration, majd a konfigurációból kérjük le a nyilvános kulcsot (JWKS). Mindez statikus tartalom.

Megoldás: a tesztünk OAuth szolgáltatója egy statikus tartalom szerver és két fájl rajta. Egy token generálva lett egyszer és el lett mentve a Git-be.

Jellemzők a SignalR tesztelésében

A Postman nem működik web socketekkel. A SignalR tesztelésére egy speciális eszköz készült.

A SignalR ügyfél nemcsak böngésző lehet. Létezik .NET Core-ra készült kliens könyvtár is. A .NET Core-on írt kliens kapcsolódik, hitelesíti magát és várakozik a megadott üzenetsorrendre. Ha váratlan üzenetet kap vagy a kapcsolat megszakad, a kliens 1-es kóddal leáll. Az utolsó várt üzenet megérkezésekor 0-ás kóddal zárul.

Egyidejűleg a Newman is fut. Több ügyfélt példányt indítanak el, hogy ellenőrizzék, hogy az üzenetek mindenkinek eljutnak.

Automatikus tesztelés mikroszolgáltatásokkal Docker alatt a folyamatos integrációhoz

Több kliens indításához az alábbi opciót használjuk —scale a docker-compose parancssorában.

A Postman indítása előtt a szkript megvárja, amíg minden kliens csatlakozik.
A csatlakozás megvárásának problémájával már találkoztunk. Ott szerverek voltak, itt pedig kliensek. Más megközelítésre van szükség.

Megoldás: a konténerben működő kliens a HealthCheckmechanizmust használja, hogy értesítse a hoszton lévő szkriptet az állapotáról. A kliens egy fájlt hoz létre egy meghatározott úton, mondjuk /healthcheck, amint a kapcsolat létrejön. A HealthCheck szkript a docker fájlban így néz ki:

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

A parancs a docker inspect a konténernek a szokásos állapotát, az egészségi állapotát és a kilépési kódját mutatja.

A Newman befejezése után a szkript ellenőrzi, hogy az összes klienssel rendelkező konténer 0-ás kóddal befejeződött-e.

Boldogság létezik

Miután túljutottunk a fent említett nehézségeken, stabilan működő tesztek álltak rendelkezésünkre. A tesztek során minden szolgáltatás egységesen működik, interakcióban van az adatbázissal és az Amazon LocalStack-kel.

Ezek a tesztek védelmet nyújtanak a 30+ fejlesztőből álló csapat számára a komplex interakciókkal rendelkező alkalmazási hibák ellen, a 10+ mikroszolgáltatás gyakori telepítései során.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster