Аўтаматычнае тэсціраванне мікрасэрвісаў у Docker для бесперапыннай інтэграцыі

У праектах, злучаных з распрацоўкай мікрасэрвіснай архітэктуры, CI/CD пераходзіць з разраду прыемнай магчымасці ў катэгорыю вострай неабходнасці. Аўтаматычнае тэсціраванне з'яўляецца неад'емнай часткай бесперапыннай інтэграцыі, пісьменны падыход да якой здольны падарыць камандзе мноства прыемных вечароў з сям'ёй і сябрамі. У адваротным жа выпадку, праект рызыкуе быць ніколі не завершаным.

Можна пакрыць увесь код мікрасэрвісу юніт-тэстамі з мок-аб'ектамі, але гэта толькі часткова вырашае задачу і пакідае мноства пытанняў і складанасцяў, асабліва пры тэставанні працы з дадзенымі. Як заўсёды, найболей вострыя – тэставанне кансістэнтнасці дадзеных у рэляцыйнай БД, тэставанне працы з хмарнымі сэрвісамі і няправільныя здагадкі пры напісанні МАК-аб'ектаў.

Усё гэта і крыху больш вырашаецца тэсціраваннем цэлага мікрасэрвісу ў 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

Большасць сэрвісаў недаступна напрамую з інтэрнэту. Доступ ажыццяўляецца праз API Gateway, які правярае правы доступу. Гэта таксама наш сервіс, і для яго таксама ёсць тэсты.

Апавяшчэнні ў рэальным часе

Прыкладанне выкарыстоўвае SignalR, Каб паказваць карыстальніку апавяшчэння ў рэальным часе. Гэта рэалізавана ў сэрвісе апавяшчэнняў. Ён даступны напрамую з інтэрнэту і сам працуе з OAuth, таму што ўбудаваць падтрымку Web-сокетаў у Gateway аказалася немэтазгодна, у параўнанні з інтэграцыяй OAuth і сэрвісу апавяшчэнняў.

Вядомы падыход да тэсціравання

Юніт-тэсты падмяняюць МАК-аб'ектамі такія рэчы, як база дадзеных. Калі мікрасэрвіс, напрыклад, спрабуе стварыць запіс у табліцы з вонкавым ключом, а запісы, на якую гэты ключ спасылаецца, не існуе, то запыт не можа быць выкананы. Юніт-тэсты не могуць гэта выявіць.

В артыкуле ад Microsoft прапануецца выкарыстоўваць in-memory базу і ўкараняць МАК-аб'екты.

In-memory база - гэта адна з СКБД, якія падтрымлівае Entity Framework. Яна створана спецыяльна для тэстаў. Дадзеныя ў такой базе захоўваюцца толькі да завяршэння працэсу, які выкарыстоўвае яе. У ёй не трэба ствараць табліцы, і цэласнасць звестак не правяраецца.

МАК-аб'екты мадэлююць які замяшчаецца клас толькі настолькі, наколькі распрацоўнік тэсту разумее яго працу.

Як дамагчыся аўтаматычнага запуску Postgres і выкананні міграцыі пры запуску тэсту, у артыкуле ад Microsoft не паказана. Маё рашэнне робіць гэта і, акрамя таго, у сам мікравервіс не дадаецца ніякі код спецыяльна для тэстаў.

Пераходзім да рашэння

У працэсе распрацоўкі стала зразумела, што юніт-тэстаў недастаткова, каб своечасова знаходзіць усе праблемы, таму было вырашана падысці да гэтага пытання з другога боку.

Настройка тэставага асяроддзя

Першая задача - разгарнуць тэставае асяроддзе. Крокі, якія неабходны для запуску мікрасэрвісу:

  • Наладзіць тэстоўваны сэрвіс на лакальнае асяроддзе, у зменных асяроддзі паказваюцца рэквізіты для падлучэння да базы і AWS;
  • Запусціць Postgres і выканаць міграцыю, запусціўшы Liquibase.
    У рэляцыйных СКБД перш, чым запісваць дадзеныя ў базу, трэба стварыць схему дадзеных, прасцей кажучы, табліцы. Пры абнаўленні прыкладання табліцы трэба прывесці да выгляду, які выкарыстоўваецца новай версіяй, прычым, пажадана, без страты дадзеных. Гэта называецца міграцыя. Стварэнне табліц у першапачаткова пустой базе - прыватны выпадак міграцыі. Міграцыю можна ўбудаваць у само дадатак. І ў .NET, і ў NodeJS ёсць фрэймворкі для міграцыі. У нашым выпадку ў мэтах бяспекі мікрасэрвісы пазбаўлены права мяняць схему дадзеных, і міграцыя выконваецца з дапамогай Liquibase.
  • Запусціць Amazon LocalStack. Гэта рэалізацыя сэрвісаў AWS для запуску ў сябе. Для LocalStack ёсць гатовая выява ў Docker Hub.
  • Запусціць скрыпт для стварэння ў LocalStack неабходных сутнасцяў. Shell-скрыпты выкарыстоўваюць AWS CLI.

Для тэсціравання на праекце выкарыстоўваецца Паштальён. Ён быў і раней, але яго запускалі ўручную і тэставалі дадатак, ужо разгорнуты на стэндзе. Гэтая прылада дазваляе рабіць адвольныя HTTP(S)-запыты і правяраць адпаведнасць адказаў чаканням. Запыты аб'ядноўваюцца ў калекцыю, і можна запусціць усю калекцыю цалкам.

Аўтаматычнае тэсціраванне мікрасэрвісаў у Docker для бесперапыннай інтэграцыі

Як уладкованы аўтаматычны тэст

Падчас тэсту ў Docker працуе ўсё: і тэстоўваны сэрвіс, і Postgres, і прылада для міграцыі, і Postman, а, дакладней, яго кансольная версія - Newman.

Docker вырашае цэлы шэраг праблем:

  • Незалежнасць ад канфігурацыі хаста;
  • Ўстаноўка залежнасцяў: докер спампоўвае вобразы з Docker Hub;
  • Зварот сістэмы ў зыходны стан: проста выдаляны кантэйнеры.

Docker-compose аб'ядноўвае кантэйнеры ў віртуальную сетку, ізаляваную ад інтэрнэту, у якой кантэйнеры знаходзяць адзін аднаго па даменных імёнах.

Тэстам кіруе shell-скрыпт. Для запуску цеста пад Windows выкарыстоўваны git-bash. Такім чынам, дастаткова аднаго скрыпту і для Windows і для Linux. Git і Docker устаноўлены ва ўсіх распрацоўшчыкаў на праекце. Пры ўсталёўцы Git пад Windows усталёўваецца git-bash, так што ён таксама ва ўсіх ёсць.

Скрыпт выконвае наступныя крокі:

  • Пабудова докер-вобразаў
    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, і на яго аснове ствараецца выява, утрымоўвальны скрыпт.

Выкарыстоўваючы тома, можна не будаваць Docker-выява толькі для дадання файлаў у кантэйнер. Аднак, volumes не падыходзяць для нашага асяроддзя, таму што задачы Gitlab CI самі працуюць у кантэйнерах. З такога кантэйнера можна кіраваць докерам, але 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, і таму яго выснова трапляе ў кансоль. Застаецца зрабіць так, каб туды пападала і выснова сэрвісу.

Першапачатковае рашэнне складалася ў тым, каб рабіць докер-кампанаваць да без сцяга -d, Але, выкарыстоўваючы магчымасці шелла, адпраўляць гэты працэс у фон:

docker-compose up <service> &

Гэта працавала датуль, пакуль не запатрабавалася адпраўляць логі з докера ў іншы сэрвіс. докер-кампанаваць да перастаў выводзіць логі ў кансоль. Аднак працавала каманда докер прыкласці.

рашэнне:

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, у якога ёсць гатовы вобраз у Дак-канцэнтратар. Чаканыя запыты і адказы на іх настройваюцца HTTP-запытам. API дакументавана, таму які робіцца запыты з Postman.

Тэставанне аўтэнтыфікацыі і аўтарызацыі OAuth

Мы выкарыстоўваем OAuth і Вэб-токены JSON (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-скрыпт у докер-файле выглядае так:

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

Каманда докер правяраць паказвае для кантэйнера звычайны статус, health-статус і код завяршэння.

Пасля завяршэння Newman, скрыпт правярае, што ўсе кантэйнеры з кліентам завяршыліся, прычым, з кодам 0.

Шчасце ёсць

Пасля таго, як мы пераадолелі апісаныя вышэй складанасці, у нас з'явіўся набор стабільна працуючых тэстаў. У тэстах кожны сэрвіс працуе як адзінае цэлае, узаемадзейнічае з базай даных і з Amazon LocalStack.

Гэтыя тэсты абараняюць каманду з 30+ распрацоўшчыкаў ад памылак у дадатку са складаным узаемадзеяннем 10+ мікрасэрвісаў пры частых дэплоях.

Крыніца: habr.com

Дадаць каментар