Kanarialind on vÀike lind, kes lauldab pidevalt. Need linnud on metaani ja vingugaasi suhtes tundlikud. Isegi vÀikese kontsentratsiooni korral liigsetest gaasidest Ôhus kaotavad nad teadvuse vÔi surevad. Kuldotsijad ja kaevurid viisid linde kaevandusse: kui kanarialind laulab, vÔib töötada; kui nad on vait, siis on kaevanduses gaas ja aeg on lahkuda. Kaevurid ohverdavad vÀikese linnu, et pÀÀseda kaevandusest elusalt.

Sarnane praktika leidis aset ka IT-s. NĂ€iteks tavapĂ€rases ĂŒlesandes uue teenuse vĂ”i rakenduse versiooni juurutamiseks tootmiskeskkonnas, koos eelneva testimisega. TestimisvĂ”imekus vĂ”ib olla liiga kallis, automatiseeritud testid ei kata kĂ”ike, mida soovitakse, ja mitte testimine ning kvaliteedi ohverdamine on riskantne. Sellistes olukordades aitab kanarialdepaalituse lĂ€henemine, kus vĂ€ike osa tegelikust tootmisliiklusest suunatakse uuele versioonile. See lĂ€henemine aitab turvaliselt kontrollida uut versiooni tootmises, ohverdades vĂ€ikese suure eesmĂ€rgi nimel. Rohkem selle lĂ€henemise toimimisest, eelistest ja rakendamisest rÀÀgib Andrei Markelov (), Infobipi rakendamise nĂ€itel.
Andrei Markelov â juhtiv insener-programmeerija Infobipis, kellel on 11-aastane kogemus Java rakenduste arendamisel finants- ja telekommunikatsioonivaldkonnas. Arendab avatud lĂ€htekoodiga tooteid, osaleb aktiivselt Atlassian Community's ja kirjutab Atlassiani toodete pluginaid. Prometheuse, Dockeri ja Redis'e evangelist.

Infobipi kohta
See on globaalne telekommunikatsiooniplatvorm, mis vĂ”imaldab pangandusel, jaemĂŒĂŒgil, e-poedel ja transpordifirmadel saata sĂ”numeid oma klientidele SMS-ide, push-sĂ”numite, e-kirjade ja hÀÀlsĂ”numite kaudu. Selles valdkonnas on oluline stabiilsus ja usaldusvÀÀrsus, et kliendid saaksid sĂ”numid Ă”igel ajal kĂ€tte.
Infobipi IT-infrastruktuur numbrites:
- 15 andmekeskust ĂŒle kogu maailma;
- 500 unikaalset teenust kasutuses;
- 2500 teenuse eksemplari, mis on palju rohkem kui meeskondi;
- 4,5 TByte kuus;
- 4,5 miljardit telefoninumbrit;
Ări kasvab ja koos sellega ka vĂ€ljalaskete arv. Meie teeme 60 vĂ€ljalaset pĂ€evas, sest kliendid soovivad rohkem vĂ”imalusi ja vĂ”imsusi. Kuid see on keeruline â teenuseid on palju, kuid meeskondi vĂ€he. Peame kiiresti kirjutama koodi, mis peab töötama tootmises veatult.
VĂ€ljalasked
Meie tĂŒĂŒpiline vĂ€ljaanne toimub jĂ€rgmiselt. NĂ€iteks on meil teenused A, B, C, D ja E, millest igaĂŒht arendab eraldi meeskond.

Milleski hetkes otsustab teenuse A meeskond juurutada uue versiooni, kuid teenuste B, C, D ja E meeskonnad ei tea sellest. Teenuse A meeskonna tegevuseks on kaks varianti.
Teostatakse inkrementeeritud vĂ€ljaanne: esiteks asendatakse ĂŒks versioon ja seejĂ€rel teine.

Kuid on ka teine variant: meeskond leiab tÀiendavad ressursid ja masinad, juurutab uue versiooni ja seejÀrel suunab ruuteri, ning versioon hakkab töötama tootmises.

Igas variandis esinevad pĂ€rast juurutamist peaaegu alati probleemid, isegi kui versioon on testitud. Testida saab kĂ€sitsi, automatiseeritult vĂ”i ei testita ĂŒldse â probleemid tekivad igal juhul. Lihtsaim ja Ă”igem viis neist probleemidest ĂŒle saada on tagasi minna töötavale versioonile. Alles pĂ€rast seda saab analĂŒĂŒsida kahjusid, pĂ”hjuseid ja neid parandada.
Nii, mida me tahame?
Probleemid ei sobi meile. Kui kliendid avastavad need enne meid, kahjustab see meie mainet. SeetÔttu peame leidma probleemid kiiremini kui kliendid. Tehes ette nÀgemise töö, aitame vÀhendada kahju.
Samas tahame kiirendada juurutamist, et see toimuks kiiresti, lihtsalt, iseenesest ja ilma meeskonna pingutuseta. Insenerite, DevOps-insenerite ja arendajate loomepeavĂ€gi â uue versiooni vĂ€ljaandmine on stressirohke. Meeskond ei ole raisatav ressurss, me pĂŒĂŒame inimressursse ratsionaalselt kasutada.
Juurutamisprobleemid
Kliendiliiklus on ettearvamatu. Ei ole vĂ”imalik ennustada, millal kliendiliiklus on minimaalne. Me ei tea, kus ja millal kliendid oma kampaaniaid alustavad â vĂ”ib-olla tĂ€na öösel Indias, ja homme Hongkongis. Suure ajavahe tĂ”ttu ei garanteeri juurutamine isegi kell 2 öösel, et kliendid ei kannata.
Teenusepakkujate probleemid. Messengrid ja teenusepakkujad on meie partnerid. Vahel esinevad neil tÔrked, mis pÔhjustavad uusi versioone juurutades vigu.
Jaotatud meeskonnad. Meeskonnad, kes arendavad kliendipoolt ja tagapinda, asuvad erinevates ajavööndites. Selle tÔttu ei suuda nad sageli omavahel kokkuleppele jÔuda.
Andmekeskuseid ei saa stendil korrata. Ăhes andmekeskuses on 200 serverit â seda liivakastis isegi umbkaudselt korrata ei Ă”nnestu.
Seisakudei ole vastuvĂ”etavad! Meil on lubatud kĂ€ttesaadavuse tase (viga eelarve), kui töötame 99,99% ajast, nĂ€iteks ja ĂŒlejÀÀnud protsendid on 'veaĂ”igus'. 100% usaldusvÀÀrsuse saavutamine on vĂ”imatu, kuid on oluline pidevalt jĂ€lgida katkestusi ja seisakuid.
Klassikalised lahenduste variandid
Kirjutada koodi ilma vigadeta. Kui olin noor arendaja, lÀhenesid mulle juhid palvega viia vÀlja versioon ilma vigadeta, kuid see ei ole alati vÔimalik.
Kirjutada teste. Testid töötavad, kuid mĂ”nikord ei lĂ€he need sugugi nii, nagu soovib Ă€ri. Raha teenimine ei ole testide ĂŒlesanne.
Testida staadiumis. Oma 3,5 aastase töö jooksul Infobipis ei ole ma kunagi nÀinud, et staadiumi seisund vÀhemalt osaliselt kattuks tootmisega.

Proovisime isegi seda ideed arendada: alguses oli meil staadium, siis ettevalmistus ja hiljem ettevalmistuse ettevalmistamine. Kuid see ei aidanud â need ei kattunud isegi vĂ”imsuse poolest. Staadiumi puhul saame garantii pĂ”hiĂŒlesannete osas, kuid ei tea, kuidas see koormuse all toimib.
VĂ€ljaande teeb see, kes arendas. See, et on hea tava: isegi kui keegi muudab kommentaari pealkirja, lisab ta selle kohe tootmisesse. See aitab arendada vastutustunnet ja mitte unustada tehtud muudatusi.
Lisaks on ka muid keerukusi. Arendajale on see stressirohke â kulutada palju aega, et kĂ”ik kĂ€sitsi kontrollida.
Kokkulepitud vÀljalaskmine. See variant on tavaliselt juhtkonna ettepanek: "Kuidas oleks, kui igapÀevaselt testiksite ja lisaksite uusi versioone". See ei toimi: alati on meeskond, kes ootab teisi vÔi vastupidi.
Smoke-testid
Veel ĂŒks viis meie juurutamisprobleemide lahendamiseks. Vaatame, kuidas smoke-testid töötavad eelneva nĂ€ite pĂ”hjal, kui meeskond A soovib juurutada uut versiooni.
Esialgu juurutab meeskond ĂŒhe instantsi tootmisse. Instants tĂ”ukab sĂ”numid simuleeritud reaalsele liiklusele, et see vastaks tavapĂ€rasele igapĂ€evasele liiklusele. Kui kĂ”ik lĂ€heb hĂ€sti, suunab meeskond uue versiooni kasutaja liiklusele.

Teine variant on juurutada lisavarustusega. Meeskond testib seda tootmises, seejÀrel suunab, ja kÔik töötab.

Smoke-testide puudused:
- Testidele ei saa usaldada. Kust saada sama liiklust, mis tootmises? VÔib kasutada eilset vÔi nÀdala tagust, kuid see ei pruugi alati vastata praegusele.
- Raskusi sÀilitamisel. Peab hoidma testkonto, pidevalt neid nullima enne igat juurutamist, kui salvestusse saadetakse aktiivsed kirjed. See on keerulisem kui testimise kirjutamine oma liivakastis.
Ainus boonus siin on â saame kontrollida jĂ”udlust.
Canary-vÀljaanded
Smoke-testide puuduste tÔttu hakkasime kasutama canary-vÀljaandeid.
Tava, mis sarnaneb sellele, kuidas kaevurite kasutada kanariesi gaasitaseme indikaatorina, leidis koha ka IT-s. Laseme veidi tĂ”elist tootmisse liiklust uue versiooni peale, samal ajal pĂŒĂŒdes jÀÀda teenuste taseme lepingusse (SLA). SLA on meie "veaĂ”igus", mida saame ĂŒks kord aastas kasutada (vĂ”i mingil muul ajavahemikul). Kui kĂ”ik lĂ€heb hĂ€sti, lisame rohkem liiklust. Kui ei â tagastame eelmised versioonid.

Rakendamine ja nĂŒansid
Kuidas me rakendasime canary-vÀljaandeid? NÀiteks kliendigrupp saadab meie teenuse kaudu sÔnumeid.

Deploy toimub jĂ€rgmiselt: eemaldame ĂŒhe sĂ”lme koormustaseme alt (1), vahetame versiooni (2) ja saadame eraldi natuke liiklust (3).

KokkuvĂ”ttes on grupis kĂ”ik Ă”nnelikud, isegi kui ĂŒks kasutaja on rahulolematu. Kui kĂ”ik on hĂ€sti, vahetame kĂ”ik versioonid.

NÀitan visuaalselt, kuidas see enamasti mikroteenuste puhul vÀlja nÀeb.
On olemas teenuste avastamine ja veel kaks teenust: S1N1 ja S2. Esimene teenus (S1N1) teatab teenuste avastamisele, kui see kÀivitatakse, ja teenuste avastus salvestab selle. Teine teenus, millel on kaks sÔlme (S2N1 ja S2N2), teatab samuti teenuste avastamisele kÀivitamisel.

Teine teenus töötab esimese jaoks serverina. Esimene kĂŒsib teenuste avastamiselt oma serverite kohta teavet ja kui ta selle saab, otsib ja kontrollib neid ("health check"). Kui ta kontrollib, saadab ta neile sĂ”numeid.
Kui keegi soovib teise teenuse uut versiooni rakendada, teatab ta teenuste avastamisele, et teine sÔlm on canary-sÔlm: sellel saadetakse vÀhem liiklust, sest toimub rakendamine. Eemaldame canary-sÔlme koormustaseme alt ja esimene teenus ei suunata liiklust sinna.

Muudame versiooni ja Service Discovery teab, et teine sĂ”lm on nĂŒĂŒd canary â sellele vĂ”ib anda vĂ€hem koormust (5%). Kui kĂ”ik lĂ€heb hĂ€sti, muudame versiooni, taastame koormuse ja töötame edasi.
Selle kÔikide elluviimiseks vajame:
- koormuse tasakaalustamist;
- jÀlgimist,, kuna on oluline teada, mida iga kasutaja ootab ja kuidas meie teenused tÀpselt toimivad;
- versioonide analĂŒĂŒsi,, et mĂ”ista, kui hĂ€sti uus versioon tootmises töötama hakkab;
- automatiseerimine â kirjutame juurutamise jĂ€rjestuse (deployment pipeline).

Koormuse tasakaalustamine
See on esimene asi, millele peame mÔtlema. On kaks tasakaalustamise strateegiat.
Lihtsaim variant on see, kui ĂŒks sĂ”lm on alati canary.See sĂ”lm saab alati vĂ€hem liiklust ja alustame juurutamist temalt. Probleemide korral vĂ”rreldame tema tööd enne ja juurutamise ajal. NĂ€iteks, kui vigu on kaks korda rohkem, tĂ€hendab see, et kahju on kasvanud kaks korda.
Canary-sÔlm mÀÀratakse juurutamise kÀigus.Kui juurutamine on lÔpetatud ja me eemaldame selle canary-sÔlme staatuse, taastatakse liikluse tasakaal. VÀhemate masinate korral saavutame ausa jaotuse.
JĂ€lgimine
Canary-releaside nurgakivi. Me peame tÀpselt mÔistma, miks me seda teeme ja milliseid mÔÔdikuid soovime koguda.
MÔned nÀited mÔÔdikutest, mida me oma teenustest kogume.
- Vigade arv, mis logidesse kirjutatakse. See on selge indikaator, et kĂ”ik töötab nagu peab. Ăldiselt on see hea mÔÔdik.
- PÀringute tÀitmise aeg (latentsus). Seda mÔÔdikut jÀlgitakse kÔigis, kuna kÔik soovivad töötada kiiresti.
- Ooteaeg (lÀbivus).
- Edukate vastuste arv sekundis.
- 95% kÔigist pÀringutest tÀidetakse.
- ĂrimÔÔdikud: kui palju raha Ă€ri teatud aja jooksul teenib vĂ”i klientide lahkumine. Need mÔÔdikud vĂ”ivad meie uue versiooni jaoks olla tĂ€htsamad kui need, mille lisavad insenerid.
MÔÔdikute nĂ€ited enamikus populaarsetes jĂ€lgimissĂŒsteemides.
Counter. See on mingi kasvav suurus, nÀiteks vigade arv. Seda mÔÔdikut on lihtne interpoleerida ja graafikut uurida: eile oli 2 viga, tÀna 500, see tÀhendab, et midagi on valesti lÀinud.
Vigade hulk minutis vĂ”i sekundites on oluline nĂ€itaja, mida saab mÔÔta Counter'iga. Need andmed annavad selge ĂŒlevaate sĂŒsteemi töö kohta ajas. Vaadakem nĂ€iteks veavigade hulka sekundis kahe tootmisversiooni jaoks.

Esimeses versioonis oli vĂ€he vigu, tĂ”enĂ€oliselt ei toiminud auditi sĂŒsteem. Teises versioonis on olukord palju halvem. On selge, et esinevad probleemid, seega peaksime selle versiooni tagasi vĂ”tma.
Gauge. Metrika sarnaneb Counter'iga, kuid me registreerime vÀÀrtused, mis vÔivad nii suureneda kui ka vÀheneda. NÀiteks pÀringute tÀitmise aeg vÔi jÀrjekorra suurus.
Graafikul on nĂ€ide latentsuse (latency) ajast. Graafik nĂ€itab, et versioonid on sarnased, neid on vĂ”imalik kasutada. Kuid kui lĂ€hemalt vaadata, on muutused mĂ€rgatavad. Kui pĂ€ringute tĂ€itmise aeg suureneb kasutajate lisandumisel, on kohe selge, et probleemid on olemas â varem polnud sellist olukorda.

Summary. Ăks kĂ”ige olulisemaid nĂ€itajaid Ă€ri jaoks on protsentiilid. Metrika nĂ€itab, et 95% juhtudest meie sĂŒsteem töötab nii nagu me soovime. Me suudame leppida probleemidega, kuna mĂ”istame ĂŒldist trendi, kui hea vĂ”i halb kĂ”ik on.
Tööriistad
ELK Stack. Canary rakendamine on vĂ”imalik, kasutades Elasticsearchi â salvestame sinna vead, kui toimuvad sĂŒndmused. Lihtsa API-kutsumisega saab mis tahes hetkel saada vigade arvu ja vĂ”rrelda seda varasemate ajavahemikega: GET /applg/_cunt?q=level:errr.
Prometheus. Töötas hÀsti Infobipis. See vÔimaldab rakendada multidimensionaalseid mÔÔdikuid, kuna kasutatakse silte.
Me vĂ”ime kasutada level, instance, teenus, kombineerida neid ĂŒhes sĂŒsteemis. Aitab vaadata nĂ€iteks, milline oli nĂ€itaja vÀÀrtus nĂ€dal tagasi ĂŒhe kĂ€suga offset GET /api/v1/query?query={query} {query}, kus rate(logback_appender_total{ level="error", instance=~"$instance" }[5m] offset $offset_value):
rate(logback_appender_total{
level="error",
instance=~"$instance"
}[5m] offset $offset_value)Versioonide analĂŒĂŒs
On mitmeid versioonide analĂŒĂŒsi strateegiaid.
Vaadata mÔÔdikuid ainult canary-sĂ”lme. Ăks lihtsamaid variante: saadetame uue versiooni ja uurime ainult selle toimimist. Kuid kui insener selle aja jooksul hakab logisid uurima, pidevalt Ă€revuses lehti vĂ€rskendades, siis ei erine see lahendus teistest.
Canary-sÔlm vÔrreldakse igasuguste teiste sÔlmedega.. See on vÔrdlus teiste instantsidega, mis töötavad tÀis liikluses. NÀiteks, kui vÀikese liiklusega on olukord halvem vÔi mitte parem kui reaalsetes instantsides, siis on midagi valesti.
Canary-noda vÔrreldakse iseendaga minevikus. Canary'le eraldatud nodasid saab vÔrrelda ajalooandmetega. NÀiteks, kui nÀdal tagasi oli kÔik hÀsti, saame neid andmeid kasutada, et mÔista praegust olukorda.
Automatiseerimine
Tahame vabastada insenere kÀsitsi vÔrdlemisest, seetÔttu on oluline automatiseerimise elluviimine. Detsenteerimisprotsess (deployment pipeline) nÀeb tavaliselt vÀlja jÀrgmine:
- alustame;
- eemaldame nodi tasakaalustajast;
- seame canary-noda;
- lĂŒlitame tasakaalustaja sisse juba piiratud liiklusega;
- vÔrdleme.

Selles etapis rakendame automaatsed vÔrdlemised. Kuidas see vÔib vÀlja nÀha ja miks on parem kui pÀrast detsenteerimist kontrollimine, uurime Jenkins'i nÀite kaudu.
See on pipeline Groovy's.
while (System.currentTimeMillis() < endCanaryTs) {
def isOk = compare(srv, canary, time, base, offset, metrics)
if (isOk) {
sleep DEFAULT SLEEP
} else {
echo "Canary ebaÔnnestus, tuleb tagasi pöörduda"
return false
}
} Siin tsĂŒklis mÀÀrame, et vĂ”rreldakse uut sĂ”lme tunni jooksul. Kui canary protsess pole veel lĂ”pule viidud â kutsume vĂ€lja funktsiooni. See annab teada, kas kĂ”ik on hĂ€sti vĂ”i mitte: def isOk = compare(srv, canary, time, base, offset, metrics).
Kui kĂ”ik on hĂ€sti â sleep DEFAULT SLEEP, nĂ€iteks ĂŒheks sekundiks, ja jĂ€tkame. Kui ei, siis lahkume â juurutamine ebaĂ”nnestus.
MÔÔdiku kirjeldus. Vaatame, milline vÔib vÀlja nÀha funktsioon compare DSL nÀitel.
metric(
'errorCounts',
'rate(errorCounts{node=~"$canaryInst"}[5m] offset $offset)',
{ baseValue, canaryValue ->
if (canaryValue > baseValue * 1.3) return false
return true
}
)Oletame, et vÔrreldakse vigade arvu ja soovitakse teada vigade arvu sekundis viimase 5 minuti jooksul.
Meil on kaks vÀÀrtust: baasiline ja canary sĂ”lme. Canarya sĂ”lme vÀÀrtus on â praegune. Baasiline â baseValue â see on vÀÀrtus, mis on mistahes muu mitte canary sĂ”lme jaoks. VĂ”rreldakse vÀÀrtusi omavahel valemi alusel, mille seame vastavalt oma kogemustele ja tĂ€helepanekutele. Kui vÀÀrtus canaryValue on halb, siis juurutamine ebaĂ”nnestus ja taganeme.
Miks see kÔik vajalik on?
Inimene ei suuda tuvastada sadu ja tuhandeid mÔÔdikke., seda on eriti oluline teha kiiresti. Automaatne vÔrdlemine aitab kontrollida kÔik mÔÔdikud ja teavitab kiiresti probleemidest. Teavitamise aeg on kriitiline: kui midagi on juhtunud viimase kahe sekundi jooksul, siis kahju on vÀiksem kui juhul, kui see juhtus 15 minutit tagasi. Kuni keegi probleemist teada saab, kirjutab toele, ja toed meile, et tagastada, vÔime kaotada kliente.
Kui protsess on lĂ€bitud ja kĂ”ik on korras, siis automatiselt juurutame kĂ”ik ĂŒlejÀÀnud nodeâid. Sel ajal ei tee insenerid midagi. Ainult siis, kui nad kĂ€ivitavad canary, otsustavad, millised mÔÔdikud vĂ”tta, kui kaua vĂ”rrelda ja millist strateegiat kasutada.

Kui probleemid tekivad, tagastame automaatselt canary-node, töötame varasemate versioonide peal ja parandame leitud vead. mÔÔdikute jÀrgi on neid lihtne leida ja nÀha uue versiooni kahjust.
TÔkked
Selle rakendamine pole kindlasti lihtne. Esiteks on vajalik ĂŒldine jĂ€lgimissĂŒsteem. Inseneridel on omad mÔÔdikud, toeteenindusel ja analĂŒĂŒtikutel teised, Ă€ril kolmandad. Ăldine sĂŒsteem on ĂŒhine keel, millega rÀÀgivad Ă€ri ja arendus.
Peab praktikas kontrollima mÔÔdikute stabiilsust. Kontroll aitab mÔista, milline minimaalne metrikate kogum on vajalik kvaliteedi tagamiseks.
Kuidas seda saavutada? Kasutada kanari teenust mitte juurutamise hetkel. Lisame vanasse versiooni mingi teenuse, mis igal ajal suudab vÔtta mistahes eraldatud sÔlme, vÀhendada liiklust ilma juurutamata. SeejÀrel vÔrreldame: uurime vigu ja otsime seda piiri, kus saavutame kvaliteedi.

Millist kasu saime kanari juurutustest
Minimeerisime vigade kahju protsendi. Enamik juurutamisvigu tekib andmete vÔi prioritiseerimise ebajÔudlikkusest. Selliseid vigu on oluliselt vÀhem, sest suudame probleemi lahendada esimestel sekunditel.
Optimeerisime meeskondade tööd. Uutel töötajatel on "viga tegemise Ôigus": nad saavad juurutada tootmises ilma kartmata eksida, see loob lisainitsiatiivi ja motivatsiooni töötada. Kui nad midagi rikuvad, siis see ei ole kriitiline ja eksinud töötajat ei vallandata.
Automatiseerisime juurutamise. See ei ole enam kÀsitsi protsess nagu varem, vaid tÔeline automatiseeritud. Kuid see vÔtab rohkem aega.
TĂ”stsime esile olulised metrikad. Kogu ettevĂ”te, alates Ă€rist ja inseneridest, mĂ”istab, mis on meie tootes tĂ”eliselt oluline, nĂ€iteks kasutajate voog ja vĂ€ljavool. Me jĂ€lgime protsessi: testime mÔÔdikuid, rakendame uusi, vaatame, kuidas vanad töötavad, et luua sĂŒsteem, mis teenib raha efektiivsemalt.
Meil on palju hĂ€id praktikaid ja sĂŒsteeme, mis meid aitavad. Sellegipoolest pĂŒĂŒame olla professionaalid ja teha oma tööd kvaliteetselt, sĂ”ltumata sellest, kas meil on sĂŒsteem, mis meid toetab, vĂ”i mitte.
Insenerilised lĂ€henemised ja praktika â . Kui olete saavutanud edusamme tehnilise tĂ€iuslikkuse nimel ja olete valmis rÀÀkima, mis teid selles aitas, â .
Plneerime korraldada 8. juuni. Me mĂ”istame, et praegu on raske otsustada konverentsil osalemise ĂŒle. Samas usume, et karantiin ei ole pĂ”hjus professionaalse suhtluse ja arengu peatamiseks. SeetĂ”ttu leiname igal juhul viisi arutada teemehheti ĂŒlesandeid ja lĂ€henemisviise nende lahendamiseks â kui vajalik, liigume veebikeskkonda ja korraldame vĂ”rgustiku seal!
Allikas: habr.com
