Wat witte wy oer mikrotsjinsten

Hallo! Myn namme is Vadim Madison, ik lied de ûntwikkeling fan it Avito System Platform. It is mear as ien kear sein hoe't wy yn it bedriuw ferpleatse fan in monolityske arsjitektuer nei in mikroservices. It is tiid om te dielen hoe't wy ús ynfrastruktuer hawwe omfoarme om it measte út mikrotsjinsten te heljen en te foarkommen dat wy yn har ferdwale. Hoe PaaS ús hjir helpt, hoe't wy de ynset ferienfâldigden en de skepping fan in mikrotsjinst fermindere ta ien klik - lês fierder. Net alles wat ik hjirûnder skriuw is folslein ymplementearre yn Avito, guon dêrfan is hoe't wy ús platfoarm ûntwikkelje.

(En oan 'e ein fan dit artikel sil ik prate oer de kâns om in trije-dei seminar by te wenjen fan mikroservice-arsjitektuer-ekspert Chris Richardson).

Wat witte wy oer mikrotsjinsten

Hoe't wy by mikrotsjinsten kamen

Avito is ien fan 'e grutste klassifisearre siden yn' e wrâld; mear as 15 miljoen nije advertinsjes wurde der per dei publisearre. Us backend akseptearret mear dan 20 tûzen oanfragen per sekonde. Wy hawwe op it stuit ferskate hûnderten mikrotsjinsten.

Wy bouwe al ferskate jierren in mikroservice-arsjitektuer. Hoe krekt - ús kollega's yn detail ferteld by ús seksje by RIT++ 2017. By CodeFest 2017 (sjoch. видео), Sergey Orlov en Mikhail Prokopchuk ferklearre yn detail wêrom't wy de oergong nei mikrotsjinsten nedich wiene en hokker rol Kubernetes hjir spile. No ja, no dogge wy der alles oan om de skaalfergruttingskosten te minimalisearjen dy't yn sa'n arsjitektuer binne.

Yn it earstoan makken wy gjin ekosysteem dat ús wiidweidich helpe soe om mikrotsjinsten te ûntwikkeljen en te lansearjen. Se sammele gewoan ferstannige oplossingen foar iepen boarne, lansearren se thús en noege de ûntwikkelder út om mei har om te gean. As gefolch, hy gie nei in tsiental plakken (dashboards, ynterne tsjinsten), wêrnei't hy waard sterker yn syn winsk om te snijen koade op de âlde manier, yn in monolith. De griene kleur yn 'e diagrammen hjirûnder jout oan wat de ûntwikkelder op ien of oare manier mei syn eigen hannen docht, en de giele kleur jout automatisearring oan.

Wat witte wy oer mikrotsjinsten

No yn it PaaS CLI-hulpprogramma wurdt in nije tsjinst makke mei ien kommando, en in nije databank wurdt tafoege mei noch twa en ynset nei Stage.

Wat witte wy oer mikrotsjinsten

Hoe te oerwinnen it tiidrek fan "mikroservice fragmentation"

Mei in monolithyske arsjitektuer, om 'e konsistinsje fan feroaringen yn it produkt, ûntwikkelders waarden twongen om út te finen wat der bart mei har buorlju. By it wurkjen oan de nije arsjitektuer binne tsjinstkonteksten net mear fan inoar ôfhinklik.

Derneist, foar in mikroservice-arsjitektuer om effektyf te wêzen, moatte in protte prosessen fêststeld wurde, nammentlik:

• logging;
• fersyk tracing (Jaeger);
• flater aggregation (Sentry);
• statusen, berjochten, eveneminten fan Kubernetes (Event Stream Processing);
• race limyt / circuit breaker (do kinst brûke Hystrix);
• kontrôle fan tsjinstferbining (wy brûke Netramesh);
• tafersjoch (Grafana);
• gearkomste (TeamCity);
• kommunikaasje en notifikaasje (Slack, e-post);
• taak tracking; (Jira)
• tarieding fan dokumintaasje.

Om te soargjen dat it systeem syn yntegriteit net ferliest en effektyf bliuwt as it skalen, hawwe wy de organisaasje fan mikrotsjinsten yn Avito opnij betocht.

Hoe't wy mikrotsjinsten beheare

De folgjende help om in unifoarm "partijbelied" út te fieren ûnder in protte Avito-mikrotsjinsten:

  • ferdieling fan ynfrastruktuer yn lagen;
  • Platfoarm as in tsjinst (PaaS) konsept;
  • tafersjoch op alles wat bart mei mikrotsjinsten.

Ynfrastruktuerabstraksjelagen omfetsje trije lagen. Litte wy fan boppen nei ûnderen gean.

A. Top - tsjinst mesh. Earst hawwe wy Istio besocht, mar it die bliken dat it tefolle boarnen brûkt, wat te djoer is foar ús folumes. Dêrom ûntwikkele senior yngenieur yn it arsjitekteteam Alexander Lukyanchenko syn eigen oplossing - Netramesh (beskikber yn Open Source), dy't wy op it stuit brûke yn produksje en dy't ferskate kearen minder boarnen ferbrûkt as Istio (mar docht net alles wêr't Istio fan kin opskeppe).
B. Medium - Kubernetes. Wy ynsette en operearje mikrotsjinsten derop.
C. Bottom - bleate metaal. Wy brûke gjin wolken of dingen lykas OpenStack, mar fertrouwe folslein op blank metaal.

Alle lagen wurde kombinearre troch PaaS. En dit platfoarm, op syn beurt, bestiet út trije dielen.

I. Generators, regele fia in CLI-helpprogramma. It is sy dy't de ûntwikkelder helpt om in mikrotsjinst op 'e goede manier en mei in minimum fan ynspanning te meitsjen.

II. Konsolidearre samler mei kontrôle fan alle ark fia in mienskiplik dashboard.

III. Opslach. Ferbynt mei planners dy't automatysk triggers ynstelle foar wichtige aksjes. Troch sa'n systeem wurdt net ien taak mist krekt omdat immen fergetten is in taak yn Jira op te setten. Wy brûke in ynterne ark neamd Atlas foar dit.

Wat witte wy oer mikrotsjinsten

De ymplemintaasje fan mikrotsjinsten yn Avito wurdt ek útfierd neffens ien skema, dy't de kontrôle oer har ferienfâldiget yn elke faze fan ûntwikkeling en frijlitting.

Hoe wurket in standert mikroservice-ûntwikkelingspipeline?

Yn 't algemien sjocht de skeppingsketting fan mikrotsjinsten der sa út:

CLI-push → Continuous Integration → Bake → Deploy → Artificial tests → Canary tests → Squeeze Testing → Production → Underhâld.

Litte wy it krekt yn dizze folchoarder gean.

CLI-druk

• It meitsjen fan in mikroservice.
Wy hawwe in lange tiid muoite om elke ûntwikkelder te learen hoe't jo mikrotsjinsten kinne dwaan. Dit omfette it skriuwen fan detaillearre ynstruksjes yn Confluence. Mar de regelingen feroare en waarden oanfolle. It resultaat is dat der oan it begjin fan 'e reis in knelpunt ferskynde: it duorre folle mear tiid om mikrotsjinsten te lansearjen, en noch altyd ûntstiene problemen by har skepping.

Uteinlik bouden wy in ienfâldich CLI-hulpprogramma dat de basisstappen automatisearret by it meitsjen fan in mikrotsjinst. Yn feite ferfangt it de earste git-push. Hjir is wat se krekt docht.

- Makket in tsjinst oan neffens in sjabloan - stap foar stap, yn "wizard" modus. Wy hawwe sjabloanen foar de wichtichste programmeartalen yn 'e Avito-backend: PHP, Golang en Python.

- Ien kommando tagelyk ynset in omjouwing foar lokale ûntwikkeling op in spesifike masine - Minikube wurdt lansearre, Helm charts wurde automatysk oanmakke en lansearre yn lokale kubernetes.

- Ferbynt de fereaske databank. De ûntwikkelder hoecht de IP, oanmelding en wachtwurd net te witten om tagong te krijen ta de databank dy't hy nedich is - of it no lokaal is, op it poadium, as yn produksje. Boppedat wurdt de databank daliks ynset yn in fouttolerante konfiguraasje en mei balânsjen.

- It fiert sels live gearkomste. Litte wy sizze dat in ûntwikkelder wat korrizjearre hat yn in mikrotsjinst fia syn IDE. It hulpprogramma sjocht feroaringen yn it bestânsysteem en, basearre op har, bout de applikaasje opnij (foar Golang) en opnij starte. Foar PHP stjoere wy gewoan de map yn 'e kubus troch en dêr wurdt live-reload "automatysk" krigen.

- Genereart autotests. Yn 'e foarm fan blanks, mar hiel geskikt foar gebrûk.

• Microservice ynset.

It ynsetten fan in mikrotsjinst wie foar ús in bytsje in karwei. De folgjende wiene ferplicht:

I. Dockerfile.

II. Config.
III. Helmdiagram, dat sels omslachtig is en omfettet:

- de charts sels;
- sjabloanen;
- spesifike wearden rekken hâldend mei ferskate omjouwings.

Wy hawwe de pine út it werwurkjen fan Kubernetes-manifesten helle, sadat se no automatysk wurde generearre. Mar it wichtichste, se ferienfâldige de ynset ta de limyt. Fanôf no hawwe wy in Dockerfile, en de ûntwikkelder skriuwt de hiele konfiguraasje yn ien inkele koarte app.toml-bestân.

Wat witte wy oer mikrotsjinsten

Ja, en yn app.toml sels is d'r in minút neat te dwaan. Wy spesifisearje wêr en hoefolle kopyen fan 'e tsjinst te ferheegjen (op de dev-tsjinner, op staging, yn produksje), en jouwe de ôfhinklikens oan. Merk op de line grutte = "lyts" yn it blok [motor]. Dit is de limyt dy't sil wurde tawiisd oan 'e tsjinst fia Kubernetes.

Dan, basearre op 'e konfiguraasje, wurde alle nedige Helm-charts automatysk oanmakke en wurde ferbiningen mei de databases makke.

• Basic falidaasje. Sokke kontrôles wurde ek automatisearre.
Moatte folgje:
- is der in Dockerfile;
- is der app.toml;
- is der dokumintaasje beskikber?
- is de ôfhinklikens yn oarder?
- oft warskôgingsregels ynsteld binne.
Oan it lêste punt: de eigner fan 'e tsjinst bepaalt sels hokker produktmetriken te kontrolearjen.

• Tarieding fan dokumintaasje.
Noch in probleem gebiet. It liket de meast foar de hân lizzende, mar tagelyk is it ek in rekord "faak fergetten", en dus in kwetsbere skeakel yn 'e keten.
It is needsaaklik dat d'r dokumintaasje is foar elke mikrotsjinst. It omfettet de folgjende blokken.

I. Koarte beskriuwing fan de tsjinst. Letterlik in pear sinnen oer wat it docht en wêrom it nedich is.

II. Arsjitektuer diagram keppeling. It is wichtich dat it mei in rappe blik maklik te begripen is, bygelyks as jo Redis brûke foar caching of as de haadgegevenswinkel yn persistente modus. Yn Avito foar no is dit in keppeling nei Confluence.

III. Runbook. In koarte hantlieding oer it begjinnen fan 'e tsjinst en de kompleksjes fan it behanneljen.

IV. FAQ, wêr't it goed wêze soe om de problemen te antisipearjen dy't jo kollega's kinne tsjinkomme by it wurkjen mei de tsjinst.

V. Beskriuwing fan einpunten foar de API. As jo ​​ynienen de bestimmingen net spesifisearje, sille kollega's waans mikrotsjinsten relatearre binne oan josels der hast wis foar betelje. No brûke wy Swagger en ús oplossing neamd koart foar dit.

VI. Labels. Of markers dy't sjen litte hokker produkt, funksjonaliteit, of strukturele ferdieling fan it bedriuw de tsjinst heart by. Se helpe jo fluch te begripen, bygelyks oft jo funksjonaliteit besunigje dy't jo kollega's in wike lyn foar deselde saaklike ienheid útrôle.

VII. Eigner of eigners fan de tsjinst. Yn 'e measte gefallen kin it - of se - automatysk wurde bepaald mei PaaS, mar om oan 'e feilige kant te wêzen, fereaskje wy dat de ûntwikkelder se manuell spesifisearret.

Uteinlik is it in goede praktyk om dokumintaasje te besjen, fergelykber mei koadebeoardieling.

Continuous integration

  • It tarieden fan repositories.
  • It meitsjen fan in pipeline yn TeamCity.
  • Ynstellingsrjochten.
  • Sykje nei tsjinsteigners. D'r is hjir in hybride skema - hânmjittich markearring en minimale automatisearring fan PaaS. In folslein automatysk skema mislearret as tsjinsten wurde oerdroegen foar stipe oan in oar ûntwikkelingsteam of, bygelyks, as de tsjinstûntwikkelder ophâldt.
  • Registrearje in tsjinst yn Atlas (Sjoch hjir boppe). Mei al syn eigeners en ôfhinklikens.
  • Migraasjes kontrolearje. Wy kontrolearje oft ien fan harren mooglik gefaarlik is. Bygelyks, yn ien fan harren ferskynt in altertabel of wat oars dat de kompatibiliteit fan it gegevensskema tusken ferskate ferzjes fan 'e tsjinst kin brekke. Dan wurdt de migraasje net útfierd, mar pleatst yn in abonnemint - de PaaS moat de tsjinsteigner sinjalearje as it feilich is om it te brûken.

Bakke

De folgjende poadium is ferpakkingstsjinsten foar ynset.

  • It bouwen fan de applikaasje. Neffens de klassiken - yn in Docker-ôfbylding.
  • Generaasje fan Helm charts foar de tsjinst sels en relatearre boarnen. Ynklusyf foar databases en cache. Se wurde automatysk oanmakke yn oerienstimming mei de app.toml-konfiguraasje dy't waard oanmakke yn 'e CLI-push-poadium.
  • Kaartsjes oanmeitsje foar admins om havens te iepenjen (as nedich).
  • Running unit tests en berekkenjen koade dekking. As de koadedekking ûnder de opjûne drompel is, dan sil de tsjinst nei alle gedachten net fierder gean - nei ynset. As it op 'e râne fan akseptabel is, dan sil de tsjinst in "pessimisearjende" koeffizient wurde tawiisd: dan, as d'r gjin ferbettering is yn 'e yndikator oer de tiid, sil de ûntwikkelder in notifikaasje krije dat d'r gjin foarútgong is yn termen fan testen ( en dêr moat wat oan dien wurde).
  • Accounting foar ûnthâld en CPU beheinings. Wy skriuwe benammen mikrotsjinsten yn Golang en rinne se yn Kubernetes. Dêrfandinne ien subtiliteit ferbûn mei de eigenaardichheid fan 'e Golang-taal: standert, by it starten, wurde alle kearnen op' e masine brûkt, as jo de GOMAXPROCS-fariabele net eksplisyt ynstelle, en as ferskate soksoarte tsjinsten wurde lansearre op deselde masine, begjinne se om te konkurrearjen foar middels, interfering mei elkoar. De grafiken hjirûnder litte sjen hoe't de útfieringstiid feroaret as jo de applikaasje útfiere sûnder twifel en yn 'e race for resources-modus. (Boarnen fan grafiken binne hjir).

Wat witte wy oer mikrotsjinsten

Utfiertiid, minder is better. Maksimum: 643ms, minimum: 42ms. De foto is te klikken.

Wat witte wy oer mikrotsjinsten

Tiid foar operaasje, minder is better. Maksimum: 14091 ns, minimum: 151 ns. De foto is te klikken.

By it tarieden fan de gearstalling kinne jo dizze fariabele eksplisyt ynstelle of jo kinne de bibleteek brûke automaxprocs fan de jonges fan Uber.

Ynsette

• Kontrolearje konvinsjes. Foardat jo begjinne mei it leverjen fan tsjinstassemblies nei jo bedoelde omjouwings, moatte jo it folgjende kontrolearje:
- API einpunten.
- Konformiteit fan antwurden fan API-einpunten mei it skema.
- Log opmaak.
- Kopteksten ynstelle foar oanfragen nei de tsjinst (op it stuit wurdt dit dien troch netramesh)
- It eignerstoken ynstelle by it ferstjoeren fan berjochten nei de barrensbus. Dit is nedich om de ferbining fan tsjinsten oer de bus te folgjen. Jo kinne stjoere sawol idempotent gegevens nei de bus, dy't net fergrutsje de ferbining fan tsjinsten (dat is goed), en saaklike gegevens dy't fersterket de ferbining fan tsjinsten (dat is hiel min!). En op it punt dat dizze ferbining in probleem wurdt, helpt begryp wa't de bus skriuwt en lêst om tsjinsten goed te skieden.

D'r binne noch net in protte konvinsjes yn Avito, mar har swimbad wreidet út. Hoe mear sokke ôfspraken beskikber binne yn in foarm dat it team kin begripe en begripe, hoe makliker it is om konsistinsje tusken mikrotsjinsten te behâlden.

Syntetyske tests

• Testing fan sletten loop. Hjirfoar brûke wy no iepen boarne Hoverfly.io. Earst registreart it de echte lading op 'e tsjinst, dan - gewoan yn in sletten lus - it emulearret it.

• Stress Testing. Wy besykje alle tsjinsten op optimale prestaasjes te bringen. En alle ferzjes fan elke tsjinst moatte ûnderwurpen wêze oan loadtesten - op dizze manier kinne wy ​​de hjoeddeistige prestaasjes fan 'e tsjinst en it ferskil mei eardere ferzjes fan deselde tsjinst begripe. As, nei in tsjinst update, syn prestaasjes sakke troch ien en in heale kear, dit is in dúdlik sinjaal foar syn eigners: jo moatte grave yn 'e koade en korrigearje de situaasje.
Wy brûke de sammele gegevens, bygelyks, om automatyske skaalfergrutting korrekt út te fieren en, op it lêst, algemien te begripen hoe skalberber de tsjinst is.

Tidens load testen, wy kontrolearje oft boarne konsumpsje foldocht oan de ynstelde grinzen. En wy rjochtsje ús foaral op ekstremen.

a) Wy sjogge nei de totale lading.
- Te lyts - nei alle gedachten wurket wat net hielendal as de lading ynienen sakket ferskate kearen.
- Te grut - optimalisaasje fereaske.

b) Wy sjogge nei de cutoff neffens RPS.
Hjir sjogge wy nei it ferskil tusken de hjoeddeistige ferzje en de foarige en de totale kwantiteit. Bygelyks, as in tsjinst 100 rps produsearret, dan is it of min skreaun, of dit is syn spesifisiteit, mar yn alle gefallen is dit in reden om de tsjinst tige nau te besjen.
As d'r krekt oarsom te folle RPS binne, dan is d'r miskien in soarte fan bug en guon fan 'e einpunten binne stoppe mei it útfieren fan' e loadload, en ien oare wurdt gewoan trigger return true;

Kanaryske tests

Nei't wy de syntetyske testen hawwe trochjûn, testen wy de mikrotsjinst op in lyts oantal brûkers. Wy begjinne foarsichtich, mei in lyts diel fan it bedoelde publyk fan 'e tsjinst - minder dan 0,1%. Op dit poadium is it heul wichtich dat de juste technyske en produktmetriken binne opnommen yn 'e monitoaring, sadat se it probleem yn' e tsjinst sa rap mooglik sjen litte. De minimale tiid foar in kanaryske test is 5 minuten, de wichtichste is 2 oeren. Foar komplekse tsjinsten sette wy de tiid manuell yn.
Litte wy analysearje:
- taalspesifike metriken, benammen php-fpm-arbeiders;
- flaters yn Sentry;
- antwurdstatussen;
- reaksjetiid, krekte en gemiddelde;
- latency;
- útsûnderingen, ferwurke en net behannele;
- produkt metriken.

Squeeze Testing

Squeeze Testing wurdt ek wol "squeeze" testen neamd. De namme fan 'e technyk waard yntrodusearre yn Netflix. De essinsje is dat wy earst ien eksimplaar folje mei echte ferkear oant it punt fan mislearring en sa syn limyt ynstelle. Dan foegje wy in oare eksimplaar ta en laden dit pear - wer nei it maksimum; wy sjogge harren plafond en delta mei de earste "squeeze". En sa ferbine wy ​​ien eksimplaar tagelyk en berekkenje it patroan fan feroaringen.
Testgegevens troch "squeezing" streamt ek yn in mienskiplike metrike databank, wêr't wy de resultaten fan 'e keunstmjittige lading dermei ferrykje, of sels "synthetics" ferfange mei har.

Produksje

• Skaalfergrutting. As wy in tsjinst útrolje nei produksje, kontrolearje wy hoe't it skalen. Yn ús ûnderfining is it kontrolearjen fan allinich CPU-yndikatoaren net effektyf. Automatysk skaalfergrutting mei RPS-benchmarking yn syn suvere foarm wurket, mar allinich foar bepaalde tsjinsten, lykas online streaming. Dat wy sjogge earst nei applikaasje-spesifike produktmetriken.

As gefolch, by skaalfergrutting analysearje wy:
- CPU- en RAM-yndikatoaren,
- it oantal oanfragen yn 'e wachtrige,
- reaksjetiid,
- prognose basearre op opboude histoaryske gegevens.

By it skaaljen fan in tsjinst is it ek wichtich om de ôfhinklikens te kontrolearjen, sadat wy de earste tsjinst yn 'e keten net skaalje, en dejingen dy't it tagong krije, mislearje ûnder load. Om in akseptabele lading foar de hiele pool fan tsjinsten te fêstigjen, sjogge wy nei de histoaryske gegevens fan 'e "neistste" ôfhinklike tsjinst (basearre op in kombinaasje fan CPU- en RAM-yndikatoaren, keppele mei app-spesifike metriken) en fergelykje se mei de histoaryske gegevens fan 'e inisjalisearjende tsjinst, ensafuorthinne troch de heule "ôfhinklike keten" ", fan boppe nei ûnderen.

Tsjinst

Neidat de mikrotsjinst yn wurking is, kinne wy ​​der triggers oan heakje.

Hjir binne typyske situaasjes wêryn triggers foarkomme.
- Potinsjeel gefaarlike migraasjes ûntdutsen.
- Feiligensupdates binne frijjûn.
- De tsjinst sels is in lange tiid net bywurke.
- De lading op 'e tsjinst is merkber fermindere of guon fan har produktmetriken binne bûten it normale berik.
- De tsjinst foldocht net mear oan de nije platfoarmeasken.

Guon fan 'e triggers binne ferantwurdlik foar stabiliteit fan' e operaasje, guon - as funksje fan systeemûnderhâld - bygelyks, guon tsjinst is net ynset foar in lange tiid en syn basis ôfbylding is ophâlden te trochjaan feiligens kontrôles.

Dashboard

Koartsein, it dashboard is it kontrôlepaniel fan ús hiele PaaS.

  • In inkeld punt fan ynformaasje oer de tsjinst, mei gegevens oer de testdekking, it oantal ôfbyldings, it oantal produksjekopyen, ferzjes, ensfh.
  • In ark foar it filterjen fan gegevens troch tsjinsten en labels (markers fan hearren ta bedriuwseenheden, produktfunksjonaliteit, ensfh.)
  • In ark foar yntegraasje mei ynfrastruktuer ark foar tracing, logging, en tafersjoch.
  • In inkeld punt fan tsjinst dokumintaasje.
  • Ien eachpunt fan alle eveneminten oer tsjinsten.

Wat witte wy oer mikrotsjinsten
Wat witte wy oer mikrotsjinsten
Wat witte wy oer mikrotsjinsten
Wat witte wy oer mikrotsjinsten

Totaal

Foardat PaaS yntrodusearre, koe in nije ûntwikkelder ferskate wiken besteegje oan it begripen fan alle ark dy't nedich binne om in mikrotsjinst yn produksje te starten: Kubernetes, Helm, ús ynterne TeamCity-funksjes, it opsetten fan ferbiningen mei databases en caches op in fouttolerante manier, ensfh. duorret in pear oeren om de fluchstart te lêzen en de tsjinst sels te meitsjen.

Ik joech in rapport oer dit ûnderwerp foar HighLoad ++ 2018, jo kinne it besjen видео и presintaasje.

Bonusspor foar dyjingen dy't lêze oant de ein

Wy by Avito organisearje in ynterne trije-dagen training foar ûntwikkelers út Chris Richardson, in ekspert yn mikroservicearsjitektuer. Wy wolle ien fan de lêzers fan dit berjocht de kâns jaan om dêr oan mei te dwaan. it is It trainingsprogramma is pleatst.

De oplieding fynt plak fan 5 oant en mei 7 augustus yn Moskou. It binne wurkdagen dy't folslein beset wurde. Lunch en training sil wêze yn ús kantoar, en de selektearre dielnimmer sil betelje foar reis en akkomodaasje sels.

Jo kinne oanfreegje foar dielname yn dit Google-formulier. Fan jo - it antwurd op 'e fraach wêrom't jo de training moatte bywenje en ynformaasje oer hoe't jo kontakt mei jo kinne. Antwurdzje yn it Ingelsk, want Chris kiest sels de dielnimmer dy't de training folgje sil.
Wy sille de namme fan 'e trainingsdielnimmer oankundigje yn in update foar dizze post en op sosjale netwurken Avito foar ûntwikkelders (AvitoTech yn Facebook, Vkontakte, Twitter) net letter as 19 july.

Boarne: www.habr.com

Add a comment