Me arendame DevOps'i, kuidas oskame. Meid oli 8 inimest ja Vassia oli Windowsi alal parim. Ăhel pĂ€eval lahkus Vassia ja mul tekkis ĂŒlesanne kĂ€ivitada uus projekt, mis pakub Windows arendust. Kui ma lauas kokku kogusin kogu Windowsi arendustehnoloogia, siis sain aru, et olukord on valus...
Nii algab lugu Aleksandr SinÄinov . Tundub, et . Kui ettevĂ”ttest lahkus peamine Windowsi spetsialist, kĂŒsis Aleksandr endalt, mida nĂŒĂŒd teha. Loomulikult liikuda Linuxile! Aleksandr rÀÀgib, kuidas tal Ă”nnestus luua pretsedent ja viia osa Windowsi arendusest Linuxile, kasutades nĂ€iteks projekti, millel on 100 000 lĂ”ppkasutajat.

Kuidas tellida projekti RPM-i, kasutades TFS-i, Puppetit, Linux .NET core'i? Kuidas hallata projekti andmebaasi versioone, kui arendajad kuulevad Postgresi ja Flywayi nimed esmakordselt ja tĂ€htaeg on ĂŒlehomme? Kuidas integreerida Dockeriga? Kuidas motiveerida .NET arendajaid loobuma Windowsist ja smuutidest Puppet ja Linuxi kasuks? Kuidas lahendada ideoloogilisi konflikte, kui Windowsi tootmisressurss ei ole mitte ainult jĂ”ud, vaid ka soov ja ressursid? Sellest, samuti Web Deployst, testimisest, CI-st, TFS-i kasutamise praktikast olemasolevates projektides, ja muidugi rikutud tugede ja töötavate lahenduste kohta, rÀÀgib Aleksandri esitluse ĂŒlevaates.

Nii, Vassia lahkus, ĂŒlesanne on nĂŒĂŒd minu Ă”lul, arendajad ootavad pĂ”nevusega. Kui ma lĂ”puks mĂ”istsin, et Vassiat ei saa tagasi tuuaâalustasin tööd. Esiteks hindasin meie Windowsi virtuaalmasinate protsenti. Numbrid ei olnud Windowsi kasuks.

Kuna me aktiivselt arendame DevOps'i, mĂ”istsin, et uus rakenduse vĂ€ljaandmine peab muutuma. Lahendus oli ĂŒheselt selgeâkĂ”ik tuleb vĂ”imaluse korral viia Linuxile. Google aitas mindâtol hetkel oli .Net juba Linuxile portitud ja ma sain aru, et see on lahendus!
Miks .NET core koos Linuxiga?
Sellel oli mitu pĂ”hjust. Enamiku jaoks, kes peavad valima 'maksta raha' ja 'mitte maksta', valib enamik teiseânagu mina. MSDB litsents maksab umbes 1 000 dollarit, Windowsi virtuaalmasinate hooldus maksab sadu dollareid. SuurettevĂ”tte jaoks on need suuremad kulud. Seega sÀÀst â on esimene pĂ”hjus. Mitte kĂ”ige olulisem, aga ĂŒks tĂ€htsamaid.
Windowsi virtuaalmasinad vĂ”tavad rohkem ressursse kui nende Linuxi vennadâ nad on rasked. Arvestades suure ettevĂ”tte mÔÔtmeid, valisime Linuxi.
SĂŒsteem integreerub lihtsalt olemasolevasse CI-sse. Peame end progressiivseteks DevOps-ideks, kasutame Bamboo, Jenkinsit ja GitLab CI-d, seetĂ”ttu pĂ”hineb meil enamik Linuxil.
Viimane pĂ”hjus on mugav teenindamine. Oli vajalik vĂ€hendada sisenemise lĂ€ve âteenindajateleâ - neile, kes mĂ”istavad tehnilist poolt, tagavad teenuste tĂ”rgeteta toimimise ja hooldavad teenuseid teise taseme abil. Nad olid juba tuttavad Linuxi tehnoloogiaga, seega on neil uus toode, selle toetamine ja hooldamine tunduvalt lihtsam, kui kulutada lisajĂ”ude Windowsi platvormide sarnaste funktsioonide mĂ”istmiseks.
NÔuded
Esiteks ja kÔige olulisem on uue lahenduse mugavus arendajatele. Mitte kÔik neist ei olnud muudatusteks valmis, eriti pÀrast sÔna Linuxi mainimist. Arendajad soovivad armastatud Visual Studio't, TFS-i koos automaatsete testidega build'ide jaoks ja smuutisid. Kuidas toimub kohaletoimetamine tootmisse - need ei hooli. SeetÔttu otsustasime mitte muuta harjumuspÀrast protsessi ja jÀtta Windowsi arendamiseks kÔik muutumatuks.
Uus projekt tuleb integreerida olemasolevasse CI-sse. Rööpad olid juba paigas ning kogu töö tuli teostada arvestades konfiguratsiooni haldamise sĂŒsteemi parameetreid, standardse kohaletoimetamise protseduuri ja jĂ€lgimissĂŒsteemide nĂ”udeid.
Toetamise ja kasutamise lihtsus, nagu tingimus, et kÔik uued osalejad erinevatest osakondadest ja teenindusosakonnast saaksid madala sisenemise lÀve.
TÀhtpÀev oli eile.
Windowsi arendamise grupp
Millega töötas Windowsi meeskond?

Praegu vÔin kindlalt öelda, et IdentityServer4 on suurepÀrane tasuta alternatiiv ADFS-ile sarnaste vÔimalustega, vÔi et Entity Framework Core on arendajate paradiis, kus ei pea muretsema SQL skriptide kirjutamise pÀrast, vaid saab kirjeldada andmebaasi pÀringuid OOP terminoloogiaga. Kuid siis, kÀimasoleval tegevuskava arutelul, vaatasin sellele tehnoloogiale nagu sumero-keelele, tunnen ainult PostgreSQL-i ja Gitit.
Sel ajal kasutasime aktiivselt Puppet konfiguratsiooni haldamiseks. Enamikus meie projektides kasutasime GitLab CI-d, Elastic, balanseerisime kĂ”rge koormusega teenuseid HAProxy-ga, kĂ”ike jĂ€lgisime Zabbix, komplekti Grafana ja Prometheus, Jaeger, ja see kĂ”ik töötas riistvaral HP c ESXi . Tundub, et VMware. KĂ”igile tuttav - ĆŸanri klassika.

Vaatame ja proovime mÔista, mis juhtus enne, kui me kÔik need sekkumised ette vÔtsime.
Mis toimus
TFS on ĂŒsna vĂ”imas sĂŒsteem, mis mitte ainult ei toeta koodi edastamist arendajalt lĂ”pplikku tootmismasinasse, vaid omab ka tugevat tööriistakomplekti, et toetada vĂ€ga paindlikku integreerimist erinevate teenustega â CI tagamiseks ĂŒlepakkumise tasemel.

Varem olid need tÀiesti aknad. TFS kasutas mitmeid Build-agenete, millel koguti kokku palju projekte. Igas agendis oli 3-4 töötlijat, et töid paralleelselt jagada ja protsessi optimeerida. Edasi, vastavalt vÀljaande plaanidele, edastas TFS vÀrskelt valmistatud Buildi Windowsi rakenduste serverisse.
Mille poole me tahtsime jÔuda
Kasutame TFS-i edastamiseks ja arendamiseks, aga kĂ€ivitame rakenduse Linuxi rakenduste serveris, ja nende vahele jÀÀb mingi maagia. See Magic Box ongi eelseisva töö sool. Enne kui hakkame seda tĂŒkeldama, teen sammu kĂ”rvale ja ĂŒtlen paar sĂ”na rakendusest.
Projekt
Rakendus pakub funktsionaalsust ette makstud kaartide haldamiseks.

Klient
Oli olemas kaks tĂŒĂŒpi kasutajaid. Esimene saime ligipÀÀsu, autentides SSL SHA-2 sertifikaadi kaudu. Teisel oli ligipÀÀs sisselogimise ja parooli kaudu. HAProxy
Edasi lĂ€ks kliendi pĂ€ring HAProxy-sse, mis lahendas jĂ€rgmised ĂŒlesanded:
esmane autentimine;
- SSL-i lÔppemine;
- HTTP pÀringute kohandamine;
- pÀringute edastamine.
- Kliendi sertifikaadi kontrollimist tehti ahela kaudu. Me oleme
authority ja saame endale sellist lubada, kuna me ise vÀljastame teenuse klientidele sertifikaate. Pane tÀhele kolmandat punkti, tuleme sellele tagasi veidi hiljem.
Planeerisime tausta teha Linuxil. Taust interakteerub andmebaasiga, laadib vajalikud privileegide loendid ja seejÀrel, olenevalt sellest, milliste privileegidega autentitud kasutaja on, annab juurdepÀÀsu finantsdokumentide allkirjastamiseks ja nende tÀitmiseks vÔi mÔne raporti genereerimiseks.
Backend
Kokkuhoid HAProxy-st
Lisaks kahe konteksti olemasolule, mille kaudu igasugused kliendid liikuda, oli veel konteksti identity.
mis just vĂ”imaldab autentimist, see on tasuta ja vĂ”imas analoog IdentityServer4 ADFS Active Directory Federation Services â PĂ€ring identity-le töödeldi mitmes etapis. Esimene etapp â.
klient sattus tausta sattus tagasiside, mis vahetas andmeid selle serveriga ja kontrollis kliendi tokeni olemasolu. Kui seda ei leitud, naasis pĂ€ring tagasi konteksti, kust see tuli, kuid juba koos ĂŒmbersuunamisega, ja ĂŒmbersuunamine lĂ€ks identiteedi suunas.
Teine samm - pÀring jÔudis autentimislehele IdentityServeris, kus klient registreerus ja IdentityServeri andmebaasi ilmnes kauaoodatud token.
Kolmas samm - klient suunati tagasi konteksti, kust ta tuli.

IdentityServer4-l on eripÀra: vastus tagasipÀringule antakse tagasi HTTP. Kuigi me pingutasime serveri seadistamise nimel, uurides dokumentatsiooni, saime iga kord kliendi algse pÀringu, mille URL tuli HTTPS-i kaudu, kuid IdentityServer tagastas sama konteksti HTTP-as. Olime ƥokis! Ja suunasime selle kÔik identiteedi konteksti kaudu HAProxy, muutes protokolli HTTP HTTPS-iks pÀistes.
Kus on siis parendused ja kust me kokkuhoidu saime?
Kokkuhoid oli rahas, kasutades tasuta lahendust kasutajagrupi autentimiseks, ressurssidest, kuna me ei tÔstnud IdentityServer4 eraldi sÔlmena eraldi segmenti, vaid kasutasime seda koos backendiga samas serveris, kus rakenduse backend töötas.
Kuidas see peaks toimima
Nii nagu ma lubasin - Magic Box. Me saame juba aru, et liigume kindlalt suunas Linux. Vaatame konkreetsed ĂŒlesanded, mis vajasid lahendamist.

Puppet'i manifestid. Teenuse ja rakenduse konfigureerimise edastamiseks ja haldamiseks tuli kirjutada toredaid retsepte. Paberirull ja pliiats nÀitavad arusaadavalt, kui kiiresti ja kvaliteetselt see tehti.
Edastamise meetod. Standard on RPM. KÔik mÔistavad, et Linuxis ei saa teisiti, kuid ise projekt pÀrast ehitamist kujutas endast teatud kÀivitatavate DLL-failide komplekti. Need olid umbes 150, projekt oli piisavalt mahukas. Ainus harmooniline lahendus oli see binaar RPM-ks pakkida ja sealt rakendust arendada.
Versioonimine. Meil tuli tihti releasida ja oli vaja otsustada, kuidas paketi nime vormistada. See on TFS-iga integreerumise tase. Meie Build-agent oli Linuxis. Kui TFS saadab ĂŒlesande töötlejale - worker - Build-agentile, edastab ta talle ka muutuja grupi, mis siseneb töötleva protsessi keskkonda. Nendes keskkonnaparameetrites edastatakse Build'i nimi, versiooni nimi ja teised muutujad. TĂ€iendavate detailide jaoks vaadake jaotist 'RPM-paketi koostamine'.
TFS-i seadistamine koondus Pipeline'i seadistamisele. Varem kogusime kĂ”ik Windowsi projektid Windows-agente peal, kuid nĂŒĂŒd tuleb lisada Linux-agent - Build-agent, mis tuleb lisada koostamisrĂŒhma, rikastada mingite artefaktidega, mÀÀrata, milliseid projekte selles Build-agendis kokku pannakse, ja kuidagi modifitseerida Pipeline'i.
IdentityServer. ADFS ei ole meie tee, toetame Open Source'i.
KÀime komponentidest lÀbi.
Magic Box
Koosneb neljast osast.

Linux Build-agent. Linux, sest me kogume selle jaoks - loogiline. See osa viidi ellu kolme sammu kaudu.
- Seada worker'id ja mitte ainult ĂŒks, kuna projektiga olnuks plaanis tegeleda jagatult.
- Installida .NET Core 1.x. Miks just 1.x, kui 2.0 on juba olemas tavalisest repost? Sest kui me arendamist alustasime, oli stabiilne versioon 1.09 ja projekti otsustati teha selle pÔhjal.
- Git 2.x.
RPM-repositorium. RPM-pakette oli vaja kuskil hoida. Eeldati, et me kasutame sama ettevÔtte RPM-repositoriumi, mis on kergesti kÀttesaadav kÔigile Linuxi hostidele. Nii me ka tegime. Repositoriumi serveris on seadistatud webhook mis allalaadib nÔutud RPM-paketi mÀÀratud kohast. Versiooni paketist edastas Build-agent webhook'ile.
GitLab. TÀhtis! GitLab'i kasutavad siin mitte arendajad, vaid haldustöötajad rakenduse versioonide, pakettide versioonide, kÔigi Linuxi masinate seisundi jÀlgimiseks ning seal hoitakse retseptuurit - kÔiki Puppet'i manifeste.
Puppet â lahendab kĂ”ik vaidlusalused kĂŒsimused ja toob tĂ€pselt selle konfiguratsiooni, mida me soovime, Gitlab'ist.
Alustame sĂŒvenemist. Kuidas toimub DLL-i ĂŒlekanne RPM-isse?
DLL-i ĂŒlekanne RPM-isse
Oletame, et meil on rockstar arendamiseks .NET-is. Ta kasutab Visual Studio't ja loob vabanemis haru. PÀrast seda laadib ta selle Git'i, ja Git on siin TFS-i subjekt, st see on rakenduse reponeering, millega arendaja töötab.

PÀrast seda TFS nÀeb, et saabus uus commit. Milline rakendus? TFS-i seadetes on silt, milliseid ressursse omab teatud Build-agent. Antud juhul nÀeb ta, et kogume .NET Core projekti ja valib Linux Build-agendi basseinist.
Build-agent saab lÀhtekoodid, laadib alla vajalikud sÔltuvused nÀiteks .NET hoidlast, npm jne ning pÀrast rakenduse kokkupanekut ja jÀrgnevat pakkimist saatab RPM-paketi RPM-hoidlasse.
Teiselt poolt toimub jÀrgmine. Operatsioonide insener tegeleb projekti vÀljaandmisega: muudab pakettide versioone Hieras hoidlas, kus hoitakse rakenduse retsepti, mille jÀrel Puppet kÀivitab Yumi, tÔmbab uue paketi hoidlast ja uus rakenduse versioon on kasutamiseks valmis.

Kuidas me rÀÀgime, tundub kÔik lihtne, kuid mis tegelikult toimub Build-agendi sees?
DLL-i RPM pakkimine
LĂ€htekoodid on saadud ja TFS-ilt on saadud kogumise ĂŒlesanne. Build-agent kĂ€ivitab projekti kokkupaneku lĂ€htekoodidest.Kogutud projekt on saadaval mitmesugustes DLL-failides, mis on pakitud zip-arhiivi, et vĂ€hendada koormust failisĂŒsteemile.
ZIP-arhiiv visatakse RPM-paketi kogumise katalooge. Edasi Bash skript initsialiseerib keskkonnamuutujad, leiab Buildi versiooni, projekti versiooni, tee kogumise kataloogi ja kÀivitab RPM-buildi. PÀrast kokkupanekut avaldatakse pakett kohalikus hoidlas, mis on Build-agendi peal.
Edasi saadetakse Build-agentilt serverisse JSON-pÀring versiooni nime ja buildi mÀÀramisega. Webhook, millest ma varem rÀÀkisin, tÔmbab selle paketi kohalikust hoidlast Build-agendis ja teeb uue kogumise paigaldamiseks kÀttesaadavaks. Miks on just selline pakettide edastusskeem RPM-hoidlasse? Miks ei saa kohe kokkupandud paketti hoidlasse saata? Asi on selles, et see on tingimus turvalisuse tagamiseks. Selline stsenaarium piirab volitamata isikute vÔimalust laadida serverisse, mis on avatud kÔigile Linuxi masinatele, RPM-pakette.

Andmebaasi versioonimine
Konsiiliumil arendusega selgus, et poisid eelistavad MS SQL-i, kuid enamikul non-Windows projektidest oleme juba ulatuslikult kasutanud PostgreSQL-i. Kuna olime otsustanud loobuda kÔikidest tasulistest lahendustest, hakkasime ka siin kasutama PostgreSQL-i.
Konsiiliumis arendamisega selgus, et noorte seas on eelistatum MS SQL, kuid enamikus non-Windows projektides oleme juba PostgreSQL-i laialdaselt kasutanud. Kuna oleme juba otsustanud loobuda kÔikidest tasulistest lahendustest, hakkasime ka siin PostgreSQL-i kasutama.

Selles osas tahan rÀÀkida sellest, kuidas me andmebaasi versioone haldasime ja kuidas valisime Flyway ja Entity Framework Core vahel. Vaatame nende plusse ja miinuseid.
Miinused
Flyway töötab ainult ĂŒhes suunas, me ei saa tagasi tagasi minna â see on oluline miinus. Entity Framework Core'i saab vĂ”rrelda teiste parameetrite jĂ€rgi â arendaja mugavuse seisukohalt. Te ju mĂ€letate, et me panime selle prioriteediks ja peamine kriteerium oli mitte muuta midagi Windows-arenduse jaoks.
Flyway jaoks vajasime mingit mĂ€hist, et arendajad ei kirjutaks SQL-pĂ€ringuid. Neil on palju mugavam töötada OOP mĂ”istete jĂ€rgi. Kirjutasime juhised andmebaasi objektidega töötamiseks, SQL-pĂ€ring moodustus ja tĂ€ideti. Uus andmebaasi versioon on valmis, testitud â kĂ”ik töötab hĂ€sti.
Entity Framework Core'il on miinus â suurte koormuste korral loob ta mitteoptimaalseid SQL-pĂ€ringuid, ja andmebaasi koormus vĂ”ib olla mĂ€rkimisvÀÀrne. Aga kuna meie teenus ei ole vĂ€ga koormatud, me ei mÔÔda koormust sadade RPS-idega, vĂ”tsime need riskid ja delegaarisime probleemi tulevikule.
Plussid
Entity Framework Core töötavad karbist vĂ€lja ja on arendajale mugavad, samas Flyway lihtsalt integreerub olemasolevasse CI-sĂŒsteemi.Aga me ju teeme arendajatele mugavate lahendusi :)
Puppet nĂ€eb, et versioonide muutmise paketid, sealhulgas migratsioonide vastuolev paket, on saabumas. Esmalt installib see paketi, mis sisaldab migratsiooniskeeme ja andmebaasile seotud funktsionaalsust. SeejĂ€rel taaskĂ€ivitab rakenduse, mis töötab andmebaasiga. Edasi toimub ĂŒlejÀÀnud komponentide installimine. Pakettide installimise ja rakenduste kĂ€ivitamise jĂ€rjestus on kirjeldatud Puppet manifestis.
Rakendused kasutavad tundlikke andmeid, nagu tokenid, andmebaasi paroolid, kĂ”ik see tĂ”mmatakse Puppet master'i konfiguraatorisse, kus nad on krĂŒptitud kujul talletatud.
TFS-i probleemid
PĂ€rast seda, kui me olime Ă€ra otsustanud ja mĂ”istsime, et meil tĂ”epoolest kĂ”ik töötab, otsustasin vaadata, mis TFS-is toimub Win-arenduste jaoks teiste projektide osas â kiiresti vĂ”i mitte, ja avastasin tĂ”sised probleemid kiirusest.
Ăks peamisi projekte koostatakse 12-15 minutiga â see on kaua, nii ei saa elada. Kiire analĂŒĂŒs nĂ€itas tohutut madalseisu I/O poolest, ja see on massiivide peal.
Komponentide kaupa analĂŒĂŒsides eristasin kolm probleemi allikat. Esimene â
«Kaspersky antivirus» «Kaspersky viirusetĂ”rje», mis skaneerib lĂ€htekoodi kĂ”igil Windows Build-agentidel. Teine - Windows Indekseerija. See ei olnud vĂ€lja lĂŒlitatud ja Build-agentidel indekseeriti reaalajas kĂ”ik deploimise protsessis.
Kolmas - npm install. Selgus, et enamikus Pipelines kasutasime just seda stsenaariumi. Miks see halb on? Npm install protseduur kÀivitub sÔltuvuste puu moodustamise ajal package-lock.json, kus fikseeritakse pakettide versioonid, mida projektile kogumisel kasutatakse. Miinuseks on, et npm install toob iga kord vÀrsked pakettide versioonid internetist, mis vÔtab palju aega suure projekti puhul.
Arendajad katsetavad vahel kohalikul masinal, et kontrollida eraldi osa vÔi projekti toimivust. MÔnikord juhtus, et kohapeal oli kÔik suurepÀrane, kuid kui kogudes ja deployides, ei töötanud midagi. Alustame probleemi uurimist - aha, erinevad pakettide versioonid sÔltuvustes.
Lahendus
- LĂ€htekoodid AV erandite jaoks.
- Indekseerimise vĂ€ljalĂŒlitamine.
- Ăleminek npm ci.
npm ci eelised on, et me kogume sĂ”ltuvuste puu vaid ĂŒks kord, ja saame arendajale pakkuda relevantsete paketide nimekirja, millega ta vĂ”ib kohapeal katsetada. See kasutab aega arendajate jaoks, kes koodi kirjutavad.
Konfiguratsioon
NĂŒĂŒd natuke konfigureerimisest. Ajalooliselt oleme kasutanud Nexus repository haldamiseks, sealhulgas Internal REPO. Sellesse sisemisse reposse tarnitakse kĂ”ik komponendid, mida me kasutame sisetööde jaoks, nĂ€iteks kohandatud monitooringud.

Kasutame ka NuGet, kuna see vahetab paremini kui teised paketihaldurid.
Tulemus
PÀrast Build-agentide optimeerimist vÀhenes keskmine kogumise aeg 12 minutilt 7 minutile.
Kui arvestada kĂ”iki masinaid, mida me oleksime vĂ”inud Windowsi jaoks kasutada, kuid mille oleme selles projektis Linuxile ĂŒle viinud, sÀÀstsime umbes 10 000 dollarit. Ja see on ainult litsentside pealt, aga kui arvestada hooldust - rohkem.
Plaane
Oleme jÀrgmisse kvartalisse planeerinud koodi kohaletoimetamise optimeerimise.
Ăleminek eelkoostatud Docker-pildile. TFS on suurepĂ€rane asi paljude pluginatega, mis vĂ”imaldavad integreerida Pipeline'i, sealhulgas ka kogumist triggered, nĂ€iteks Docker-pildi kogumist. Soovime, et see trigger töötab just seda. package-lock.json. Kui projektis kasutatavad koostisosad mingil juhul muutuvad, siis koostame uue Docker-pildi. Tulevikus kasutatakse seda kokku pandud rakenduse konteineri juurutamiseks. Praegu seda ei ole, kuid plaanime ĂŒle minna mikroteenuste arhitektuurile Kuberneteses, mis meie firmas aktiivselt areneb ja pikka aega tootmislahendusi teenindab.
Elulookirjeldus
Kutsun kÔiki vÀlja viskama Windowsi, kuid see ei ole sellepÀrast, et ma ei oska seda kasutada. PÔhjus on see, et suur osa avatud lÀhtekoodiga lahendustest on Linuxi tehnoloogia. Te saate hÀsti ressursside pealt kokku hoida. Minu arvates on tulevik avatud lÀhtekoodiga lahendustes Linuxil koos tugeva kogukonnaga.
Esineja profiil Aleksandr SinÄvin .
 on arendamise, testimise ja haldamise protsesside integreerimise konverents professionaalide jaoks professionaalide poolt. Just seetĂ”ttu on projekt, millest Aleksandr rÀÀkis, realiseeritud ja töötab, ning esinemise pĂ€eva jooksul viidi lĂ€bi kaks edukat vĂ€ljalaset. 27. ja 28. mail on veelgi rohkem sarnaseid juhtumeid praktikutelt. Veel on vĂ”imalik hĂŒpata viimasesse vagunisse ja vĂ”i rahulikult piletit. Kohtume Skolkoves!
Allikas: habr.com
