Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

В проектите, свързани с разработването на микросервисна архитектура, CI/CD преминава от категория на приятна възможност в категория на остра необходимост. Автоматичното тестване е неотменима част от непрекъснатата интеграция, а добрият подход към него може да дари на екипа многобройни приятни вечери с семейството и приятелите. В противен случай проектът рискува да не бъде завършен никога.

Може да се покрие целият код на микросервиса с юнит тестове с мок обекти, но това решава само част от задачата и оставя множество въпроси и трудности, особено при тестването на работа с данни. Както винаги, най-остри са тестовете за консистентност на данни в реляционна БД, тестовете за работа с облачни услуги и неправилни предположения при написването на мок обекти.

Всичко това и малко повече се решава чрез тестване на целия микросервис в Docker контейнер. Несъмнено предимство за осигуряване на валидността на тестовете е, че тестват същите Docker образи, които отиват в продукция.

Автоматизацията на такъв подход представя редица проблеми, чието решение ще бъде описано малко по-долу:

  • конфликти на паралелни задачи в един Docker хост;
  • конфликти на идентификатори в БД при итерации на теста;
  • очакване на готовността на микросервисите;
  • обединяване и извеждане на логовете във външни системи;
  • тестване на изходящи HTTP заявки;
  • тестване на уеб сокети (чрез SignalR);
  • тестване на аутентикация и авторизация OAuth.

Това е статия по мотиви моето представяне от SECR 2019. Така че за тези, които не искат да четат, ето запис на лекцията.

Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

В статията ще разкажа как с помощта на скрипт да стартирате в Docker тестваната услуга, базата данни и услугите на Amazon AWS, след което тестовете в Postman и след приключването им да спрете и изтриете създадените контейнери. Тестовете се изпълняват при всяка промяна в кода. По този начин се уверяваме, че всяка версия работи коректно с базата данни и услугите на AWS.

Един и същ скрипт стартират както самите разработчици на своите Windows десктопи, така и сървърът Gitlab CI под Linux.

За да бъде внедряването на нови тестове оправдано, то не трябва да изисква инсталиране на допълнителни инструменти нито на компютъра на разработчика, нито на сървъра, където тестовете се стартират при комит. Docker решава тази задача.

Тестът трябва да работи на локален сървър по следните причини:

  • Мрежата не е абсолютно надеждна. От хиляда заявки, една може да не мине;
    Автоматичният тест в такъв случай няма да премине, работата ще спре, ще трябва да се търси причината в логовете;
  • Прекалено честите заявки не се допускат от някои външни услуги.

Освен това, ангажиране на стенд не е желателно, защото:

  • Стендът може да бъде повреден не само от лош код, работещ на него, но и от данни, които правилният код не може да обработи;
  • Каквото и да правим, за да върнем всичките промени, направени от теста, по време на самия тест, нещо може да се обърка (иначе, защо е тестът?).

За проекта и организацията на процеса

Нашата компания разработва микросервисно уеб приложение, работещо в Docker в облака на Amazon AWS. В проекта вече се използваха юнит тестове, но често възникваха грешки, които юнит тестовете не откриваха. Необходимо беше да се тества целият микросервис заедно с базата данни и услугите на Amazon.

В проекта се прилага стандартен процес на непрекъсната интеграция, включващ тестване на микросервиса при всяко комитиране. След назначаване на задача, разработчикът внася промени в микросервиса, тества го ръчно и стартира всички налични автоматични тестове. При необходимост разработчикът променя тестовете. Ако проблеми не бъдат открити, се прави комит в клон на данната задача. След всяко комитиране тестовете автоматично се стартират на сървъра. Сливането в общия клон и стартирането на автоматичните тестове на него се извършва след успешен преглед. Ако тестовете на общия клон преминат, услугата автоматично се актуализира в тестовата среда на Amazon Elastic Container Service (стенд). Стендът е необходим за всички разработчици и тестери и не е желателно да се поврежда. Тестерите на тази среда проверяват фикса или новата функция, извършвайки ръчни тестове.

Архитектура на проекта

Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

Приложението се състои от повече от десет услуги. Някои от тях са написани на .NET Core, а други на NodeJs. Всяка услуга работи в Docker контейнер в Amazon Elastic Container Service. Всяка има собствена Postgres база, а някои разполагат и с Redis. Няма общи бази. Ако няколко услуги се нуждаят от едни и същи данни, то тези данни при промяна се предават на всяка от услугите чрез SNS (Simple Notification Service) и SQS (Amazon Simple Queue Service), и услугите ги запазват в своите отделни бази.

SQS и SNS

SQS позволява чрез HTTPS протокол да се поставят съобщения в опашка и да се четат съобщения от опашката.

Ако няколко услуги четат от една опашка, всяко съобщение достига само до една от тях. Това е полезно при стартиране на няколко инстанции на една и съща услуга за разпределение на натовареността между тях.

Ако трябва всяко съобщение да бъде доставяно на няколко услуги, всяко получател трябва да има своя опашка, а за дублиране на съобщения в няколко опашки е нужен SNS.

В SNS създавате topic и абонирате за него, например, SQS опашка. В topic може да се изпращат съобщения. При това съобщението се изпраща до всяка опашка, абонирана за този topic. В SNS няма метод за четене на съобщения. Ако в процеса на отстраняване на проблеми или тестване е необходимо да разберете какво се изпраща в SNS, можете да създадете SQS опашка, да я абонирате за необходимия topic и да четете опашката.

Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

API Gateway

Повечето услуги не са достъпни директно от интернет. Достъпът се осъществява чрез API Gateway, който проверява правата за достъп. Това също е наша услуга, и за нея също има тестове.

Уведомления в реално време

Приложението използва SignalR, за да показва на потребителя уведомления в реално време. Това е реализирано в услуга за уведомления. Тя е достъпна директно от интернет и сама работи с OAuth, тъй като интегрирането на поддръжка за Web сокети в Gateway се е оказало нецелесообразно в сравнение с интеграцията на OAuth и услугата за уведомления.

Известен подход към тестването

Юнит тестовете заместват мока с обекти, подобни на базата данни. Ако микросервис, например, се опитва да създаде запис в таблица с външен ключ, а записът, на който този ключ се позовава, не съществува, то заявката не може да бъде изпълнена. Юнит тестовете не могат да открият това.

В статия от Microsoft предлага използването на in-memory база и внедряване на мок обекти.

In-memory базата е една от СУБД, които поддържа Entity Framework. Тя е създадена специално за тестове. Данните в тази база се съхраняват само до приключване на процеса, който я използва. Не е необходимо да се създават таблици и целостта на данните не се проверява.

Mock-обектите моделират заменимия клас само в мярка, в която разработчикът на теста разбира неговата работа.

В статията на Microsoft не е посочено как да се достига до автоматично стартиране на Postgres и изпълнение на миграцията при стартиране на теста. Моето решение го извършва и, освен това, в самия микросервис не се добавя никакъв код специално за тестове.

Преминаваме към решението

В процеса на разработка стана ясно, че юнит-тестовете не са достатъчни, за да бъдат своевременно открити всички проблеми, затова беше решено да се подходи към този въпрос от друга страна.

Настройка на тестовото окружение

Първата задача е да се разгръне тестовото окружение. Стъпките, които са необходими за стартиране на микросервиса:

  • Настройте тествания сервис за локалната среда, в променливите на средата се посочват реквизитите за свързване с базата и AWS;
  • Стартирайте Postgres и извършете миграцията, стартирайки Liquibase.
    В релационните СУБД преди да запишете данни в базата, е необходимо да се създаде схема на данните, по-просто казано, таблици. При актуализация на приложението таблиците трябва да бъдат приведени във вид, използван от новата версия, като е желателно да не се загубят данни. Това се нарича миграция. Създаването на таблици в първоначално празна база е частен случай на миграция. Миграцията може да бъде вградена в самото приложение. И в .NET, и в NodeJS има фреймворкове за миграция. В нашия случай, с оглед на сигурността, микросервисите нямат право да променят схемата на данните и миграцията се извършва с помощта на Liquibase.
  • Стартирайте Amazon LocalStack. Това е реализация на AWS услуги за локално изпълнение. За LocalStack има готов образ в Docker Hub.
  • Стартирайте скрипт за създаване на необходимите сущности в LocalStack. Shell-скриптовете използват AWS CLI.

За тестване на проекта се използва Postman. Той беше наличен и по-рано, но го стартираха ръчно и тестваха приложението, вече реализирано на стенд. Този инструмент позволява произволни HTTP(S) заявки и проверка на съответствието на отговорите с очакванията. Запитванията се обединяват в колекция и може да се стартира цялата колекция наведнъж.

Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

Как е устроен автоматичният тест

По време на теста в Docker всичко работи: и тестваната услуга, и Postgres, и инструментът за миграция, и Postman, или по-скоро неговата конзолна версия – Newman.

Docker решава редица проблем:

  • Независимост от конфигурацията на хоста;
  • Инсталация на зависимости: Docker изтегля образи от Docker Hub;
  • Връщане на системата в първоначално състояние: просто премахваме контейнерите.

Docker-compose обединява контейнерите в виртуална мрежа, изолирана от интернет, в която контейнерите се намират помежду си по домейни.

Тестът се управлява от shell-скрипт. За стартиране на теста под Windows използваме git-bash. По този начин, един скрипт е достатъчен и за Windows, и за Linux. Git и Docker са инсталирани на всички разработчици в проекта. При установяване на Git под Windows се инсталира и git-bash, така че той също е наличен за всички.

Скриптът изпълнява следните стъпки:

  • Изграждане на Docker образи
    docker-compose build
  • Стартиране на БД и LocalStack
    docker-compose up -d
  • Миграция на БД и подготовка на LocalStack
    docker-compose run
  • Стартиране на тестваната услуга
    docker-compose up -d
  • Стартиране на теста (Newman)
  • Спиране на всички контейнери
    docker-compose down
  • Публикуване на резултатите в Slack
    Имайме чат, в който постъпват съобщения със зелена отметка или червен кръст и линк към лог.

В тези стъпки са включени следните Docker образи:

  • Тестваната услуга е същият образ, какъвто и за продукцията. Конфигурацията за теста е чрез променливи на средата.
  • За Postgres, Redis и LocalStack се използват готови образи от Docker Hub. Има и готови образи за Liquibase и Newman. Ние изграждаме своите на тяхна основа, добавяйки нашите файлове.
  • За подготовка на LocalStack се използва готов образ AWS CLI, на базата на който се създава образ, съдържащ скрипт.

Използвайки volumes, не е необходимо да се изгражда Docker образ само за добавяне на файлове в контейнера. Въпреки това, volumes не са подходящи за нашата среда, защото задачите на Gitlab CI работят в контейнери. От такъв контейнер е възможно да управляем Docker, но volumes монтират папки само от хост системата, а не от друг контейнер.

Проблеми, с които може да се срещнете

Чакане на готовност

Когато контейнерът с услугата е стартиран, това все още не означава, че е готов да приема връзки. Трябва да изчакате връзките, за да продължите.

Тази задача понякога се решава с помощта на скрипт wait-for-it.sh, който чака възможността за установяване на TCP свързване. Въпреки това, LocalStack може да даде грешка 502 Bad Gateway. Освен това, той се състои от множество услуги и, ако една от тях е готова, това не говори нищо за останалите.

Решение: скриптове за подготовка на LocalStack, които очакват отговор 200 и от SQS, и от SNS.

Конфликти на паралелни задачи

Неколко теста могат да работят едновременно в един Docker хост, затова имената на контейнерите и мрежите трябва да са уникални. Освен това, тестовете от различни клонове на една и съща услуга също могат да работят едновременно, затова не е достатъчно да се напишат свои имена в всеки compose файл.

Решение: скриптът задава уникална стойност на променливата COMPOSE_PROJECT_NAME.

Особености на Windows

При използване на Docker в Windows има няколко неща, на които искам да обърна вниманието ви, тъй като този опит е важен за разбирането на причините за грешки.

  1. Шелл скриптовете в контейнера трябва да имат линукс край на ред.
    Символът CR за шелла е синтактична грешка. От съобщението за грешка е трудно да се разбере, че проблемът е в това. При редактиране на такива скриптове в Windows е нужен правилният текстов редактор. Освен това, системата за контрол на версиите трябва да бъде настроена правилно.

Така се настройва git:

git config core.autocrlf input

  1. Git-bash емулира стандартните папки на Linux и при извикване на exe файл (включително docker.exe) заменя абсолютните Linux пътища с Windows пътища. Въпреки това, това няма смисъл за пътищата, които не са на локалната машина (или пътищата в контейнера). Такова поведение не може да бъде изключено.

Решение: добавяне на допълнителен слеш в началото на пътя: //bin вместо /bin. Linux разбира такива пътища, за него няколко слеша са равни на един. Но git-bash не разпознава такива пътища и не се опитва да ги преобразува.

Изход на логовете

При изпълнение на тестовете е хубаво да виждаме логовете както на Newman, така и на тестваната услуга. Тъй като събитията в тези логове са свързани помежду си, комбинирането им в едно конзолно прозорче е много по-удобно от две отделни файлове. Newman се стартира чрез docker-compose run, затова изходът му попада в конзолата. Остава да направим така, че там да попада и изходът на услугата.

Първоначалното решение беше да се прави docker-compose up без флага -d, но, използвайки възможностите на шелла, да се изпраща този процес на заден план:

docker-compose up  &

Това работеше, докато не се наложи да се изпращат логове от Docker в странична услуга. docker-compose up спря да извежда логове в конзолата. Въпреки това, командата работеше docker attach.

Решение:

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

Конфликт на идентификаторите при итерации на теста

Тестовете се изпълняват с няколко итерации. Базата данни при това не се изчиства. Записите в базата имат уникални ID. Ако запишем конкретни ID в заявките, при втората итерация получаваме конфликт.

За да го избегнем, или ID трябва да са уникални, или трябва да премахнем всички обекти, създадени от теста. Не е позволено да се премахват някои обекти в съответствие с изискванията.

Решение: генериране на GUID с помощта на скриптове в Postman.

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

След това в заявката да използваме символа {{myUUID}}, който ще бъде заменен със стойността на променливата.

Взаимодействие чрез LocalStack

Ако тестваният сервис чете SQS опашка или записва в нея, тогава за проверка тестът също трябва да работи с тази опашка.

Решение: заявките от Postman към LocalStack.

API на AWS услугите е документирано, което позволява да се правят заявки без SDK.

Ако сервисът записва в опашката, ние я четем и проверяваме съдържанието на съобщението.

Ако сервисът изпраща съобщения в SNS, на етапа на подготовка LocalStack създава също и опашка и се подписва на този SNS темата. След това всичко свършва с описаното по-горе.

Ако сервисът трябва да прочете съобщение от опашката, то на предишната стъпка на теста записваме това съобщение в опашката.

Тестване на HTTP заявки, излизащи от тествания микросервис

Някои услуги работят по HTTP с нещо освен AWS, а някои функции на AWS не са реализирани в LocalStack.

Решение: в тези случаи може да помогне MockServer, който има готов образ в Docker Hub. Очакваните заявки и отговори на тях се настройват с HTTP заявка. API-то е документирано, затова правим заявки от Postman.

Тестване на удостоверяване и авторизация OAuth

Използваме OAuth и JSON Web Tokens (JWT). За теста ни трябва OAuth доставчик, който можем да стартираме локално.

Цялото взаимодействие на сервиза с OAuth доставчика се свежда до две заявки: първо се иска конфигурация /.well-known/openid-configuration, а след това се иска публичен ключ (JWKS) на адреса от конфигурацията. Всичко това е статично съдържание.

Решение: нашият тестови OAuth доставчик е сървър на статично съдържание и два файла на него. Токенът е генериран веднъж и е комитнат в Git.

Особености при тестването на SignalR

Postman не работи с уеб сокети. За тестиране на SignalR е създаден специален инструмент.

Клиент на SignalR може да бъде не само браузър. Съществува клиентска библиотека за .NET Core. Клиент, написан на .NET Core, установява връзка, преминава аутентификация и очаква определена последователност от съобщения. Ако получи неочаквано съобщение или връзката се разкъса, клиентът приключва с код 1. При получаване на последното очаквано съобщение, той приключва с код 0.

Наравно с клиента работи и Newman. Стартират се няколко клиента, за да се провери дали съобщенията достигат до всички, които трябва.

Автоматизирано тестване на микросервизи в Docker за непрекъсната интеграция

За стартиране на няколко клиента се използва опцията —scale в командния ред на docker-compose.

Преди стартиране на Postman, скриптът изчаква установяването на връзката от всички клиенти.
Проблемът с изчакването на връзката ни вече е срещан. Но там имаше сървъри, а тук е клиент. Нужен е друг подход.

Решение: клиентът в контейнера използва механизма HealthCheck, за да уведоми скрипта на хоста за своето състояние. Клиентът създава файл на определен път, да кажем, /healthcheck, веднага след установяване на връзката. HealthCheck-скриптът в Docker файла изглежда така:

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

Екип docker inspect показва нормален статус, health-статус и код на приключване за контейнера.

След приключването на Newman, скриптът проверява, че всички контейнери с клиента са приключили, и то с код 0.

Щастието съществува

След като преодоляхме описаните по-горе затруднения, разполагаме с набор от стабилно работещи тестове. В тестовете всеки сервис работи като цяло, взаимодействува с базата данни и с Amazon LocalStack.

Тези тестове защитават екипа от 30+ разработчици от грешки в приложението със сложна взаимовръзка между 10+ микросервиза при чести деплойове.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster