27. mai DevOpsConf 2019 peamise saali konverentsil, mis toimub festivali raames , konverentsi sektsioonis "Pidev tarne" esitati ettekannet "werf - meie tööriist CI/CD jaoks Kuberneteses". Ettekanne kĂ€sitleb neid probleeme ja vĂ€ljakutseid, millega igaĂŒhel silmitsi seista tuleb, kui nĂ€iteks kasutatakse Kuberneteset, samuti nĂŒansse, mis ei pruugi kohe silma paista. Potentsiaalsete lahenduste kĂ€sitlemisel nĂ€itame, kuidas see on ellu viidud avatud lĂ€htekoodiga tööriist .
Alates ettekandest on meie utiliit (varem tuntud kui dapp) ĂŒletanud ajaloolise piiri 1000 tĂ€hte GitHubis â loodame, et kasvav kasutajate kogukond muudab paljude DevOps inseneride elu lihtsamaks.

Nii et, tutvustame (~47 minutit, oluliselt informatiivsem kui artikkel) ja selle pÔhisisu tekstivormis. Alustame!
Koodi kohaletoimetamine Kubernetesesse
Ettekanne ei kÀsitle enam werfi, vaid CI/CD-d Kuberneteses, eeldades, et meie tarkvara on pakitud Docker-konteineritesse (sellest rÀÀkisin ma ), ja K8s kasutatakse selle tootmisse viimisel (sellest - ).
Kuidas nÀeb vÀlja kohaletoimetamine Kubernetesesse?
- On Git-repos, kus on kood ja selle koostamiseks vajalikud juhised. Rakendus kogutakse Docker-pildiks ja avaldatakse Docker Registry's.
- Samuti on samas repos juhised rakenduse kohaletoimetamiseks ja kÀitamiseks. TÀienduse etapis saadetakse need juhised Kubernetesesse, mis saab vajalikku pilti registry'st ja kÀivitab selle.
- Ja tavaliselt on olemas ka testid. MÔningaid teste saab kÀivitada pildi avaldamise ajal. Samuti saab (neid samu juhiseid kasutades) luua rakenduse koopia (eraldi K8s nimespaces vÔi eraldi klastris) ja testida seal.
- LĂ”puks on vaja CI-sĂŒsteemi, mis saab sĂŒndmusi Git'ist (vĂ”i nuppude vajutamisest) ja kutsub vĂ€lja kĂ”ik mÀÀratud etapid: build, publish, deploy, test.

Siin on mitu olulist tÀhelepanekut:
- Kuna meil on muutumatu infrastruktuur (immutable infrastructure), peab rakenduse pilt, mida kasutatakse kĂ”igis etappides (staging, tootmine jne), olema ĂŒks. Selle kohta rÀÀkisin ma pĂ”hjalikumalt koos nĂ€idistega .
- Kuna me jĂ€rgime lĂ€henemist infrastruktuur kui kood (IaC), rakenduskood, juhised selle koostamiseks ja kĂ€itamiseks peavad olema just selles ĂŒhes repos. Selle kohta - vt .
- Kohaletoimetamise ahel (delivery) meie nĂ€eme seda tavaliselt nii: rakendus on kokku pandud, testitud ja vĂ€lja antud (vĂ€ljaandmise etapp) ja kĂ”ik â kohaletoimetamine on toimunud. Kuid tegelikult saab kasutaja selle, mis te vĂ€lja andsite, ei sel hetkel, kui te selle tootmiseni toimetasite, ja millal ta sai sinna sisse logida ning see tootmine töötas. Seega arvan, et kohaletoimetamise ahel lĂ”ppeb ainult ekspluateerimise etapis (kĂ€itus), ja tĂ€psemalt isegi siis, kui kood kĂ”rvaldati tootmisest (asendades selle uuega).
Naaseme varem nimetatud kohaletoimetamise skeemi juurde Kuberneteses: selle lĂ”id mitte ainult meie, vaid ka praktiliselt igaĂŒke, kes selle probleemiga tegeleb. Selle mustrit nimetatakse praegu GitOps (selle termini ja selle taga olevate ideede kohta saab lugeda ). Vaatame skeemi etappe.
Kogumise etapp
Tundub, et 2019. aastal ei ole midagi uut rÀÀkida Docker-imaid kogumise kohta, kuna kĂ”ik oskavad kirjutada Dockerfile'e ja neid kĂ€ivitada. docker build?.. ĐĐŸŃ ĐœŃĐ°ĐœŃŃ, ĐœĐ° ĐșĐŸŃĐŸŃŃĐ” Ń
ĐŸŃĐ”Đ»ĐŸŃŃ Đ±Ń ĐŸĐ±ŃаŃĐžŃŃ ĐČĐœĐžĐŒĐ°ĐœĐžĐ”:
- Pildi kaal on oluline, seega kasutage , et jÀtta pildis alles ainult see, mis on tegelikult rakenduse toimimiseks vajalik.
- Kihtide arvu tuleb minimeerida, ĂŒhendades mĂ”tteliselt seotud
KĂIVITA-kĂ€skluste jadad. - Kuid see toob kaasa probleeme silumisega, kuna tĂ”rgete korral tuleb tuvastada tĂ€pselt see kĂ€sklus jadast, mis probleemi pĂ”hjustas.
- Koostamise kiirus on oluline, sest soovime kiiresti muudatusi vĂ€lja anda ja tulemust vaadata. NĂ€iteks ei taha me igal rakenduse koostamisel sĂ”ltuvusi ĂŒmber koostada.
- Sageli on ĂŒhest Git-repositooriumist vajalik palju pilte, mis saab lahendada Dockerfile'ide komplektiga (vĂ”i ĂŒhes failis mÀÀratletud etappidega) ja Bash-skriptiga nende jĂ€rjestikuseks koostamiseks.
See oli vaid jÀÀmÀe tipp, millega kÔik silmitsi seisavad. Kuid olemas on ka teised probleemid, nimelt:
- Sageli on selle koostamise etapis meil vaja midagi mÀMountida (nÀiteks, salvestada apt-kÀskluse tulemused eraldi katalooge).
- Me soovime Ansible asemel, et kirjutada shellis.
- Me soovime koostada ilma Dockerita (miks meil on vaja lisavirtuaalmasinat, millele kÔik selle jaoks seadistada, kui meil on juba Kubernetes klaster, kus saab konteinerit kÀivitada?).
- Paralleelne kogu, mida vÔib tÔlgendada erinevalt: erinevad kÀsklused Dockerfile'is (kui kasutatakse multi-stage'i), mitmed sama repo commit'id, mitmed Dockerfile'id.
- Jaotatud koostamine: me tahame koguda midagi pod'ides, mis on "efemerid", kuna neil kaob cache, seega tuleb see kuhugile eraldi salvestada.
- LÔpuks nimetasin soovide tippu automagiks: oleks ideaalne sisse logida hoidlasse, sisestada mingi kÀsk ja saada valmis pilt, mis on kokku pandud arusaamisega, kuidas ja mida Ôigesti teha. Siiski, isiklikult ei ole ma kindel, et kÔiki nuansse saab nii ettenÀhtud.
Ja siin on projektid:
- â koostaja ettevĂ”ttelt Docker Inc (juba integreeritud aktuaalsetesse Docker'i versioonidesse), mis pĂŒĂŒab lahendada kĂ”ik need probleemid;
- â koostaja Google'ilt, mis vĂ”imaldab kokku panna ilma Dockerita;
- â CNCF katse teha automagiat ja eelkĂ”ige huvitav lahendus rebase'i jaoks kihtide jaoks;
- ja veel hulk teisi tööriistu, nagu , âŠ
⊠ja vaadake, kui palju neil on tĂ€hti GitHubis. Seega, ĂŒhelt poolt, docker build on olemas ja nad vĂ”ivad midagi teha, kuid tegelikult kĂŒsimus pole tĂ€ielikult lahendatud â tĂ”estuseks sellele on mitme alternatiivse koostaja parallelne arendamine, igaĂŒhel neist on oma osa probleemide lahendamiseks.
Koostamine werf'is
Nii jĂ”udsime (varem nagu dapp) â Open Source tööriist ettevĂ”ttelt "Flant", mille loomisega tegeleme juba palju aastaid. KĂ”ik algas umbes 5 aastat tagasi Bash-skriptidest, mis optimeerisid Dockerfile'ide koostamist, ja viimased 3 aastat on kĂ€inud tĂ€iemahuline arendus ĂŒhe projekti raames oma Git repo'l. (esialgu Ruby's, aga siis Go's, ja ĂŒhtlasi ĂŒmber nimetatud). Milliseid koostamise kĂŒsimusi on werf lahendanud?

Siniseks vĂ€rvitud probleemid on juba rakendatud, paralleelne koostamine on tehtud ĂŒhe hosti raames, ja kollasega esile tĂ”stetud kĂŒsimused plaanime lĂ”petada suve lĂ”puks.
Registreerimisse (publish) etapp
Sisse laaditud docker push⊠â mis vĂ”ib olla keeruline, et laadida pilt registreerimisse? Ja siis tekib kĂŒsimus: âMillise sildi peaks pildile panema?â See tekib seetĂ”ttu, et meil on Gitflow (vĂ”i strateegia Git'ist) ja Kubernetes, ning tööstus pĂŒĂŒdleb selle poole, et Kuberneteses toimu meie Git'iga kooskĂ”las. Sest Git on meie ainus tĂ”e allikas.
Mis selles keerulist on? Tagada korduv kasutatavust: Git commitist, kellel on olemuslikult muutumatu (muutumatuna), kuni Docker pildini, mida tuleb sÀilitada samana.
Meile on samuti oluline mÀÀratleda pÀritolu, kuna me tahame mÔista, millisest commit'ist on kokku pandud rakendus, mis on kÀivitatud Kuberneteses (siis saame teha diff'e ja sarnaseid asju).
Sildistamise strateegiad
Esimene on lihtne git tag. Meil on registri pilt, mida on silti pandud nagu 1.0. Kubernetes'is on etapp ja tootmine, kuhu see pilt on vÀlja veetud. Git'is teeme commit'e ja mingil hetkel paneme sildi 2.0. Kogume selle vastavalt repo juhistele ja paneme registrisse sildiga 2.0. VÀlja veame etapis ja, kui kÔik on korras, siis tootmisse.

Selle lĂ€henemise probleem on see, et me panime kĂ”igepealt sildi ja alles siis testisime ja vĂ€ljastasime. Miks? Esiteks, see on lihtsalt ebatavaline: me vĂ€ljastame versiooni tarkvarast, mida me isegi ei ole kontrollinud (me ei saa teisiti, kuna testi tegemiseks tuleb silt panna). Teiseks, see tee ei ĂŒhildu Gitflow'ga.
Teine variant on git commit + tag. Master-haru on olemas silt 1.0; selle jaoks registris on pilt, mis on vÀlja veetud tootmisse. Lisaks on Kubernetes'i klastris preview ja staging kontuurid. Edasi jÀrgime Gitflow't: peaharu arendamiseks (develop) loome uusi funktsioone, millega tekib commit identifikaator #c1. Kogume selle ja avaldame registrisse, kasutades seda identifikaatorit (#c1). Sama identifikaatoriga vÀljastame preview'l. Sarnast teeme commit'idega #c2 ja #c3.
Kui oleme aru saanud, et funktsioone on piisavalt, hakkame kĂ”ike stabiliseerima. Git'is loome haru release_1.1 (pĂ”hjal #c3 API-s develop). Selle vĂ€ljaande kogumist ei ole vaja, kuna see tehti eelmisel etapil. SeetĂ”ttu vĂ”ime selle lihtsalt staging'ile vĂ€lja anda. Parandame vead #c4 ja samamoodi vĂ€ljastame staging'ile. Samal ajal kĂ€ib paralleelselt arendus develop, kuhu perioodiliselt kantakse muudatusi release_1.1. Ăhel hetkel saame kokku pandud ja staging'ile vĂ€lja antud commit'i, millega oleme rahul (#c25).
Siis teeme merge'i (kiirelt edasiviimisega) vĂ€ljalaske harust (release_1.1) masterisse. Paneme sellele commit'ile sildi uue versiooniga (1.1). Kuid see pilt on juba registris kokku pandud, seega, et seda uuesti kokku ei pandaks, lisame lihtsalt teise sildi juba olemasolevale pildile (nĂŒĂŒd on see registris silti #c25 ja 1.1). PĂ€rast seda vĂ€ljastame selle tootmisse.
On puudus, et staging'ile on vĂ€lja veetud ĂŒks pilt (#c25), ja tootmisse on vĂ€lja veetud justkui teine (1.1), kuid me teame, et âfĂŒĂŒsiliseltâ on see sama registreerimise pilt.

Kuid tegelik miinus on see, et merge commit'e ei toetata, tuleb teha kiire edasiviimine.
Saame edasi minna ja teha trikki⊠Vaadakem lihtsa Dockerfile nÀidet:
FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb
FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/publicLoome sellest faili sellise pÔhimÔtte kohaselt, et vÔtame:
- SHA256 kasutatavate piltide identifikaatoritest (
ruby:2.3janginx:alpine), mis on nende sisu kontrollsummad; - kÔik kÀsud (
KĂIVITA,CMDjne); - SHA256 failidest, mis on lisatud.
⊠ja vÔtame kontrollsumma (uuesti SHA256) sellisest failist. See on allkiri kÔik, mis mÀÀratleb Docker-pildi sisu.

Tagasi skeemi juurde ja kasutame commit'ide asemel selliseid allkirju, s.t. mÀrgistame pilte allkirjadega.

NĂŒĂŒd, kui on vaja nĂ€iteks muutusi vĂ€ljalaskest master'isse, saame teha tĂ”elise merge commit'i: sellel on erinev identifikaator, kuid sama allkiri. Samasuguse identifikaatoriga viime pildi tootmisesse.
Puuduseks on see, et nĂŒĂŒd ei saa mÀÀrata, milline commit on tootmises â kontrollsummad töötavad ainult ĂŒhes suunas. See probleem lahendatakse tĂ€iendava metainfoki kihiga â rÀÀgin sellest hiljem.
MĂ€rgistamine werfis
Werfis oleme lĂ€inud veel kaugemale ja valmistasime ette jaotatud ehituse, mille vahemĂ€lu ei asu ĂŒhel masinal⊠Seega genereerime kahte tĂŒĂŒpi Docker-pilte, mida nimetame stage ja image.
Git-repositooriumis werf hoitakse spetsiifilisi juhiseid ehitamiseks, mis kirjeldavad erinevaid ehitusetappe (beforeInstall, install, beforeSetup, setup). Esimene stage-pilt luuakse allkirjaga, mis on mÀÀratletud esialgsete sammude kontrollsummana. SeejÀrel lisame lÀhtekoodi, uue stage-pildi jaoks arvutame selle kontrollsumma⊠Need toimingud korduvad kÔigis etappides, mille tulemusena saame komplekti stage-piltidest. SeejÀrel loome lÔpliku image-pildi, mis sisaldab ka selle pÀritolu metainfot. Ja just seda pilti me mÀrgistame erinevate viisidega (detailid hiljem).

Laske, et pÀrast seda tekib uus commit, kus on muudetud ainult rakenduse koodi. Mis juhtub? Koodi muutuste jaoks luuakse patch, uue stage-image'i valmistamiseks. Selle allkiri mÀÀratakse vanade stage-image'i ja uue patƥi kontrollsummana. Sellest pildist vormitakse uus lÔplik image.
Nii on stage-image'd - vahemÀlu, mida saab salvestada jaotatult, ja sellest loodud image'id laaditakse Docker Registry'sse.

Registry puhastamine
RÀÀgime mitte kihtide eemaldamisest, mis on jÀÀnud hÔljuvaks pÀrast kustutatud silte - see on Docker Registry enda standardne funktsioon. Jutt on olukorrast, kus akumuleerub palju Docker-silte ja me mÔistame, et osa neist pole meile enam vajalik, aga nad vÔtavad ruumi (ja/vÔi me maksame selle eest).
Milliseid puhastamisstrateegiaid on?
- VĂ”ib lihtsalt mitte midagi puhastada. MĂ”nikord on tĂ”esti lihtsam maksta natuke ĂŒleliigse ruumi eest, kui harutada lahti tohutult sildistatud kimbu. Kuid see töötab ainult teatud hetkeni.
- TĂ€ielik lĂ€htestamine. Kui kustutada kĂ”ik pildid ja uuesti koostada ainult asjakohased CI-sĂŒsteemis, vĂ”ib tekkida probleem. Kui tootmises konteiner taaskĂ€ivitub, laaditakse sellele uus pilt - selline, mida pole veel keegi testinud. See tapab mĂ”tte immutable infrastructure'ist.
- Blue-green. Kui ĂŒks registry hakkas ĂŒle koormama - laadime pildid teise. Sama probleem, mis eelmisel meetodil: millal saab seda registry't, mis hakkas ĂŒle koormama, puhastada?
- Aja jĂ€rgi. Kustutada kĂ”ik pildid, mis on vanemad kui 1 kuu? Aga kindlasti on olemas teenus, mis pole terve kuu uuendatudâŠ
- KÀsitsi mÀÀrata, mida saab juba eemaldada.
TÔeliselt elujÔulisi variante on kaks: mitte puhastada vÔi kombinatsioon blue-green + kÀsitsi. Viimase puhul on juttu jÀrgmistest asjadest: kui mÔistate, et on aeg registrit puhastada, loote uue ja lisate kÔik uued pildid sellesse, nÀiteks kuu aega. Ja kuu pÀrast vaatate, millised pod'id Kuberneteses ikka veel kasutavad vana registrit ja viite need ka uude registrisse.
Millele me lĂ”puks jĂ”udsime werf? ĐŃ ŃĐŸĐ±ĐžŃĐ°Đ”ĐŒ:
- Git head: kĂ”ik sildid, kĂ”ik harud â eeldades, et kĂ”ik, mis on Git'is silte saanud, peab olema ka piltides (kui ei, siis tuleb need Git'ist eemaldada);
- kÔik pod'id, mis on praegu Kubernetesesse tÔmmatud;
- vanad ReplicaSet'id (mis just hiljuti tÔmmati), samuti plaanime skaneerida Helm-vÀljaandeid ja valida sealt viimased pildid.
... ja teeme sellest komplektist whitelist â nimekirja piltidest, mida me ei kustuta. KĂ”ik muu puhastame, pĂ€rast mida leiame orvud stage-pildid ja kustutame need samuti.
Deploy'e etapp
UsaldusvÀÀrne deklaratiivsus
Esimene punkt, millele sooviksin tÀhelepanu juhtida deploy'e osas, on vÀrskendatud ressursside konfiguratsiooni vÀljalatku minek, mis on kuulutatud deklaratiivselt. Originaalne YAML-dokument Kubernetes'i ressursside kirjelduseks erineb alati oluliselt tulemusest, mis tegelikult klastris töötab. Sest Kubernetes lisab konfiguratsiooni:
- tuvastid;
- teenuseteabe;
- palju vaikimisi vÀÀrtusi;
- osa praeguse staatuse kohta;
- muudatused, mis on tehtud vastuvÔtu veebirakenduse töö kÀigus;
- erinevate kontrollere (ja planeerija) töö tulemused.
Seega, kui ilmub uus ressursi konfiguratsioon (uus), ei saa me lihtsalt olemasolevat, 'elavat' konfiguratsiooni (live) selle asemel kirjutada. Selleks peame me vÔrdlema uus eelnevalt rakendatud konfiguratsiooniga (last-applied) ja peame peale pressima live saadud patƥi.
Seda lÀhenemist nimetatakse 2-way merge. Seda kasutatakse nÀiteks Helm'is.
On olemas ka 3-way merge, mille erinevus on see, et:
- vÔrreldes last-applied ja uus, vaatame, mis on eemaldatud;
- vÔrreldes uus ja live, vaatame, mis on lisatud vÔi muudetud;
- kumulatiivne patch kantakse live.
Me deployime 1000+ rakendust Helmiga, seega elame tegelikult 2-way merge'i reĆŸiimis. Sellega on aga mitmeid probleeme, mille oleme lahendanud oma plaastritega, mis aitavad Helm'il korralikult töötada.
Tegelik vÀljalaskmise seisund
PĂ€rast seda, kui meie CI-sĂŒsteem on genereerinud uue konfiguratsiooni Kubernetes'ile jĂ€rgmise sĂŒndmuse kĂ€igus, edastab see selle rakendamiseks (apply) klastrisse â Helm'i vĂ”i kubectl applyabil. Edasi kĂ€ib juba kirjeldatud N-way merge, millele Kubernetes API vastab CI-sĂŒsteemile heakskiiduga, ja see omakorda oma kasutajale.

Kuid on tohutu probleem: nimelt edukas rakendamine ei tĂ€henda edukat vĂ€ljalaskmist.. Kui Kubernetes mĂ”istab, millised muutused tuleb rakendada, rakendab ta need â me ei tea veel, mis tulemusena vĂ€lja tuleb. NĂ€iteks vĂ”ib esimeses etapis pod'ide uuendamine ja taaskĂ€ivitamine ĂŒhel pool Ă”nnestuda, samas kui taustaprotsessis mitte, ja saame rakenduse kĂ€itamiseks erinevaid versioone.
Ette, et kĂ”ik Ă”igesti teha, vajab see skeem tĂ€iendavat elementi â spetsiaalset jĂ€lgijat, mis saab Kubernetes API-lt teavet oleku kohta ja edastab selle edasiseks analĂŒĂŒsiks tegeliku olukorra kohta. Oleme loonud avatud lĂ€htekoodiga teegid Go keeles â (vt selle teadaannet ), â mis lahendab selle probleemi ja on integreeritud werf-iga.
Selle jĂ€lgija kĂ€itumist werf tasemel saab seadistada annotatsioonide abil, mis on mÀÀratud Deployments vĂ”i StatefulSets. Peamine annotatsioon on fail-mode â mĂ”istab jĂ€rgmist vÀÀrtust:
-
IgnoreAndContinueDeployProcessâ ignoreerime selle komponendi juurutamise probleeme ja jĂ€tkame juurutamist; -
FailWholeDeployProcessImmediatelyâ vea tekkimine selles komponendis peatab juurutamisprotsessi; -
HopeUntilEndOfDeployProcessâ loodame, et see komponent töötab juurutamise lĂ”puks.
NÀiteks selline kombinatsioon ressurssidest ja annotatsiooni vÀÀrtustest fail-mode:

Kui deployime esimest korda, ei pruugi andmebaas (MongoDB) veel valmis olla â Deployment'id kukuvad. Kuid vĂ”ime oodata hetke, et see kĂ€ivituks, ja deploy siiski Ă”nnestub.
Werf-is on veel kaks annotatsiooni kubedogi jaoks:
-
failures-allowed-per-replicaâ lubatud kokkuvarisemiste arv iga koopia kohta; -
show-logs-untilâ reguleerib hetke, kuni mille jooksul werf kuvab (stdout'is) logisid kĂ”igist vĂ€lja lastud pod'idest. Vaikimisi on seePodIsReady(et ignoreerida sĂ”numeid, mis tĂ”enĂ€oliselt ei ole meile vajalikud, kui pod hakkab liiklust saama), kuid lubatavad on ka vÀÀrtusedControllerIsReadyjaEndOfDeploy.
Mida veel tahame juurutamiselt?
Peale juba kirjeldatud kahest punktist soovime:
- nĂ€ha logisid â ja ainult vajalikke, mitte kĂ”ikide teiste;
- vÀlja selgitada edusamme, sest kui töö "vaikselt" ripub mitu minutit, on oluline mÔista, mis seal toimub;
- omada automaatset tagasivÔtmist juhtudel, kui midagi lÀheb valesti (ja seega on kriitiline teada juurutamise tegelikku staatust). Juurutamine peab olema atomaarne: kas see lÀheb lÔpuni vÔi kÔik naaseb eelnevasse olekusse.
Summary
Meie ettevĂ”ttena on nende erinevate raamistikus etappide (build, publish, deploy) lĂ€biviimiseks piisav CI-sĂŒsteem ja tööriist .
KokkuvÔtteks:

Werf-i abil oleme saavutanud palju DevOps-inseneride probleemide lahendamisel ning oleksime rÔÔmsad, kui laiem kogukond vÀhemalt prooviks seda tööriista praktikas. HÀid tulemusi on koos saavutada lihtsam.
Videod ja slaidid
Esitluse video (~47 minutit):

Ettekande esitlemine:
P.S.
Teised ettekanded Kubernetesest meie blogis:
- «» (Dmitri Stoljarov; 27. aprill 2019 Stachka'l);
- «» (Andrei Polovov; 8. aprill 2019 Saint HighLoad++'s);
- «» (Dmitri Stoljarov; 8. november 2018 HighLoad++'s);
- «» (Dmitri Stoljarov; 28. mai 2018 RootConf'il);
- «» (Dmitri Stoljarov; 7. november 2017 HighLoad++'s);
- «» (Dmitri Stoljarov; 6. juuni 2017 RootConf'il).
Allikas: habr.com
