Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Katika miradi inayohusiana na ukuzaji wa usanifu wa huduma ndogo, CI/CD hutoka kwenye kitengo cha fursa ya kupendeza hadi kitengo cha hitaji la dharura. Majaribio ya kiotomatiki ni sehemu muhimu ya ujumuishaji unaoendelea, mbinu mwafaka ambayo inaweza kuipa timu jioni nyingi za kupendeza pamoja na familia na marafiki. Vinginevyo, hatari ya mradi kutokamilika.

Inawezekana kufunika msimbo mzima wa huduma ndogo na vipimo vya kitengo na vitu vya kejeli, lakini hii hutatua tatizo kwa sehemu tu na huacha maswali na matatizo mengi, hasa wakati wa kupima kazi na data. Kama kawaida, zinazoshinikiza zaidi ni kujaribu uthabiti wa data katika hifadhidata ya uhusiano, kufanya kazi ya kujaribu na huduma za wingu, na kutoa mawazo yasiyo sahihi wakati wa kuandika vitu vya kejeli.

Haya yote na kidogo zaidi yanaweza kutatuliwa kwa kupima huduma ndogo ndogo kwenye chombo cha Docker. Faida isiyo na shaka ya kuhakikisha uhalali wa majaribio ni kwamba picha zile zile za Docker zinazoingia kwenye uzalishaji hujaribiwa.

Uendeshaji wa njia hii hutoa shida kadhaa, suluhisho ambalo litaelezewa hapa chini:

  • migogoro ya kazi sambamba katika jeshi sawa la docker;
  • migogoro ya vitambulisho katika hifadhidata wakati wa marudio ya majaribio;
  • kusubiri microservices kuwa tayari;
  • kuunganisha na kutoa magogo kwa mifumo ya nje;
  • kupima maombi ya HTTP yanayotoka;
  • upimaji wa tundu la mtandao (kwa kutumia SignalR);
  • kupima uthibitishaji na uidhinishaji wa OAuth.

Makala hii inategemea hotuba yangu katika SECR 2019. Kwa hivyo kwa wale ambao ni wavivu sana kusoma, hapa kuna rekodi ya hotuba hiyo.

Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Katika makala hii nitakuambia jinsi ya kutumia script kuendesha huduma chini ya mtihani, database na Amazon AWS huduma katika Docker, kisha vipimo kwenye Postman na, baada ya kukamilika, kuacha na kufuta vyombo vilivyoundwa. Majaribio yanatekelezwa kila wakati msimbo unapobadilika. Kwa njia hii, tunahakikisha kwamba kila toleo linafanya kazi ipasavyo na hifadhidata na huduma za AWS.

Hati hiyo hiyo inaendeshwa na watengenezaji wenyewe kwenye kompyuta zao za mezani za Windows na seva ya Gitlab CI chini ya Linux.

Ili kuhalalisha, kuanzisha majaribio mapya hakufai kuhitaji usakinishaji wa zana za ziada ama kwenye kompyuta ya msanidi programu au kwenye seva ambapo majaribio yanaendeshwa kwa ahadi. Docker hutatua tatizo hili.

Jaribio lazima lifanyike kwenye seva ya ndani kwa sababu zifuatazo:

  • Mtandao hauaminiki kabisa. Kati ya maombi elfu moja, moja inaweza kushindwa;
    Katika kesi hii, mtihani wa moja kwa moja hauwezi kufanya kazi, kazi itaacha, na utakuwa na kuangalia kwa sababu katika magogo;
  • Maombi ya mara kwa mara hayaruhusiwi na baadhi ya huduma za wahusika wengine.

Kwa kuongeza, haifai kutumia msimamo kwa sababu:

  • Msimamo unaweza kuvunjika sio tu kwa msimbo mbaya unaoendesha juu yake, lakini pia kwa data ambayo msimbo sahihi hauwezi kusindika;
  • Haijalishi jinsi tunavyojaribu kurudisha nyuma mabadiliko yote yaliyofanywa na jaribio wakati wa jaribio lenyewe, kitu kinaweza kwenda vibaya (vinginevyo, kwa nini mtihani?).

Kuhusu mradi na shirika la mchakato

Kampuni yetu ilitengeneza programu ya wavuti ya huduma ndogo inayoendeshwa huko Docker kwenye wingu la Amazon AWS. Vipimo vya kitengo vilikuwa tayari kutumika kwenye mradi, lakini mara nyingi makosa yalitokea ambayo vipimo vya kitengo havikugundua. Ilihitajika kujaribu huduma ndogo ndogo pamoja na hifadhidata na huduma za Amazon.

Mradi hutumia mchakato wa kawaida wa ujumuishaji, ambao ni pamoja na kujaribu huduma ndogo kwa kila ahadi. Baada ya kukabidhi kazi, msanidi hufanya mabadiliko kwenye huduma ndogo, huijaribu mwenyewe na huendesha majaribio yote ya kiotomatiki yanayopatikana. Ikiwa ni lazima, msanidi hubadilisha vipimo. Ikiwa hakuna shida zinazopatikana, ahadi inafanywa kwa tawi la suala hili. Baada ya kila ahadi, majaribio yanaendeshwa kiotomatiki kwenye seva. Kuunganisha kwenye tawi la kawaida na kuzindua majaribio ya kiotomatiki juu yake hutokea baada ya ukaguzi wa mafanikio. Majaribio kwenye tawi la pamoja yatapita, huduma inasasishwa kiotomatiki katika mazingira ya majaribio kwenye Huduma ya Vyombo vya Ubora wa Amazon (benchi). Msimamo ni muhimu kwa watengenezaji na wapimaji wote, na haifai kuivunja. Wanaojaribu katika mazingira haya huangalia urekebishaji au kipengele kipya kwa kufanya majaribio ya mikono.

Usanifu wa mradi

Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Maombi yana huduma zaidi ya kumi. Baadhi yao yameandikwa katika .NET Core na baadhi katika NodeJs. Kila huduma inaendeshwa kwenye kontena la Docker kwenye Huduma ya Kontena ya Amazon Elastic. Kila moja ina hifadhidata yake ya Postgres, na zingine pia zina Redis. Hakuna hifadhidata za kawaida. Ikiwa huduma kadhaa zinahitaji data sawa, basi data hii, inapobadilika, hupitishwa kwa kila moja ya huduma hizi kupitia SNS (Huduma ya Arifa Rahisi) na SQS (Huduma ya Foleni ya Amazon), na huduma huihifadhi katika hifadhidata zao tofauti.

SQS na SNS

SQS hukuruhusu kuweka ujumbe kwenye foleni na kusoma ujumbe kutoka kwenye foleni kwa kutumia itifaki ya HTTPS.

Ikiwa huduma kadhaa zinasoma foleni moja, basi kila ujumbe hufika kwa mmoja wao tu. Hii ni muhimu wakati wa kuendesha matukio kadhaa ya huduma sawa ili kusambaza mzigo kati yao.

Ikiwa ungependa kila ujumbe uwasilishwe kwa huduma nyingi, kila mpokeaji lazima awe na foleni yake, na SNS inahitajika ili kunakili ujumbe katika foleni nyingi.

Katika SNS unaunda mada na kujiandikisha, kwa mfano, foleni ya SQS. Unaweza kutuma ujumbe kwa mada. Katika kesi hii, ujumbe unatumwa kwa kila foleni iliyosajiliwa kwa mada hii. SNS haina mbinu ya kusoma ujumbe. Ikiwa wakati wa utatuzi au majaribio unahitaji kujua ni nini kinatumwa kwa SNS, unaweza kuunda foleni ya SQS, ujiandikishe kwa mada inayotaka na usome foleni.

Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Lango la API

Huduma nyingi hazipatikani moja kwa moja kutoka kwa Mtandao. Ufikiaji ni kupitia API Gateway, ambayo hukagua haki za ufikiaji. Hii pia ni huduma yetu, na kuna majaribio yake pia.

Arifa za wakati halisi

Maombi hutumia IsharaRili kuonyesha arifa za wakati halisi kwa mtumiaji. Hii inatekelezwa katika huduma ya arifa. Inaweza kufikiwa moja kwa moja kutoka kwa Mtandao na yenyewe inafanya kazi na OAuth, kwa sababu ilionekana kuwa haiwezekani kuunda usaidizi wa soketi za Wavuti kwenye Gateway, ikilinganishwa na kuunganisha OAuth na huduma ya arifa.

Mbinu inayojulikana ya Upimaji

Majaribio ya kitengo hubadilisha vitu kama hifadhidata na vitu vya kejeli. Ikiwa microservice, kwa mfano, inajaribu kuunda rekodi katika meza na ufunguo wa kigeni, na rekodi iliyorejelewa na ufunguo huo haipo, basi ombi haliwezi kutekelezwa. Majaribio ya kitengo hayawezi kugundua hili.

Π’ makala kutoka Microsoft Inapendekezwa kutumia hifadhidata ya kumbukumbu na kutekeleza vitu vya kejeli.

Hifadhidata ya kumbukumbu ni mojawapo ya DBMS zinazotumika na Mfumo wa Huluki. Iliundwa mahsusi kwa majaribio. Data katika hifadhidata kama hiyo huhifadhiwa tu hadi mchakato wa kuitumia kukomesha. Haihitaji kuunda majedwali na haiangalii uadilifu wa data.

Vitu vya dhihaka ni mfano wa darasa ambalo wanabadilisha tu kwa kiwango ambacho msanidi wa jaribio anaelewa jinsi inavyofanya kazi.

Jinsi ya kufanya Postgres ianze na kutekeleza uhamaji kiotomatiki unapofanya jaribio haijabainishwa katika makala ya Microsoft. Suluhisho langu hufanya hivi na, kwa kuongezea, haiongezi nambari yoyote haswa kwa vipimo kwenye huduma ndogo yenyewe.

Wacha tuendelee kwenye suluhisho

Wakati wa mchakato wa maendeleo, ikawa wazi kuwa vipimo vya kitengo havikutosha kupata matatizo yote kwa wakati, kwa hiyo iliamua kukabiliana na suala hili kutoka kwa pembe tofauti.

Kuweka mazingira ya mtihani

Kazi ya kwanza ni kupeleka mazingira ya mtihani. Hatua zinazohitajika ili kuendesha huduma ndogo:

  • Sanidi huduma inayojaribiwa kwa mazingira ya ndani, taja maelezo ya kuunganisha kwenye hifadhidata na AWS katika anuwai za mazingira;
  • Anzisha Postgres na ufanye uhamiaji kwa kuendesha Liquibase.
    Katika DBMS za uhusiano, kabla ya kuandika data kwenye hifadhidata, unahitaji kuunda schema ya data, kwa maneno mengine, meza. Wakati wa kusasisha programu, meza lazima ziletwe kwa fomu inayotumiwa na toleo jipya, na, ikiwezekana, bila kupoteza data. Hii inaitwa uhamiaji. Kuunda meza katika hifadhidata tupu ni kesi maalum ya uhamiaji. Uhamiaji unaweza kujengwa kwenye programu yenyewe. NET na NodeJS zote zina mifumo ya uhamiaji. Kwa upande wetu, kwa sababu za usalama, huduma ndogo hunyimwa haki ya kubadilisha schema ya data, na uhamiaji unafanywa kwa kutumia Liquibase.
  • Zindua Amazon LocalStack. Huu ni utekelezaji wa huduma za AWS za kuendeshwa nyumbani. Kuna picha iliyotengenezwa tayari kwa LocalStack kwenye Docker Hub.
  • Endesha hati ili kuunda huluki zinazohitajika katika LocalStack. Maandishi ya Shell hutumia AWS CLI.

Inatumika kwa majaribio kwenye mradi Postman. Ilikuwepo hapo awali, lakini ilizinduliwa kwa mikono na kujaribu programu ambayo tayari imetumwa kwenye stendi. Zana hii hukuruhusu kutuma maombi ya HTTP(S) kiholela na kuangalia kama majibu yanalingana na matarajio. Hoja huunganishwa kuwa mkusanyiko, na mkusanyiko mzima unaweza kuendeshwa.

Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Mtihani otomatiki hufanyaje kazi?

Wakati wa jaribio, kila kitu hufanya kazi katika Docker: huduma chini ya majaribio, Postgres, chombo cha uhamiaji, na Postman, au tuseme toleo lake la console - Newman.

Docker hutatua shida kadhaa:

  • Uhuru kutoka kwa usanidi wa mwenyeji;
  • Kufunga tegemezi: Docker inapakua picha kutoka kwa Docker Hub;
  • Kurejesha mfumo kwa hali yake ya asili: kuondoa tu vyombo.

Docker-tunga huunganisha vyombo kuwa mtandao pepe, uliotengwa na mtandao, ambamo vyombo hupatana kwa majina ya kikoa.

Jaribio linadhibitiwa na hati ya ganda. Ili kuendesha jaribio kwenye Windows tunatumia git-bash. Kwa hivyo, hati moja inatosha kwa Windows na Linux. Git na Docker imewekwa na watengenezaji wote kwenye mradi huo. Wakati wa kusakinisha Git kwenye Windows, git-bash imewekwa, kwa hivyo kila mtu anayo hiyo pia.

Nakala hufanya hatua zifuatazo:

  • Kujenga picha za docker
    docker-compose build
  • Inazindua hifadhidata na LocalStack
    docker-compose up -d <ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€>
  • Uhamishaji wa hifadhidata na utayarishaji wa LocalStack
    docker-compose run <ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€>
  • Kuzindua huduma chini ya majaribio
    docker-compose up -d <сСрвис>
  • Kuendesha mtihani (Newman)
  • Kusimamisha vyombo vyote
    docker-compose down
  • Inachapisha matokeo katika Slack
    Tuna gumzo ambapo ujumbe wenye alama ya kuteua ya kijani au msalaba mwekundu na kiungo cha logi huenda.

Picha zifuatazo za Docker zinahusika katika hatua hizi:

  • Huduma inayojaribiwa ni picha sawa na ya uzalishaji. Usanidi wa jaribio ni kupitia anuwai za mazingira.
  • Kwa Postgres, Redis na LocalStack, picha zilizotengenezwa tayari kutoka kwa Docker Hub hutumiwa. Pia kuna picha zilizotengenezwa tayari kwa Liquibase na Newman. Tunaunda yetu kwenye mifupa yao, na kuongeza faili zetu hapo.
  • Ili kuandaa LocalStack, unatumia picha ya AWS CLI iliyotengenezwa tayari na kuunda picha iliyo na hati kulingana nayo.

Kutumia kiasi, sio lazima ujenge picha ya Docker ili kuongeza faili kwenye kontena. Walakini, kiasi hakifai kwa mazingira yetu kwa sababu majukumu ya Gitlab CI yenyewe huendeshwa kwenye vyombo. Unaweza kudhibiti Docker kutoka kwa chombo kama hicho, lakini kiasi cha folda huwekwa tu kutoka kwa mfumo wa mwenyeji, na sio kutoka kwa chombo kingine.

Matatizo unayoweza kukutana nayo

Kusubiri kwa utayari

Wakati kontena iliyo na huduma inaendeshwa, hii haimaanishi kuwa iko tayari kukubali miunganisho. Lazima usubiri muunganisho uendelee.

Tatizo hili wakati mwingine hutatuliwa kwa kutumia hati subiri.sh, ambayo inasubiri fursa ya kuanzisha uhusiano wa TCP. Walakini, LocalStack inaweza kutupa hitilafu ya 502 Bad Gateway. Kwa kuongeza, inajumuisha huduma nyingi, na ikiwa mmoja wao yuko tayari, hii haisemi chochote kuhusu wengine.

uamuzi: Hati za utoaji za LocalStack ambazo husubiri majibu 200 kutoka kwa SQS na SNS zote mbili.

Migogoro ya Kazi Sambamba

Majaribio mengi yanaweza kufanya kazi kwa wakati mmoja kwenye seva pangishi ya Docker, kwa hivyo majina ya vyombo na mtandao lazima yawe ya kipekee. Aidha, vipimo kutoka kwa matawi tofauti ya huduma sawa vinaweza pia kukimbia wakati huo huo, kwa hiyo haitoshi kuandika majina yao katika kila faili ya kutunga.

uamuzi: Hati huweka kigezo cha COMPOSE_PROJECT_NAME kuwa thamani ya kipekee.

Vipengele vya Windows

Kuna mambo kadhaa ambayo ninataka kuashiria wakati wa kutumia Docker kwenye Windows, kwani uzoefu huu ni muhimu kuelewa kwa nini makosa hufanyika.

  1. Hati za shell katika chombo lazima ziwe na miisho ya mstari wa Linux.
    Alama ya shell CR ni hitilafu ya kisintaksia. Ni ngumu kusema kutoka kwa ujumbe wa makosa kwamba hii ndio kesi. Wakati wa kuhariri hati kama hizo kwenye Windows, unahitaji mhariri sahihi wa maandishi. Kwa kuongeza, mfumo wa udhibiti wa toleo lazima usanidiwe vizuri.

Hivi ndivyo git imeundwa:

git config core.autocrlf input

  1. Git-bash huiga folda za kawaida za Linux na, wakati wa kupiga faili ya exe (pamoja na docker.exe), inachukua nafasi ya njia za Linux kabisa na njia za Windows. Walakini, hii haina maana kwa njia zisizo kwenye mashine ya ndani (au njia kwenye chombo). Tabia hii haiwezi kulemazwa.

uamuzi: ongeza mkwaju wa ziada mwanzoni mwa njia: //bin badala ya /bin. Linux inaelewa njia kama hizo; kwa hiyo, mikwaju kadhaa ni sawa na moja. Lakini git-bash haitambui njia kama hizo na haijaribu kuzibadilisha.

Toleo la kumbukumbu

Wakati wa kufanya majaribio, ningependa kuona kumbukumbu kutoka kwa Newman na huduma ikijaribiwa. Kwa kuwa matukio ya kumbukumbu hizi zimeunganishwa, kuchanganya kwenye console moja ni rahisi zaidi kuliko faili mbili tofauti. Newman azindua kupitia docker-compose run, na hivyo matokeo yake huishia kwenye koni. Kinachobaki ni kuhakikisha kuwa pato la huduma pia huenda huko.

Suluhisho la asili lilikuwa kufanya docker-compose up hakuna bendera -d, lakini kwa kutumia uwezo wa ganda, tuma mchakato huu nyuma:

docker-compose up <service> &

Hii ilifanya kazi hadi ilipohitajika kutuma kumbukumbu kutoka kwa Docker hadi kwa huduma ya mtu wa tatu. docker-compose up iliacha kutoa kumbukumbu kwenye koni. Walakini, timu ilifanya kazi docker amefungwa.

uamuzi:

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

Mzozo wa vitambulisho wakati wa marudio ya majaribio

Majaribio yanaendeshwa kwa marudio kadhaa. Hifadhidata haijafutwa. Rekodi katika hifadhidata zina vitambulisho vya kipekee. Ikiwa tutaandika vitambulisho maalum katika maombi, tutapata mgongano katika marudio ya pili.

Ili kuiepuka, ni lazima vitambulisho viwe vya kipekee, au vitu vyote vilivyoundwa na jaribio lazima vifutwe. Baadhi ya vitu haviwezi kufutwa kutokana na mahitaji.

uamuzi: toa GUID kwa kutumia hati za Postman.

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

Kisha tumia ishara katika swali {{myUUID}}, ambayo itabadilishwa na thamani ya kutofautisha.

Ushirikiano kupitia LocalStack

Ikiwa huduma inayojaribiwa inasoma au kuandika kwenye foleni ya SQS, basi ili kuthibitisha hili, mtihani yenyewe lazima pia ufanye kazi na foleni hii.

uamuzi: maombi kutoka kwa Postman hadi LocalStack.

API ya huduma za AWS imerekodiwa, ikiruhusu maswali kufanywa bila SDK.

Ikiwa huduma inaandika kwenye foleni, basi tunaisoma na kuangalia yaliyomo kwenye ujumbe.

Ikiwa huduma itatuma ujumbe kwa SNS, katika hatua ya maandalizi LocalStack pia huunda foleni na kujiandikisha kwa mada hii ya SNS. Kisha yote inakuja kwa kile kilichoelezwa hapo juu.

Ikiwa huduma inahitaji kusoma ujumbe kutoka kwenye foleni, basi katika hatua ya awali ya mtihani tunaandika ujumbe huu kwenye foleni.

Kujaribu maombi ya HTTP yanayotoka kwa huduma ndogo chini ya majaribio

Baadhi ya huduma hufanya kazi kupitia HTTP na kitu kingine isipokuwa AWS, na baadhi ya vipengele vya AWS havitekelezwi katika LocalStack.

uamuzi: katika kesi hizi inaweza kusaidia MockServer, ambayo ina picha iliyotengenezwa tayari ndani Kitovu cha Docker. Maombi na majibu yanayotarajiwa kwao yanasanidiwa na ombi la HTTP. API imerekodiwa, kwa hivyo tunatuma maombi kutoka kwa Postman.

Kujaribu Uthibitishaji na Uidhinishaji wa OAuth

Tunatumia OAuth na Tokeni za Wavuti za JSON (JWT). Jaribio linahitaji mtoa huduma wa OAuth ambaye tunaweza kufanya kazi ndani ya nchi.

Mwingiliano wote kati ya huduma na mtoaji wa OAuth unakuja kwa maombi mawili: kwanza, usanidi unaombwa. /.inajulikana/kufungua-usanidi, na kisha ufunguo wa umma (JWKS) unaombwa kwenye anwani kutoka kwa usanidi. Yote haya ni yaliyomo tuli.

uamuzi: Mtoa huduma wetu wa OAuth wa majaribio ni seva ya maudhui tuli na faili mbili juu yake. Ishara hutolewa mara moja na kujitolea kwa Git.

Vipengele vya upimaji wa SignalR

Postman haifanyi kazi na soketi za wavuti. Zana maalum iliundwa ili kujaribu SignalR.

Mteja wa SignalR anaweza kuwa zaidi ya kivinjari. Kuna maktaba ya mteja kwa hiyo chini ya .NET Core. Mteja, iliyoandikwa katika NET Core, huanzisha muunganisho, imethibitishwa, na inasubiri mlolongo maalum wa ujumbe. Ikiwa ujumbe usiotarajiwa umepokelewa au muunganisho umepotea, mteja huondoka na msimbo wa 1. Ikiwa ujumbe unaotarajiwa wa mwisho utapokelewa, mteja huondoka na msimbo wa 0.

Newman hufanya kazi wakati huo huo na mteja. Wateja kadhaa huzinduliwa ili kuangalia kama ujumbe unawasilishwa kwa kila mtu anayehitaji.

Upimaji wa kiotomatiki wa huduma ndogo katika Docker kwa ujumuishaji unaoendelea

Ili kuendesha wateja wengi tumia chaguo -- wadogo kwenye mstari wa amri ya kutunga docker.

Kabla ya kuendeshwa, hati ya Postman inasubiri wateja wote waanzishe miunganisho.
Tayari tumekumbana na tatizo la kusubiri muunganisho. Lakini kulikuwa na seva, na hapa kuna mteja. Njia tofauti inahitajika.

uamuzi: mteja kwenye chombo hutumia utaratibu AfyaCheckkufahamisha hati kwa mwenyeji kuhusu hali yake. Mteja huunda faili kwa njia maalum, sema / afya, mara tu uunganisho unapoanzishwa. Hati ya HealthCheck kwenye faili ya docker inaonekana kama hii:

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

Timu ukaguzi wa docker Inaonyesha hali ya kawaida, hali ya afya na msimbo wa kuondoka wa kontena.

Baada ya Newman kukamilisha, hati hukagua kuwa kontena zote zilizo na mteja zimekatishwa, na nambari 0.

Happinnes ipo

Baada ya kushinda shida zilizoelezewa hapo juu, tulikuwa na seti ya majaribio thabiti ya kukimbia. Katika majaribio, kila huduma hufanya kazi kama kitengo kimoja, ikishirikiana na hifadhidata na Amazon LocalStack.

Majaribio haya hulinda timu ya watengenezaji 30+ kutokana na makosa katika programu yenye mwingiliano changamano wa huduma ndogo 10+ zinazotumwa mara kwa mara.

Chanzo: mapenzi.com

Kuongeza maoni