Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Na RIT 2019 je naredil naš kolega Alexander Korotkov poročilo o avtomatizaciji razvoja v CIAN: za poenostavitev življenja in dela uporabljamo lastno platformo Integro. Sledi življenjskemu ciklu opravil, razbremeni razvijalce rutinskih operacij in bistveno zmanjša število hroščev v proizvodnji. V tej objavi bomo dopolnili Alexanderovo poročilo in vam povedali, kako smo prešli s preprostih skriptov na združevanje odprtokodnih izdelkov prek naše lastne platforme in kaj počne naša ločena ekipa za avtomatizacijo.
 

Ničelna raven

"Ničla raven ne obstaja, jaz tega ne poznam"
Mojster Shifu iz filma "Kung Fu Panda"

Avtomatizacija v CIAN se je začela 14 let po ustanovitvi podjetja. Takrat je bilo v razvojni ekipi 35 ljudi. Težko je verjeti, kajne? Seveda je avtomatizacija obstajala v neki obliki, vendar se je leta 2015 začela oblikovati ločena smer za stalno integracijo in dostavo kode. 

Takrat smo imeli ogromen monolit Pythona, C# in PHP, nameščenega na strežnikih Linux/Windows. Za uvedbo te pošasti smo imeli niz skriptov, ki smo jih izvajali ročno. Prišlo je tudi do sestavljanja monolita, ki je prineslo bolečino in trpljenje zaradi konfliktov pri združevanju vej, popravljanju napak in obnovi »z drugačnim naborom nalog v gradnji«. Poenostavljen postopek je izgledal takole:

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

S tem nismo bili zadovoljni in želeli smo zgraditi ponovljiv, avtomatiziran in obvladljiv postopek gradnje in uvajanja. Za to smo potrebovali sistem CI/CD, izbirali pa smo med brezplačno različico Teamcityja in brezplačno različico Jenkinsa, saj smo delali z njima in sta nam obe ustrezali po naboru funkcij. Kot novejši izdelek smo izbrali Teamcity. Takrat še nismo uporabljali mikrostoritvene arhitekture in nismo pričakovali velikega števila nalog in projektov.

Prišli smo do ideje lastnega sistema

Izvedba Teamcityja je odstranila le del ročnega dela: ostalo je ustvarjanje zahtev za vlečenje, promocija težav po statusu v Jiri in izbira težav za objavo. Sistem Teamcity temu ni bil več kos. Treba je bilo izbrati pot nadaljnje avtomatizacije. Upoštevali smo možnosti za delo s skripti v Teamcityju ali prehod na sisteme za avtomatizacijo tretjih oseb. A na koncu smo se odločili, da potrebujemo maksimalno fleksibilnost, ki jo lahko zagotovi le lastna rešitev. Tako se je pojavila prva različica sistema interne avtomatizacije z imenom Integro.

Teamcity se ukvarja z avtomatizacijo na ravni zagona procesov gradnje in uvajanja, medtem ko se je Integro osredotočil na vrhunsko avtomatizacijo razvojnih procesov. Delo s težavami v Jiri je bilo treba združiti z obdelavo povezane izvorne kode v Bitbucketu. Na tej stopnji je Integro začel imeti lastne poteke dela za delo z nalogami različnih vrst. 

Zaradi vse večje avtomatizacije poslovnih procesov se je povečalo število projektov in zagonov v Teamcityju. Tako se je pojavila nova težava: ena brezplačna instanca Teamcity ni bila dovolj (3 agenti in 100 projektov), ​​dodali smo še eno instanco (še 3 agenti in 100 projektov), ​​nato še eno. Posledično smo dobili sistem večih grozdov, ki ga je bilo težko upravljati:

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Ko se je pojavilo vprašanje 4. stopnje, smo ugotovili, da tako ne moremo več živeti, saj skupni stroški vzdrževanja 4. stopnje niso bili več v nobenih mejah. Pojavilo se je vprašanje o nakupu plačanega Teamcityja ali izbiri brezplačnega Jenkinsa. Naredili smo izračune na primerkih in načrtih avtomatizacije ter se odločili, da bomo živeli na Jenkinsu. Po nekaj tednih smo prešli na Jenkins in odpravili nekaj glavobola, povezanega z vzdrževanjem več primerkov Teamcity. Zato smo se lahko osredotočili na razvoj Integra in prilagajanje Jenkinsa sebi.

Z rastjo osnovne avtomatizacije (v obliki samodejnega ustvarjanja zahtev za vlečenje, zbiranja in objave pokritosti kode in drugih preverjanj) obstaja močna želja, da bi čim bolj opustili ročne izdaje in to delo prepustili robotom. Poleg tega se je podjetje začelo seliti na mikrostoritve znotraj podjetja, ki so zahtevale pogoste izdaje in ločeno ena od druge. Tako smo postopoma prišli do samodejnih izdaj naših mikrostoritev (trenutno izdajamo monolit ročno zaradi zahtevnosti procesa). Toda, kot se običajno zgodi, se je pojavila nova zapletenost. 

Avtomatiziramo testiranje

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Zaradi avtomatizacije izdaj so se razvojni procesi pospešili, delno tudi zaradi preskoka nekaterih faz testiranja. In to je povzročilo začasno izgubo kakovosti. Sliši se nepomembno, a skupaj s pospeševanjem izdaj je bilo treba spremeniti metodologijo razvoja izdelkov. Treba je bilo razmišljati o avtomatizaciji testiranja, vcepljanju osebne odgovornosti (tukaj govorimo o »sprejemanju ideje v glavo«, ne o denarnih kaznih) razvijalca za izdano kodo in napake v njej, pa tudi o odločitvi za sprostitev/ne sprostitev naloge prek samodejne uvedbe. 

Z odpravo težav s kakovostjo smo prišli do dveh pomembnih odločitev: začeli smo izvajati kanarsko testiranje in uvedli avtomatsko spremljanje ozadja napake s samodejnim odzivom na njen presežek. Prva rešitev je omogočila iskanje očitnih napak, preden je bila koda v celoti sproščena v produkcijo, druga je skrajšala odzivni čas na težave v produkciji. Napake se seveda dogajajo, vendar večino časa in truda porabimo ne za njihovo popravljanje, ampak za njihovo zmanjšanje. 

Ekipa za avtomatizacijo

Trenutno imamo 130 zaposlenih razvijalcev in nadaljujemo rastejo. Ekipo za kontinuirano integracijo in dostavo kode (v nadaljevanju Deploy and Integration ali DI team) sestavlja 7 ljudi in deluje v 2 smereh: razvoj platforme za avtomatizacijo Integro in DevOps. 

DevOps je odgovoren za okolje Dev/Beta spletnega mesta CIAN, okolje Integro, pomaga razvijalcem pri reševanju težav in razvija nove pristope k skaliranju okolij. Razvojna smer Integro se ukvarja tako s samim Integrom kot s povezanimi storitvami, na primer vtičniki za Jenkins, Jira, Confluence, razvija pa tudi pomožne pripomočke in aplikacije za razvojne skupine. 

Ekipa DI sodeluje z ekipo Platform, ki interno razvija arhitekturo, knjižnice in razvojne pristope. Hkrati lahko vsak razvijalec znotraj CIAN prispeva k avtomatizaciji, na primer naredi mikroavtomatizacijo, ki ustreza potrebam ekipe, ali deli kul idejo o tem, kako avtomatizacijo še izboljšati.

Večplastna torta avtomatizacije pri CIAN

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Vse sisteme, vključene v avtomatizacijo, lahko razdelimo na več plasti:

  1. Zunanji sistemi (Jira, Bitbucket itd.). Z njimi sodelujejo razvojne ekipe.
  2. Platforma Integro. Najpogosteje razvijalci z njim ne delajo neposredno, vendar je to tisto, kar skrbi za delovanje celotne avtomatizacije.
  3. Storitve dostave, orkestracije in odkrivanja (na primer Jeknins, Consul, Nomad). Z njihovo pomočjo umestimo kodo na strežnike in zagotovimo medsebojno delovanje storitev.
  4. Fizična plast (strežniki, OS, sorodna programska oprema). Naša koda deluje na tej ravni. To je lahko fizični strežnik ali virtualni (LXC, KVM, Docker).

Na podlagi tega koncepta razdelimo področja odgovornosti znotraj ekipe DI. Prvi dve ravni sta v pristojnosti smeri razvoja Integro, zadnji dve ravni pa sta že v pristojnosti DevOps. Ta ločenost nam omogoča, da se osredotočimo na naloge in ne moti interakcije, saj smo blizu drug drugemu in nenehno izmenjujemo znanja in izkušnje.

Neokrnjeno

Osredotočimo se na Integro in začnimo s tehnološkim skladom:

  • CentOS 7
  • Docker + Nomad + Consul + Vault
  • Java 11 (stari Integro monolit bo ostal na Javi 8)
  • Spring Boot 2.X + Spring Cloud Config
  • PostgreSql 11
  • RabbitMQ 
  • Apache Ignite
  • Camunda (vdelana)
  • Grafana + Graphite + Prometheus + Jaeger + ELK
  • Spletni uporabniški vmesnik: React (CSR) + MobX
  • SSO: Keycloak

Držimo se načela razvoja mikrostoritev, čeprav imamo zapuščino v obliki monolita zgodnje različice Integra. Vsaka mikrostoritev teče v svojem vsebniku Docker; storitve med seboj komunicirajo prek zahtev HTTP in sporočil RabbitMQ. Mikrostoritve se med seboj najdejo prek Consul in mu pošljejo zahtevo, pri čemer posredujejo avtorizacijo prek SSO (Keycloak, OAuth 2/OpenID Connect).

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Kot primer iz resničnega življenja razmislite o interakciji z Jenkinsom, ki je sestavljena iz naslednjih korakov:

  1. Mikrostoritev za upravljanje poteka dela (v nadaljevanju imenovana mikrostoritev Flow) želi zagnati gradnjo v Jenkinsu. Za to uporabi Consul, da poišče IP:PORT integracijske mikrostoritve Jenkins (v nadaljevanju mikrostoritev Jenkins) in ji pošlje asinhrono zahtevo za začetek gradnje v Jenkinsu.
  2. Po prejemu zahteve mikrostoritev Jenkins ustvari in odgovori z ID-jem delovnega mesta, ki se nato lahko uporabi za identifikacijo rezultata dela. Hkrati sproži gradnjo v Jenkinsu prek klica REST API.
  3. Jenkins izvede gradnjo in po zaključku pošlje webhook z rezultati izvajanja mikrostoritvi Jenkins.
  4. Mikrostoritev Jenkins, ko prejme webhook, ustvari sporočilo o zaključku obdelave zahteve in mu pripne rezultate izvedbe. Ustvarjeno sporočilo se pošlje v čakalno vrsto RabbitMQ.
  5. Preko RabbitMQ doseže objavljeno sporočilo mikrostoritev Flow, ki izve za rezultat obdelave svoje naloge tako, da ujema ID opravila iz zahteve in prejetega sporočila.

Zdaj imamo približno 30 mikrostoritev, ki jih lahko razdelimo v več skupin:

  1. Upravljanje konfiguracije.
  2. Informacije in interakcija z uporabniki (messengerji, pošta).
  3. Delo z izvorno kodo.
  4. Integracija z orodji za uvajanje (jenkins, nomad, consul itd.).
  5. Spremljanje (izjave, napake itd.).
  6. Spletni pripomočki (UI za upravljanje testnih okolij, zbiranje statistik itd.).
  7. Integracija s sledilci opravil in podobnimi sistemi.
  8. Upravljanje poteka dela za različne naloge.

Naloge poteka dela

Integro avtomatizira dejavnosti, povezane z življenjskim ciklom naloge. Poenostavljeno povedano, bo življenjski cikel naloge razumljen kot potek dela naloge v Jiri. Naši razvojni procesi imajo več različic poteka dela, odvisno od projekta, vrste naloge in izbranih možnosti v posamezni nalogi. 

Poglejmo potek dela, ki ga najpogosteje uporabljamo:

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

V diagramu zobnik označuje, da Integro samodejno prikliče prehod, medtem ko človeška figura označuje, da prehod ročno prikliče oseba. Oglejmo si več poti, ki jih lahko opravi naloga v tem delovnem toku.

Popolnoma ročno testiranje na DEV+BETA brez kanarčkov (običajno tako izdamo monolit):

Od skriptov do lastne platforme: kako smo avtomatizirali razvoj pri CIAN

Obstajajo lahko tudi druge kombinacije prehodov. Včasih je pot, po kateri bo šla težava, mogoče izbrati prek možnosti v Jiri.

Gibanje naloge

Oglejmo si glavne korake, ki se izvedejo, ko se naloga premakne skozi potek dela »DEV Testing + Canary Tests«:

1. Razvijalec ali PM ustvari nalogo.

2. Razvijalec vzame nalogo v delo. Po zaključku se preklopi v status V PREGLEDU.

3. Jira pošlje Webhook mikrostoritvi Jira (ki je odgovorna za integracijo z Jira).

4. Mikrostoritev Jira pošlje zahtevo storitvi Flow (ki je odgovorna za interne tokove dela, v katerih se izvaja delo) za zagon toka dela.

5. Znotraj storitve Flow:

  • Nalogi so dodeljeni pregledovalci (mikrostoritev Uporabniki, ki ve vse o uporabnikih + mikrostoritev Jira).
  • Prek mikrostoritve Source (pozna repozitorije in veje, vendar ne dela s samo kodo) se izvede iskanje repozitorijev, ki vsebujejo vejo naše izdaje (za poenostavitev iskanja ime veje sovpada z izdajo številka v Jira). Najpogosteje ima naloga samo eno vejo v enem repozitoriju; to poenostavi upravljanje čakalne vrste za uvajanje in zmanjša povezljivost med repozitoriji.
  • Za vsako najdeno vejo se izvede naslednje zaporedje dejanj:

    i) Posodabljanje glavne veje (mikrostoritev Git za delo s kodo).
    ii) Razvijalec (mikrostoritev Bitbucket) prepreči spremembe podružnice.
    iii) Za to vejo je ustvarjena zahteva za vlečenje (mikrostoritev Bitbucket).
    iv) Sporočilo o novi zahtevi za vlečenje se pošlje razvijalskim klepetom (mikrostoritev Notify za delo z obvestili).
    v) Naloge gradnje, testiranja in uvajanja se začnejo na DEV (mikrostoritev Jenkins za delo z Jenkinsom).
    vi) Če so vsi predhodni koraki uspešno zaključeni, potem Integro doda odobritev v zahtevo za vlečenje (mikrostoritev Bitbucket).

  • Integro čaka na odobritev v zahtevi za poteg od imenovanih pregledovalcev.
  • Takoj ko so prejete vse potrebne odobritve (vključno s pozitivnimi samodejnimi testi), Integro prenese nalogo v status Test on Dev (mikrostoritev Jira).

6. Preizkuševalci preizkusijo nalogo. Če ni težav, se opravilo prenese v stanje Pripravljeno za gradnjo.

7. Integro »vidi«, da je naloga pripravljena za izdajo, in začne njeno uvajanje v kanaričnem načinu (jenkinsova mikrostoritev). Pripravljenost za sprostitev je določena z nizom pravil. Naloga je na primer v zahtevanem statusu, za druge naloge ni zaklepanja, trenutno ni aktivnih nalaganj te mikrostoritve itd.

8. Naloga se prenese v status Canary (mikrostoritev Jira).

9. Jenkins zažene nalogo uvajanja prek Nomada v načinu Canary (običajno 1-3 primerki) in obvesti storitev za spremljanje izdaje (mikrostoritev DeployWatch) o uvedbi.

10. Mikrostoritev DeployWatch zbira ozadje napake in se nanjo po potrebi odzove. Če je ozadje napake preseženo (norma ozadja se izračuna samodejno), so razvijalci obveščeni prek mikrostoritve Notify. Če se po 5 minutah razvijalec ne odzove (klikne Povrni ali Ostani), se sproži samodejni povrnitev instanc Canary. Če ozadje ni preseženo, mora razvijalec ročno zagnati uvajanje opravila v produkcijo (s klikom na gumb v uporabniškem vmesniku). Če v 60 minutah razvijalec ne zažene uvedbe v produkcijo, bodo primerki kanarčka prav tako povrnjeni nazaj iz varnostnih razlogov.

11. Po zagonu uvajanja v produkcijo:

  • Naloga se prenese v status proizvodnje (mikrostoritev Jira).
  • Mikrostoritev Jenkins začne postopek uvajanja in o uvajanju obvesti mikrostoritev DeployWatch.
  • Mikrostoritev DeployWatch preveri, ali so bili posodobljeni vsi vsebniki v produkciji (v nekaterih primerih niso bili posodobljeni vsi).
  • Prek mikrostoritve Notify se v produkcijo pošlje obvestilo o rezultatih uvedbe.

12. Razvijalci bodo imeli na voljo 30 minut, da začnejo vračati nalogo iz produkcije, če je zaznano nepravilno vedenje mikrostoritve. Po tem času bo naloga samodejno združena v glavno (mikrostoritev Git).

13. Po uspešnem spajanju v master bo status opravila spremenjen v Zaprto (mikrostoritev Jira).

Diagram se ne pretvarja, da je popolnoma podroben (v resnici je še več korakov), vendar vam omogoča, da ocenite stopnjo integracije v procese. Ta shema se nam ne zdi idealna in izboljšujemo procese samodejne izdaje in podpore za uvajanje.

Kaj je naslednje?

Imamo velike načrte za razvoj avtomatizacije, na primer odpravo ročnih operacij med izdajami monolita, izboljšanje spremljanja med samodejnim uvajanjem in izboljšanje interakcije z razvijalci.

A ustavimo se za zdaj tukaj. Marsikatero temo smo v pregledu avtomatizacije obdelali površno, nekaterih se sploh nismo dotaknili, zato bomo z veseljem odgovorili na vprašanja. Čakamo na predloge, kaj podrobneje pokriti, zapišite v komentarje.

Vir: www.habr.com

Dodaj komentar