Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

V projektech souvisejících s vývojem mikroservisní architektury se CI/CD stává z příjemné možnosti nezbytnou nutností. Automatizované testování je nedílnou součástí kontinuální integrace, přičemž rozumný přístup k ní může týmu přinést mnoho příjemných večerů s rodinou a přáteli. V opačném případě hrozí, že projekt nebude nikdy dokončen.

Celý kód mikroservisu lze pokrýt unit testy s mock objekty, ale to pouze částečně řeší problém a zanechává mnoho otázek a komplikací, zejména při testování práce s daty. Největší výzvou je jako vždy testování konzistence dat v relační databázi, testování spolupráce s cloudovými službami a nesprávné předpoklady při psaní mock objektů.

To vše a ještě víc se dá vyřešit testováním celého mikroservisu v Docker kontejneru. Nezpochybnitelnou výhodou pro zajištění platnosti testů je, že testy probíhají na tom samém Docker obraze, který jde do produkce.

Automatizace takového přístupu představuje řadu problémů, jejichž řešení bude popsáno níže:

  • konflikty paralelních úloh v jednom docker hostiteli;
  • konflikty identifikátorů v databázi při iteracích testu;
  • čekání na připravenost mikroservisů;
  • sjednocení a výstup logů do externích systémů;
  • testování odchozích HTTP požadavků;
  • testování webových soketů (pomocí SignalR);
  • testování autentizace a autorizace OAuth.

Toto je článek na motivy mého vystoupení na SECR 2019. Takže pro ty, kteří mají lenost číst, zde je záznam vystoupení.

Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

V článku ukážu, jak pomocí skriptu spustit v Dockeru testovaný servis, databázi a služby Amazon AWS, pak testy na Postmanu a po jejich dokončení zastavit a smazat vytvořené kontejnery. Testy se provádějí při každé změně kódu. Takto se ujistíme, že každá verze správně pracuje s databází a službami AWS.

Stejný skript spouští jak sami developeri na svých Windows desktopách, tak server Gitlab CI pod Linuxem.

Aby bylo zavedení nových testů opodstatněné, nesmí vyžadovat instalaci dalších nástrojů ani na počítači vývojáře, ani na serveru, kde se testy spouštějí při commitu. Docker tuto úlohu vyřeší.

Test by měl fungovat na místním serveru z následujících důvodů:

  • Síť nikdy není zcela spolehlivá. Z tisíce požadavků může jeden selhat;
    Automatický test v takovém případě neproběhne, činnost se zastaví a bude nutné hledat příčinu v logách;
  • Některé externí služby nepovolují příliš časté požadavky.

Kromě toho je nežádoucí využívat testovací prostředí, protože:

  • Testovací prostředí může poškodit nejen špatný kód, který na něm běží, ale i data, která správný kód není schopen zpracovat;
  • Bez ohledu na to, jak se snažíme vrátit všechny změny provedené testem, v průběhu samotného testu se může něco pokazit (jinak, proč test?).

O projektu a organizaci procesu

Naše firma vyvíjela mikroslužbu ve webové aplikaci fungující v Dockeru na cloudové platformě Amazon AWS. V projektu již byly použity jednotkové testy, nicméně často se vyskytovaly chyby, které jednotkové testy neodhalily. Bylo třeba testovat celou mikroslužbu spolu s databází a službami Amazon.

V projektu se aplikuje standardní proces kontinuální integrace, který zahrnuje testování mikroslužby při každém commitu. Po přidělení úkolu vývojář provede změny v mikroslužbě, sám ji manuálně testuje a spouští všechny dostupné automatické testy. Pokud je to nutné, vývojář mění testy. Pokud nejsou zjištěny žádné problémy, dojde ke commitu do větve daného úkolu. Po každém commitu se na serveru automaticky spouští testy. Sloučení do hlavní větve a spuštění automatických testů na ní probíhá po úspěšném revu. Pokud testy na hlavní větvi proběhnou, služba se automaticky aktualizuje v testovacím prostředí na Amazon Elastic Container Service (testovacím prostředí). Testovací prostředí je nezbytné pro všechny vývojáře a testery, a je nežádoucí ho poškodit. Testeři v tomto prostředí ověřují opravu nebo novou funkci prováděním manuálních testů.

Architektura projektu

Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

Aplikace se skládá z více než deseti služeb. Některé z nich jsou napsané v .NET Core a některé v NodeJs. Každá služba běží v Docker kontejneru v Amazon Elastic Container Service. Každá má svou vlastní databázi Postgres, a některé mají také Redis. Neexistují žádné sdílené databáze. Pokud více služeb potřebuje stejná data, tato data se při jejich změně posílají každé z těchto služeb pomocí SNS (Simple Notification Service) a SQS (Amazon Simple Queue Service), a služby je ukládají do svých oddělených databází.

SQS a SNS

SQS umožňuje prostřednictvím protokolu HTTPS vkládat zprávy do fronty a číst zprávy z fronty.

Pokud více služeb čte jednu frontu, každá zpráva přijde pouze jedné z nich. To je užitečné při spouštění několika instancí jedné služby pro rozložení zátěže mezi nimi.

Pokud je potřeba, aby každá zpráva byla doručena několika službám, musí mít každý příjemce svou frontu, a pro duplikaci zpráv do několika front je potřeba SNS.

V SNS vytvoříte téma a přihlásíte k němu například SQS frontu. Na téma lze odesílat zprávy. Zpráva se přitom odešle do každé fronty, která je na toto téma přihlášena. V SNS není metoda pro čtení zpráv. Pokud je během ladění nebo testování potřeba zjistit, co se odesílá do SNS, můžete vytvořit SQS frontu, přihlásit ji k příslušnému tématu a číst frontu.

Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

API Gateway

Většina služeb není dostupná přímo z internetu. Přístup se provádí prostřednictvím API Gateway, která kontroluje přístupová práva. Toto je také naše služba a pro ni také existují testy.

Upozornění v reálném čase

Aplikace používá SignalR, aby zobrazovala uživateli upozornění v reálném čase. To je realizováno ve službě upozornění. Ta je dostupná přímo z internetu a sama pracuje s OAuth, protože integrovat podporu WebSocketů do Gateway se ukázalo jako neefektivní, ve srovnání s integrací OAuth a služby upozornění.

Známý přístup k testování

Jednotkové testy nahrazují maketami takové věci, jako je databáze. Pokud se například mikroslužba pokusí vytvořit záznam v tabulce s cizím klíčem a záznam, na který tento klíč odkazuje, neexistuje, požadavek nemůže být splněn. Jednotkové testy to nemohou odhalit.

V v článku od Microsoftu se navrhuje používat in-memory databázi a vkládat makety.

In-memory databáze je jednou z databázových systémů, které podporuje Entity Framework. Byla vytvořena speciálně pro testování. Data v takové databázi jsou uchovávána pouze do okamžiku ukončení procesu, který ji používá. Není třeba vytvářet tabulky a integrita dat není kontrolována.

Mock objekty modelují nahrazující třídu pouze tolik, kolik developer testu rozumí jejímu fungování.

Jak dosáhnout automatického spuštění Postgresu a provedení migrace při spuštění testu, není uvedeno v článku od Microsoftu. Moje řešení to dělá a kromě toho se do samotného mikroservisu nepřidává žádný kód speciálně pro testy.

Přejdeme k řešení

Během vývoje se ukázalo, že jednotkových testů je nedostatečně, aby včas odhalily všechny problémy, proto bylo rozhodnuto přistoupit k tomu z jiné strany.

Nastavení testovacího prostředí

Prvním úkolem je nasadit testovací prostředí. Kroky, které jsou potřebné k spuštění mikroservisu:

  • Nastavit testovanou službu na lokální prostředí, v proměnných prostředí se uvádějí údaje pro připojení k databázi a AWS;
  • Spustit Postgres a provést migraci spuštěním Liquibase.
    V relačních databázových systémech je před zápisem dat do databáze třeba vytvořit schéma dat, jednoduše řečeno, tabulky. Při aktualizaci aplikace je třeba tabulky přizpůsobit nové verzi, přičemž ideálně bez ztráty dat. To se nazývá migrace. Vytvoření tabulek v původně prázdné databázi je zvláštní případ migrace. Migraci je možné integrovat přímo do aplikace. Jak v .NET, tak v NodeJS existují frameworky pro migraci. V našem případě jsou mikroservisy z bezpečnostních důvodů zbaveny práva měnit schéma dat, a migrace se provádí pomocí Liquibase.
  • Spustit Amazon LocalStack. To je implementace služeb AWS pro spuštění u sebe. Pro LocalStack je k dispozici hotový obraz v Docker Hub.
  • Spustit skript pro vytvoření potřebných entit v LocalStack. Shell skripty používají AWS CLI.

Pro testování na projektu se používá Postman. Byl tu už dříve, ale spouštěl se ručně a testoval aplikaci, již nasazenou na standu. Tento nástroj umožňuje provádět libovolné HTTP(S) požadavky a ověřovat shodu odpovědí s očekáváním. Požadavky se spojují do kolekce a lze je spustit celou kolekci najednou.

Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

Jak funguje automatický test

Během testu v Dockeru funguje vše: jak testovaná služba, tak Postgres, nástroj pro migraci a Postman, respektive jeho konzolová verze – Newman.

Docker řeší celou řadu problémů:

  • Nezávislost na konfiguraci hostitele;
  • Instalace závislostí: docker stahuje obrazy z Docker Hub;
  • Vrácení systému do původního stavu: jednoduše odstraníme kontejnery.

Docker-compose sjednocuje kontejnery do virtuální sítě, izolované od internetu, ve které si kontejnery navzájem najdou podle doménových jmen.

Testem řídí shellový skript. Pro spuštění testu na Windows používáme git-bash. Tímto způsobem stačí jeden skript jak pro Windows, tak pro Linux. Git a Docker má nainstalované všechno vývojářské tým na projektu. Při instalaci Gitu na Windows se instaluje git-bash, takže ho má také každý.

Skript provádí následující kroky:

  • Sestavení docker obrazů
    docker-compose build
  • Spuštění DB a LocalStack
    docker-compose up -d
  • Migrování DB a příprava LocalStack
    docker-compose run
  • Spuštění testované služby
    docker-compose up -d
  • Spuštění testu (Newman)
  • Zastavení všech kontejnerů
    docker-compose down
  • Zveřejnění výsledků v Slacku
    Máme chat, kam přicházejí zprávy se zeleným zaškrtnutím nebo červeným křížkem a odkazem na log.

V těchto krocích jsou zapojeny následující Docker obrazy:

  • Testovaná služba – stejný obraz, jaký je pro produkci. Konfigurace pro test – prostřednictvím proměnných prostředí.
  • Pro Postgres, Redis a LocalStack se používají hotové obrazy z Docker Hub. Pro Liquibase a Newman jsou také k dispozici hotové obrazy. Vytváříme si vlastní na jejich základě, přidáváme tam naše soubory.
  • Pro přípravu LocalStack se používá hotový obraz AWS CLI, a na jeho základě se vytváří obraz obsahující skript.

Použitím volumes, není nutné stavět Docker obraz pouze pro přidání souborů do kontejneru. Nicméně, volumes nejsou vhodné pro naše prostředí, protože úlohy Gitlab CI pracují samy v kontejnerech. Z takového kontejneru lze spravovat docker, ale volumes montují složky pouze z hostitelského systému, a ne z jiného kontejneru.

Problémy, se kterými se můžete setkat

Očekávání připravenosti

Když je kontejner se službou spuštěn, ještě to neznamená, že je připraven přijímat spojení. Je třeba si počkat na spojení, aby se mohlo pokračovat.

Tento úkol se někdy řeší pomocí skriptu wait-for-it.sh, který čeká na možnost navázat TCP spojení. Nicméně LocalStack může vrátit chybu 502 Bad Gateway. Kromě toho se skládá z mnoha služeb, a pokud je jedna z nich připravena, nic to neříká o ostatních.

Řešení: skripty přípravy LocalStack, které čekají na odpověď 200 jak od SQS, tak od SNS.

Konflikty paralelních úloh

Několik testů může běžet současně na jednom Docker hostiteli, takže jména kontejnerů a sítí musí být jedinečná. Navíc testy z různých větví jedné služby mohou také běžet současně, proto není dostatečné napsat v každém compose souboru svá jména.

Řešení: skript nastavuje jedinečnou hodnotu proměnné COMPOSE_PROJECT_NAME.

Vlastnosti Windows

Při používání Dockeru ve Windows je řada věcí, na které chci upozornit, protože tato zkušenost je důležitá pro pochopení příčin chyb.

  1. Shell skripty v kontejneru by měly mít linuxové konce řádků.
    Symbol CR pro shell je syntaktická chyba. Z chybové zprávy je obtížné pochopit, že jde o tento problém. Při editaci takových skriptů ve Windows je potřeba správný textový editor. Kromě toho by mělo být nastavení systému správy verzí správně nakonfigurováno.

Takto se nastavuje git:

git config core.autocrlf input

  1. Git-bash emuluje standardní složky Linuxu a při volání exe souboru (včetně docker.exe) nahrazuje absolutní linuxové cesty cestami Windows. Nicméně to nedává smysl pro cesty, které nejsou na místním stroji (nebo cesty v kontejneru). Toto chování se nedá vypnout.

Řešení: přidávat další lomítko na začátek cesty: \/\/bin místo \/bin. Linux tyto cesty rozumí, pro něj je několik lomítek to samé jako jedno. Ale git-bash tyto cesty neuznává a nepokouší se je převádět.

Výstup logů

Při provádění testů bych chtěl vidět logy jak z Newmana, tak ze testované služby. Jelikož události těchto logů jsou mezi sebou propojené, kombinace v jedné konzoli je mnohem pohodlnější než dva samostatné soubory. Newman je spuštěn prostřednictvím docker-compose run, a proto se jeho výstup dostane do konzole. Zbývá zajistit, aby tam šel i výstup služby.

Původní řešení spočívalo v tom, že uděláme docker-compose up bez příznaku -d, ale pomocí možností shellu poslat tento proces do pozadí:

docker-compose up <service> &

To fungovalo, dokud nebylo potřeba posílat logy z dockera do externí služby. docker-compose up přestala vycházet logy do konzole. Přesto fungoval příkaz docker attach.

Řešení:

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

Konflikt identifikátorů při iteracích testu

Testy se spouští několika iteracemi. Při tom nedochází k vymazání databáze. Záznamy v databázi mají unikátní ID. Pokud zapíšete konkrétní ID do dotazů, v druhé iteraci dojde ke konfliktu.

Abychom se tomu vyhnuli, musí být ID buď unikátní, nebo je třeba odstranit všechny objekty vytvořené testem. Některé objekty odstranit nelze, podle požadavků.

Řešení: generovat GUIDy skripty v Postman.

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

Poté v dotazu použijte symbol {{myUUID}}, který bude nahrazen hodnotou proměnné.

Interakce přes LocalStack

Pokud testovaná služba čte SQS frontu nebo do ní zapisuje, pro ověření toho také musí test pracovat s touto frontou.

Řešení: dotazy z Postman k LocalStack.

API služeb AWS je zdokumentováno, což umožňuje provádět dotazy bez SDK.

Pokud služba zapisuje do fronty, čteme ji a kontrolujeme obsah zprávy.

Pokud služba posílá zprávy do SNS, ve fázi přípravy se vytváří také fronta a přihlašuje se k tomuto SNS tématu. Dále se vše redukuje na výše uvedené.

Pokud služba má přečíst zprávu z fronty, pak v předchozím kroku testu zapíšeme tuto zprávu do fronty.

Testování HTTP dotazů, které vycházejí z testované mikroservisy

Některé služby pracují přes HTTP s něčím jiným než AWS a některé funkce AWS nejsou v LocalStack implementovány.

Řešení: v těchto případech může pomoci MockServer, který má připravený obraz v Docker Hub. Očekávané dotazy a odpovědi na ně se nastavují HTTP dotazem. API je zdokumentováno, takže provádíme dotazy z Postman.

Testování autentizace a autorizace OAuth

Používáme OAuth a JSON Web Tokens (JWT). Pro test potřebujeme OAuth poskytovatele, kterého můžeme spustit lokálně.

Veškerá interakce služby s OAuth poskytovatelem se skládá ze dvou dotazů: nejprve se vyžaduje konfigurace /.well-known/openid-configuration, a poté se žádá o veřejný klíč (JWKS) na adrese z konfigurace. To všechno je statický obsah.

Řešení: náš testovací OAuth poskytovatel – je to server statického obsahu a dva soubory na něm. Token byl vygenerován jednou a zcommittován v Gitu.

Specifika testování SignalR

S webovými sokety Postman nefunguje. Pro testování SignalR byl vytvořen speciální nástroj.

Klientem SignalR nemusí být pouze prohlížeč. Existuje klientská knihovna pro .NET Core. Klient napsaný v .NET Core navazuje spojení, autentizuje se a čeká na určitou sekvenci zpráv. Pokud je přijata neočekávaná zpráva nebo se spojení přeruší, klient se ukončí s kódem 1. Po přijetí poslední očekávané zprávy se ukončí s kódem 0.

Současně s klientem běží Newman. Klientů se spouští několik, aby se ověřilo, že zprávy jsou doručovány všem, kterým mají.

Automatizované testování mikroslužeb v Dockeru pro kontinuální integraci

Pro spuštění několika klientů se používá volba —scale v příkazovém řádku docker-compose.

Před spuštěním Postman skript čeká na navázání spojení všemi klienty.
Problém s čekáním na spojení jsme už zažili. Ale tam byly servery, tady je klient. Potřebujeme jiný přístup.

Řešení: klient v kontejneru používá mechanismus HealthCheck, aby informoval skript na hostiteli o svém stavu. Klient vytvoří soubor na určité cestě, například /healthcheck, hned jak je spojení navázáno. HealthCheck skript v dockerfile vypadá takto:

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

Tým docker inspect ukazuje pro kontejner běžný stav, health-status a kód ukončení.

Po ukončení Newmana skript ověřuje, že všechny kontejnery s klientem se ukončily, a to s kódem 0.

Štěstí existuje

Jakmile jsme překonali výše uvedené obtíže, měli jsme sada stabilně fungujících testů. V testech každý služba funguje jako celek, interaguje s databází a s Amazon LocalStack.

Tyto testy chrání tým s více než 30 vývojáři před chybami v aplikaci se složitou interakcí více než 10 mikroslužeb při častých nasazeních.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster