Mit tudunk a mikroszolgáltatásokról?

Helló! A nevem Vadim Madison, én vezetem az Avito System Platform fejlesztését. Nem egyszer elhangzott már, hogy mi a cégnél hogyan lépünk át a monolitikus architektúrából a mikroszolgáltatások felé. Itt az ideje, hogy megosszuk, hogyan alakítottuk át infrastruktúránkat annak érdekében, hogy a legtöbbet hozzuk ki a mikroszolgáltatásokból, és elkerüljük, hogy elveszünk bennük. Hogyan segít nekünk itt a PaaS, hogyan egyszerűsítettük az üzembe helyezést és egy kattintásra csökkentettük a mikroszolgáltatás létrehozását - olvass tovább. Nem minden, amiről alább írok, teljesen implementálva az Avitóban, részben az, ahogyan fejlesztjük platformunkat.

(A cikk végén pedig arról a lehetőségről fogok beszélni, hogy részt vehetek egy háromnapos szemináriumon Chris Richardson mikroszolgáltatás-építészeti szakértőtől).

Mit tudunk a mikroszolgáltatásokról?

Hogyan jutottunk el a mikroszolgáltatásokhoz

Az Avito a világ egyik legnagyobb apróhirdetési oldala, naponta több mint 15 millió új hirdetés jelenik meg rajta. Háttérrendszerünk másodpercenként több mint 20 ezer kérést fogad el. Jelenleg több száz mikroszolgáltatással rendelkezünk.

Már több éve építünk mikroszolgáltatási architektúrát. Hogy pontosan hogyan - kollégáink részletesen mondta a RIT++ 2017-es szekciónkon. A CodeFest 2017-en (lásd. videó), Sergey Orlov és Mihail Prokopchuk részletesen elmagyarázta, miért volt szükségünk a mikroszolgáltatásokra való átállásra, és milyen szerepet játszott itt a Kubernetes. Nos, most mindent megteszünk annak érdekében, hogy minimalizáljuk az ilyen architektúrával járó skálázási költségeket.

Kezdetben nem hoztunk létre egy olyan ökoszisztémát, amely átfogóan segítene mikroszolgáltatások fejlesztésében és beindításában. Egyszerűen összegyűjtötték az értelmes nyílt forráskódú megoldásokat, elindították otthon, és felkérték a fejlesztőt, hogy foglalkozzon velük. Ennek eredményeként egy tucat helyre ment (műszerfalak, belső szolgáltatások), ami után megerősödött benne a vágy, hogy a régi módon, monolitban vágja le a kódot. Az alábbi ábrákon a zöld szín azt jelzi, hogy a fejlesztő így vagy úgy mit csinál a saját kezével, a sárga szín pedig az automatizálást.

Mit tudunk a mikroszolgáltatásokról?

Most a PaaS CLI segédprogramban egy új szolgáltatás jön létre egy paranccsal, és egy új adatbázist adnak hozzá még kettővel, és telepítik a Stage-re.

Mit tudunk a mikroszolgáltatásokról?

Hogyan lehet leküzdeni a „mikroszolgáltatások töredezettségének” korszakát?

A monolitikus architektúra miatt a termék változásainak következetessége érdekében a fejlesztők kénytelenek voltak kitalálni, mi történik a szomszédaikkal. Amikor az új architektúrán dolgozunk, a szolgáltatási kontextusok többé nem függenek egymástól.

Ezenkívül a mikroszolgáltatási architektúra hatékony működéséhez számos folyamatot kell létrehozni, nevezetesen:

• fakitermelés;
• nyomon követés kérése (Jaeger);
• hibaösszesítés (Sentry);
• állapotok, üzenetek, események a Kubernetestől (Event Stream Processing);
• versenykorlát / megszakító (használhatja a Hystrixet);
• szolgáltatási kapcsolat vezérlése (Netramesh-t használunk);
• monitorozás (Grafana);
• összeszerelés (TeamCity);
• kommunikáció és értesítés (Slack, email);
• feladatkövetés; (Jira)
• dokumentáció elkészítése.

Annak érdekében, hogy a rendszer ne veszítse el integritását, és a méretezés során hatékony maradjon, újragondoltuk a mikroszolgáltatások megszervezését az Avitóban.

Hogyan kezeljük a mikroszolgáltatásokat

Az alábbiak segítik az egységes „pártpolitika” megvalósítását számos Avito mikroszolgáltatás között:

  • az infrastruktúra rétegekre osztása;
  • Platform as a Service (PaaS) koncepció;
  • figyelemmel kísér mindent, ami a mikroszolgáltatásokkal történik.

Az infrastruktúra absztrakciós rétegei három rétegből állnak. Menjünk fentről lefelé.

A. Top - szervizháló. Először az Istióval próbálkoztunk, de kiderült, hogy túl sok erőforrást használ fel, ami túl drága a köteteinkhez képest. Ezért az építészeti csapat vezető mérnöke, Alekszandr Lukjancsenko saját megoldást fejlesztett ki - Netramesh (nyílt forráskódú), amelyet jelenleg termelésben használunk, és amely többszörösen kevesebb erőforrást fogyaszt, mint az Istio (de nem tesz meg mindent, amivel az Istio büszkélkedhet).
B. Közepes - Kubernetes. Mikroszolgáltatásokat telepítünk és működtetünk rajta.
C. Alul - csupasz fém. Nem használunk felhőket vagy olyan dolgokat, mint az OpenStack, hanem teljes mértékben a csupasz fémre támaszkodunk.

Az összes réteget a PaaS kombinálja. Ez a platform pedig három részből áll.

I. Generátorok, CLI segédprogramon keresztül vezérelhető. Ő az, aki segít a fejlesztőnek mikroszolgáltatást a megfelelő módon és minimális erőfeszítéssel létrehozni.

II. Összevont gyűjtő az összes eszköz vezérlésével egy közös műszerfalon keresztül.

III. Tárolás. Kapcsolatba lép az ütemezőkkel, amelyek automatikusan beállítják a jelentősebb műveletek eseményindítóit. Egy ilyen rendszernek köszönhetően egyetlen feladat sem marad el csak azért, mert valaki elfelejtett beállítani egy feladatot Jira-ban. Ehhez az Atlas nevű belső eszközt használjuk.

Mit tudunk a mikroszolgáltatásokról?

A mikroszolgáltatások megvalósítása az Avito-ban is egyetlen séma szerint történik, ami leegyszerűsíti a vezérlést a fejlesztés és a kiadás minden szakaszában.

Hogyan működik egy szabványos mikroszolgáltatás-fejlesztési folyamat?

Általában a mikroszolgáltatás-létrehozási lánc így néz ki:

CLI-push → Folyamatos integráció → Sütés → Telepítés → Mesterséges tesztek → Kanári tesztek → Nyomási teszt → Gyártás → Karbantartás.

Menjünk végig pontosan ebben a sorrendben.

CLI-push

• Mikroszolgáltatás létrehozása.
Sokáig küzdöttünk azért, hogy minden fejlesztőt megtanítsunk a mikroszolgáltatások elvégzésére. Ez magában foglalta a részletes utasítások megírását a Confluence-ben. De a sémák megváltoztak és kiegészítésre kerültek. Ennek eredményeként az út elején megjelent egy szűk keresztmetszet: sokkal több időbe telt a mikroszolgáltatások elindítása, és ennek ellenére gyakran adódnak problémák létrehozásuk során.

Végül egy egyszerű CLI segédprogramot építettünk, amely automatizálja a mikroszolgáltatások létrehozásának alapvető lépéseit. Valójában ez helyettesíti az első git push-ot. Íme, pontosan mit csinál.

— Szolgáltatást hoz létre egy sablon szerint – lépésről lépésre, „varázsló” módban. Sablonjaink vannak az Avito backend fő programozási nyelveihez: PHP, Golang és Python.

- Egyszerre egy parancs telepít egy környezetet a helyi fejlesztéshez egy adott gépen - A Minikube elindul, a Helm diagramok automatikusan generálódnak és elindulnak a helyi kubernetesben.

— Összekapcsolja a szükséges adatbázist. A fejlesztőnek nem kell ismernie az IP-címet, a bejelentkezési nevet és a jelszót, hogy hozzáférjen a számára szükséges adatbázishoz – legyen az helyi, Stage vagy éles. Ezenkívül az adatbázis hibatűrő konfigurációban és kiegyensúlyozottan azonnal üzembe kerül.

— Maga végzi az élő szerelést. Tegyük fel, hogy egy fejlesztő javított valamit egy mikroszolgáltatásban az IDE-jén keresztül. A segédprogram látja a fájlrendszer változásait, és ezek alapján újraépíti az alkalmazást (a Golanghoz), majd újraindul. PHP esetén egyszerűen továbbítjuk a kocka belsejében lévő könyvtárat, és ott a live-reload „automatikusan” megtörténik.

— Automatikus teszteket generál. Nyersdarabok formájában, de nagyon alkalmas a használatra.

• Mikroszolgáltatás telepítése.

A mikroszolgáltatás telepítése korábban egy kis meló volt számunkra. A következőkre volt szükség:

I. Dockerfile.

II. Konfig.
III. Helm chart, amely maga is nehézkes, és a következőket tartalmazza:

— maguk a diagramok;
— sablonok;
— meghatározott értékek, figyelembe véve a különböző környezeteket.

Megszüntettük a Kubernetes manifesztek átdolgozásából adódó fájdalmat, így a rendszer mostantól automatikusan generálja őket. De ami a legfontosabb, a végsőkig leegyszerűsítették a telepítést. Mostantól van egy Dockerfile, és a fejlesztő a teljes konfigurációt egyetlen rövid app.toml fájlba írja.

Mit tudunk a mikroszolgáltatásokról?

Igen, és magában az app.toml-ben egy percig nincs mit tenni. Meghatározzuk, hogy a szolgáltatás hol és hány példányban készüljön el (a fejlesztői szerveren, a stádiumban, a termelésben), és jelezzük a függőségeit. Figyelje meg, hogy a vonalméret = "small" a [motor] blokkban. Ez az a korlát, amely a Kubernetesen keresztül lesz hozzárendelve a szolgáltatáshoz.

Ezután a konfiguráció alapján automatikusan létrejön az összes szükséges Helm diagram, és létrejön a kapcsolatok az adatbázisokkal.

• Alapvető érvényesítés. Az ilyen ellenőrzések szintén automatizáltak.
Követni kell:
— van-e Dockerfile;
— van app.toml;
– rendelkezésre áll-e dokumentáció?
- rendben van a függőség?
— meghatározták-e a riasztási szabályokat.
Az utolsó pontig: a szolgáltatás tulajdonosa maga határozza meg, hogy mely termékmutatókat kell figyelni.

• Dokumentáció elkészítése.
Továbbra is problémás terület. Ez tűnik a legkézenfekvőbbnek, ugyanakkor egyben „gyakran elfelejtett” rekord is, ezért a lánc sérülékeny láncszeme.
Szükséges, hogy minden mikroszolgáltatáshoz rendelkezzen dokumentáció. A következő blokkokat tartalmazza.

I. A szolgáltatás rövid leírása. Szó szerint néhány mondat arról, hogy mit csinál, és miért van rá szükség.

II. Építészeti diagram hivatkozás. Fontos, hogy egy gyors pillantással könnyen megérthető legyen például, hogy a Redis-t gyorsítótárazásra vagy fő adattárként használja-e állandó módban. Az Avitóban egyelőre ez egy link a Confluence-hez.

III. Runbook. Rövid útmutató a szolgáltatás elindításához és kezelésének fortélyaihoz.

IV. GYIK, ahol jó lenne előre látni azokat a problémákat, amelyekkel kollégái találkozhatnak a szolgáltatással való munka során.

V. Az API végpontjainak leírása. Ha hirtelen nem adta meg a desztinációkat, akkor szinte biztosan fizetni fognak érte azok a kollégák, akiknek mikroszolgáltatásai az Önéhez kapcsolódnak. Most a Swaggert és a short nevű megoldásunkat használjuk erre.

VI. Címkék. Vagy jelzők, amelyek megmutatják, hogy a szolgáltatás melyik termékhez, funkcionalitáshoz vagy a vállalat strukturális részlegéhez tartozik. Segítenek például gyorsan megérteni, hogy levágja-e azt a funkcionalitást, amelyet kollégái egy héttel ezelőtt bevezettek ugyanahhoz az üzleti egységhez.

VII. A szolgáltatás tulajdonosa vagy tulajdonosai. A legtöbb esetben ez – vagy azok – automatikusan meghatározhatók a PaaS segítségével, de a biztonság kedvéért megköveteljük, hogy a fejlesztő kézzel adja meg őket.

Végül bevált gyakorlat a dokumentáció áttekintése, hasonlóan a kódellenőrzéshez.

Folyamatos integráció

  • Adattárak előkészítése.
  • Csővezeték létrehozása a TeamCityben.
  • Jogok beállítása.
  • Keressen szolgáltatástulajdonosokat. Itt van egy hibrid séma - kézi jelölés és minimális automatizálás a PaaS-től. A teljesen automatikus séma meghiúsul, ha a szolgáltatásokat egy másik fejlesztőcsapathoz támogatás céljából átadják, vagy például ha a szolgáltatásfejlesztő kilép.
  • Szolgáltatás regisztrálása az Atlasban (lásd fent). Minden tulajdonosával és függőségével együtt.
  • A migráció ellenőrzése. Ellenőrizzük, hogy ezek közül valamelyik potenciálisan veszélyes-e. Például az egyikben felbukkan egy alter tábla vagy valami más, ami megszakíthatja az adatséma kompatibilitását a szolgáltatás különböző verziói között. Ekkor a migrációt nem hajtják végre, hanem előfizetésben helyezik el – a PaaS-nek jeleznie kell a szolgáltatás tulajdonosának, ha biztonságos a használata.

Süt

A következő lépés a csomagolási szolgáltatások bevezetése előtt.

  • Az alkalmazás felépítése. A klasszikusok szerint - Docker képben.
  • Helm diagramok generálása magának a szolgáltatásnak és a kapcsolódó erőforrásoknak. Beleértve az adatbázisokat és a gyorsítótárat. A rendszer automatikusan létrehozza őket a CLI-push szakaszban előállított app.toml konfigurációval összhangban.
  • Jegyek létrehozása a rendszergazdáknak a portok megnyitásához (amikor szükséges).
  • Egységtesztek futtatása és kódlefedettség kiszámítása. Ha a kód lefedettsége a megadott küszöb alatt van, akkor valószínűleg a szolgáltatás nem megy tovább - a telepítésig. Ha az elfogadható határán van, akkor a szolgáltatáshoz „pesszimista” együtthatót rendelnek: ekkor, ha az idő múlásával nem javul a mutató, a fejlesztő értesítést kap arról, hogy a tesztek terén nincs előrelépés ( és valamit tenni kell ellene).
  • Figyelembe veszi a memória és a CPU korlátait. Főleg Golang nyelven írunk mikroszolgáltatásokat, és Kubernetesben futtatjuk. Ebből adódóan a Golang nyelv sajátosságához köthető egy finomság: alapértelmezés szerint indításkor a gép összes magját felhasználja, ha nem állítja be kifejezetten a GOMAXPROCS változót, és amikor több ilyen szolgáltatás indul ugyanazon a gépen, akkor elindul. versenyezni az erőforrásokért, egymást zavarva. Az alábbi grafikonok azt mutatják, hogyan változik a végrehajtási idő, ha az alkalmazást vita nélkül és az erőforrásokért való verseny módban futtatja. (A grafikonok forrásai: itt).

Mit tudunk a mikroszolgáltatásokról?

Végrehajtási idő, a kevesebb jobb. Maximum: 643 ms, minimum: 42 ms. A fotó kattintható.

Mit tudunk a mikroszolgáltatásokról?

Ideje a műtétnek, a kevesebb jobb. Maximum: 14091 ns, minimum: 151 ns. A fotó kattintható.

Az összeállítás előkészítésének szakaszában ezt a változót kifejezetten beállíthatja, vagy használhatja a könyvtárat automaxprocs az Uber srácaitól.

Telepítés

• Konvenciók ellenőrzése. Mielőtt elkezdené a szervizszerelvények szállítását a tervezett környezetbe, ellenőriznie kell a következőket:
- API-végpontok.
— Az API-végpontok válaszainak megfelelősége a sémának.
— Naplóformátum.
- Fejlécek beállítása a szolgáltatásra irányuló kérésekhez (jelenleg a netramesh végzi el)
— A tulajdonos token beállítása üzenetek küldésekor az eseménybuszra. Ez szükséges a szolgáltatások összeköttetésének nyomon követéséhez a buszon keresztül. Idempotens adatokat is küldhetünk a buszra, ami nem növeli a szolgáltatások összekapcsolhatóságát (ami jó), és üzleti adatokat is, amelyek erősítik a szolgáltatások összekapcsolhatóságát (ami nagyon rossz!). És amikor ez a kapcsolat problémássá válik, annak megértése, hogy ki írja és olvassa a buszt, segít a szolgáltatások megfelelő elkülönítésében.

Avitóban még nincs túl sok kongresszus, de a medencéjük bővül. Minél több ilyen megállapodás érhető el a csapat számára érthető és érthető formában, annál könnyebben fenntartható a mikroszolgáltatások közötti konzisztencia.

Szintetikus tesztek

• Zárt hurkú tesztelés. Ehhez most nyílt forráskódot használunk Hoverfly.io. Először rögzíti a szolgáltatás valós terhelését, majd - csak zárt ciklusban - emulálja azt.

• Stresszteszt. Igyekszünk minden szolgáltatást optimális teljesítményre hozni. És minden szolgáltatás minden verzióját terhelési tesztelésnek kell alávetni - így megérthetjük a szolgáltatás jelenlegi teljesítményét és a különbséget ugyanazon szolgáltatás korábbi verzióihoz képest. Ha egy szervizfrissítés után a teljesítménye másfélszeresére esett, ez egyértelmű jelzés a tulajdonosok számára: bele kell ásni a kódot és ki kell javítani a helyzetet.
Az összegyűjtött adatokat például arra használjuk, hogy helyesen hajtsuk végre az automatikus skálázást, és végül általában megértsük, mennyire méretezhető a szolgáltatás.

A terhelési tesztelés során ellenőrizzük, hogy az erőforrás-felhasználás megfelel-e a beállított határértékeknek. Mi pedig elsősorban a szélsőségekre koncentrálunk.

a) Nézzük a teljes terhelést.
- Túl kicsi - valószínűleg valami egyáltalán nem működik, ha a terhelés hirtelen többször esik le.
- Túl nagy - optimalizálás szükséges.

b) Nézzük az RPS szerinti határértéket.
Itt megnézzük a jelenlegi és az előző verzió közötti különbséget és a teljes mennyiséget. Például, ha egy szolgáltatás 100 rps-t produkál, akkor vagy rosszul van megírva, vagy ez a sajátossága, de mindenesetre ez ok arra, hogy alaposan megvizsgáljuk a szolgáltatást.
Ha éppen ellenkezőleg, túl sok az RPS, akkor lehet, hogy valami hiba van, és néhány végpont leállította a hasznos terhelés végrehajtását, és egy másik egyszerűen kivált. return true;

Kanári tesztek

Miután átmentünk a szintetikus teszteken, kis számú felhasználón teszteljük a mikroszolgáltatást. Óvatosan kezdünk, a szolgáltatás célközönségének csekély részével – kevesebb, mint 0,1%. Ebben a szakaszban nagyon fontos, hogy a megfelelő műszaki és termékmérőszámok bekerüljenek a monitorozásba, hogy azok a lehető leggyorsabban mutassák a problémát a szolgáltatásban. A kanári teszt minimális ideje 5 perc, a fő 2 óra. Komplex szolgáltatások esetén manuálisan állítjuk be az időt.
Elemezzük:
— nyelvspecifikus mérőszámok, különösen a php-fpm dolgozók;
— hibák a Sentry-ben;
— válaszállapotok;
— pontos és átlagos válaszidő;
- késleltetés;
— kivételek, feldolgozott és kezeletlen;
— termékmutatók.

Préselési tesztelés

A préselési tesztelést „préselési” tesztelésnek is nevezik. A technika nevét bevezették a Netflixbe. Lényege, hogy először egy példányt töltünk meg valós forgalommal a meghibásodásig, és így állítjuk be a határát. Ezután hozzáadunk egy másik példányt, és betöltjük ezt a párt - ismét a maximumig; mennyezetüket és deltájukat látjuk az első „szorítással”. Így egy-egy példányt csatlakoztatunk, és kiszámítjuk a változások mintáját.
A tesztadatok a „sajtolással” egy közös mérőszám-adatbázisba is befolynak, ahol vagy gazdagítjuk velük a mesterséges terhelési eredményeket, vagy akár a „műanyagot” is helyettesítjük velük.

Termelés

• Méretezés. Amikor egy szolgáltatást élesítünk, figyelemmel kísérjük, hogyan méreteződik. Tapasztalataink szerint csak a CPU indikátorok figyelése nem hatékony. Az automatikus méretezés az RPS benchmarking segítségével tiszta formájában működik, de csak bizonyos szolgáltatásoknál, például az online streamingnél. Tehát először az alkalmazás-specifikus termékmutatókat nézzük.

Ennek eredményeként a méretezés során a következőket elemezzük:
- CPU és RAM kijelzők,
— a sorban lévő kérelmek száma,
- válaszidő,
— előrejelzés a felhalmozott múltbeli adatokon.

Egy szolgáltatás méretezésekor fontos figyelni a függőségeit is, hogy ne a lánc első szolgáltatását méretezzük, és azok, amelyekhez hozzáfér, terhelés alatt meghibásodjanak. A teljes szolgáltatáskészlet elfogadható terhelésének megállapításához megvizsgáljuk a „legközelebbi” függő szolgáltatás előzményadatait (a CPU és RAM indikátorok kombinációja alapján, alkalmazás-specifikus mérőszámokkal párosítva), és összehasonlítjuk azokat a korábbi adatokkal. az inicializálási szolgáltatás, és így tovább a „függőségi láncon” keresztül, fentről lefelé.

szolgáltatás

A mikroszolgáltatás üzembe helyezése után triggereket rögzíthetünk rá.

Íme tipikus helyzetek, amelyekben triggerek fordulnak elő.
— Potenciálisan veszélyes migrációt észleltek.
— Biztonsági frissítések jelentek meg.
— Maga a szolgáltatás már régóta nem frissült.
— A szolgáltatás terhelése érezhetően csökkent, vagy egyes termékmutatói kívül esnek a normál tartományon.
— A szolgáltatás már nem felel meg az új platformkövetelményeknek.

A triggerek egy része a működés stabilitásáért felelős, van, amelyik - a rendszerkarbantartás függvényében - például néhány szolgáltatást hosszabb ideje nem telepítettek, és annak alapképe már nem megy át a biztonsági ellenőrzéseken.

Vezérlőpult

Röviden, a műszerfal a teljes PaaS-ünk vezérlőpultja.

  • Egyetlen információs pont a szolgáltatásról, a teszt lefedettségével, képeinek számával, gyártott példányszámával, verzióival stb.
  • Eszköz az adatok szolgáltatások és címkék (üzleti egységekhez való tartozás jelzői, termékfunkciók stb.) szerinti szűrésére.
  • Eszköz a nyomkövetési, naplózási és megfigyelési infrastrukturális eszközökkel való integrációhoz.
  • Egypontos szervizdokumentáció.
  • Egyetlen nézőpont a szolgáltatások összes eseményéről.

Mit tudunk a mikroszolgáltatásokról?
Mit tudunk a mikroszolgáltatásokról?
Mit tudunk a mikroszolgáltatásokról?
Mit tudunk a mikroszolgáltatásokról?

Összességében

A PaaS bevezetése előtt egy új fejlesztő több hetet tölthet azzal, hogy megértse a mikroszolgáltatások éles üzembe helyezéséhez szükséges összes eszközt: a Kubernetes, a Helm, a belső TeamCity szolgáltatásaink, az adatbázisokhoz és a gyorsítótárakhoz való kapcsolatok hibatűrő beállítása stb. néhány órát vesz igénybe a gyors útmutató elolvasása és magának a szolgáltatásnak a létrehozása.

Ebben a témában a HighLoad++ 2018-hoz adtam beszámolót, megtekinthetitek videó и előadás.

Bónusz szám azoknak, akik a végéig elolvassák

Mi az Avitóban három napos belső tréninget szervezünk fejlesztőknek től Chris Richardson, a mikroszolgáltatási architektúra szakértője. A bejegyzés egyik olvasójának szeretnénk lehetőséget adni a részvételre. Itt A képzési program közzétételre került.

A képzés augusztus 5. és 7. között lesz Moszkvában. Ezek teljesen lefoglalt munkanapok. Az ebéd és a képzés az irodánkban lesz, az utazást és a szállást a kiválasztott résztvevő maga fizeti.

Jelentkezni lehet ezen a google űrlapon. Tőled - a válasz arra a kérdésre, hogy miért kell részt vennie a képzésen, és tájékoztatás a kapcsolatfelvétel módjáról. Angolul válaszolj, mert Chris maga választja ki azt a résztvevőt, aki részt vesz a képzésen.
A tréning résztvevőjének nevét a bejegyzés frissítésében és az Avito fejlesztőknek közösségi hálózatokon közöljük (AvitoTech in Facebook, Vkontakte, Twitter) legkésőbb július 19-ig.

Forrás: will.com

Hozzászólás