Kas yra mikroservisai ir kodėl visi apie juos kalba?
Jei dirbate su programine įranga bent kelerius metus, tikriausiai esate girdėję žodį „mikroservisai” tiek daug kartų, kad jis jau pradeda skambėti kaip tuščias žargonas. Tačiau už šio termino slypi gana konkretus ir praktiškas architektūrinis sprendimas, kuris iš tikrųjų pakeitė tai, kaip šiuolaikinės kompanijos kuria ir palaiko savo sistemas.
Mikroservisų architektūra – tai požiūris į programinės įrangos kūrimą, kai didelė aplikacija suskaidoma į mažus, nepriklausomus servisus, kurių kiekvienas atlieka vieną konkrečią funkciją. Kiekvienas servisas gyvena savo procese, bendrauja su kitais per API (dažniausiai HTTP/REST arba pranešimų eiles) ir gali būti diegiamas bei atnaujinamas nepriklausomai nuo kitų.
Priešingybė šiam požiūriui – monolitinė architektūra, kur visas kodas gyvena vienoje didelėje aplikacijoje. Monolitas nėra blogas žodis – daugeliui projektų tai visiškai tinkamas sprendimas. Bet kai sistema auga, komanda plečiasi, o reikalavimai keičiasi greičiau nei spėjate atsipūsti, monolitas pradeda slėgti kaip granito luitas.
Monolitas prieš mikroservisus: kur yra ta riba?
Prieš nerdami giliau, verta suprasti, kada mikroservisai iš viso prasmingi. Nes tiesa tokia – daugelis projektų, kurie perėjo prie mikroservisų, to neturėjo daryti, arba bent jau neturėjo daryti taip anksti.
Monolitinė architektūra turi aiškių privalumų: lengviau derinti (debug), paprastesnė infrastruktūra, mažiau tinklo skambučių, lengviau suprasti naujam komandos nariui. Jei turite penkių žmonių komandą ir vidutinio dydžio aplikaciją, monolitas tikriausiai yra geresnis pasirinkimas.
Mikroservisai tampa aktualūs, kai:
- Skirtingos sistemos dalys turi labai skirtingus apkrovos reikalavimus – pavyzdžiui, paieška naudojama 100 kartų dažniau nei ataskaitos
- Skirtingos komandos dirba su skirtingomis sistemos dalimis ir nuolat „trukdo” viena kitai
- Reikia naudoti skirtingas technologijas skirtingoms problemoms spręsti
- Diegimo ciklai tampa per ilgi ir rizikingai dėl to, kad vienas pakeitimas gali paveikti viską
- Sistema turi augti iki tokio masto, kad vienas serveris nebepakanka
Amazon, Netflix, Uber – visos šios kompanijos perėjo prie mikroservisų ne todėl, kad tai buvo madinga, o todėl, kad jų monolitai tiesiog nustojo veikti esant tokiam masto lygiui. Netflix turėjo vieną katastrofišką gedimą 2008 metais, kuris privertė juos permąstyti visą architektūrą. Rezultatas – sistema, kuri dabar gali atlaikyti vieno komponento gedimą nesugriovusi viso srauto.
Kaip mikroservisai bendrauja tarpusavyje
Vienas iš svarbiausių aspektų, kurį reikia suprasti, – kaip šie maži servisai „kalbasi” tarpusavyje. Čia yra du pagrindiniai modeliai: sinchroninis ir asinchroninis bendravimas.
Sinchroninis bendravimas – tai kai servisas A kviečia servisą B ir laukia atsakymo. Dažniausiai tai daroma per REST API arba gRPC. Paprastas, intuityvus, lengvai suprantamas. Bet turi vieną didelį trūkumą: jei servisas B lėtas arba neveikia, servisas A taip pat sustoja ir laukia. Tai sukuria vadinamąją „kaskadinio gedimo” problemą.
Asinchroninis bendravimas – tai kai servisai bendrauja per pranešimų eiles (message queues), tokias kaip RabbitMQ, Apache Kafka ar AWS SQS. Servisas A išsiunčia pranešimą ir tęsia darbą, nesvarbu, ar servisas B jį iš karto apdorojo. Tai daug atspariau gedimams, bet sudėtingiau suprojektuoti ir derinti.
Praktinis patarimas: naudokite sinchroninį bendravimą ten, kur reikia greito atsakymo (pvz., vartotojo autentifikacija), ir asinchroninį ten, kur galima palaukti (pvz., el. laiško siuntimas, ataskaitos generavimas). Nereikia rinktis vieno modelio visai sistemai.
Dar vienas svarbus elementas – API Gateway. Tai tarpinis sluoksnis tarp kliento ir jūsų mikroservisų. Vietoj to, kad klientas žinotų apie 20 skirtingų servisų adresus, jis kalba tik su vienu taškų. API Gateway rūpinasi autentifikacija, apkrovos balansavimu, užklausų maršrutavimu ir daug kuo kitu. AWS API Gateway, Kong, Nginx – tai populiariausi pasirinkimai.
Duomenų valdymas: kiekvienas servisas turi savo „namus”
Vienas iš principų, kuris dažnai sukelia daugiausiai diskusijų – kiekvienas mikroservisas turėtų turėti savo duomenų bazę. Tai skamba kaip resursų švaistymas, bet yra labai svarbi priežastis, kodėl taip rekomenduojama.
Jei visi servisai naudoja tą pačią duomenų bazę, jūs iš tikrųjų turite paskirstytą monolitą. Vienas servisas gali pakeisti duomenų struktūrą ir sulaužyti kitus. Vienas servisas gali „nusavinti” visus duomenų bazės resursus. Ir svarbiausia – negalite keisti ar pakeisti vieno serviso neatsižvelgdami į visus kitus.
Praktiškai tai reiškia, kad galite turėti:
- Vartotojų servisas → PostgreSQL duomenų bazė
- Produktų servisas → MongoDB (nes produktų duomenys yra labai skirtingos struktūros)
- Paieškos servisas → Elasticsearch
- Sesijų servisas → Redis (greita, in-memory duomenų bazė)
Kiekvienas servisas naudoja tą technologiją, kuri geriausiai tinka jo specifiniam uždaviniui. Tai vadinama „polyglot persistence” – ir tai yra vienas iš tikrų mikroservisų architektūros privalumų.
Bet čia iškyla kita problema: kaip palaikyti duomenų nuoseklumą tarp servisų? Jei vartotojas ištrinamas vartotojų servise, kaip užsakymų servisas apie tai sužino? Čia ateina į pagalbą Saga pattern – sekos kompensacinių transakcijų, kurios užtikrina duomenų nuoseklumą paskirstytoje sistemoje. Tai sudėtinga tema, bet labai svarbi, jei rimtai žiūrite į mikroservisų architektūrą.
Konteineriai, Kubernetes ir visa ta infrastruktūros magija
Teoriškai galite paleisti mikroservisus be Docker ir Kubernetes. Praktiškai – tai beveik neįmanoma valdyti, kai servisų skaičius viršija dešimt. Konteineriai ir orkestracija tapo de facto standartu mikroservisų pasaulyje, ir tam yra labai konkrečios priežastys.
Docker išsprendžia „pas mane veikia” problemą. Kiekvienas mikroservisas supakuojamas į konteinerį su visomis savo priklausomybėmis. Nesvarbu, ar tai kūrimo aplinka, testavimo serveris ar produkcija – konteineris elgiasi vienodai. Tai dramatiškai sumažina „aplinkos” problemas.
Kubernetes (arba trumpai K8s) yra konteinerių orkestravimo sistema. Ji rūpinasi tuo, kad jūsų konteineriai veiktų, automatiškai perkelia juos į kitus serverius, jei vienas serveris sugenda, skalina servisus aukštyn ir žemyn pagal apkrovą, ir dar dešimtys kitų dalykų. Kubernetes mokymosi kreivė yra statoka, bet investicija atsipirks.
Keletas praktinių patarimų dirbant su K8s:
- Naudokite Helm charts savo aplikacijų diegimui – tai Kubernetes paketų valdymo sistema, kuri dramatiškai supaprastina konfigūraciją
- Nustatykite resource limits kiekvienam konteineriui – be jų vienas servisas gali „suvalgyti” visus klasterio resursus
- Naudokite liveness ir readiness probes – tai mechanizmai, leidžiantys Kubernetes žinoti, ar jūsų servisas veikia ir ar pasiruošęs priimti srautą
- Pradėkite su managed Kubernetes sprendimu (GKE, EKS, AKS) – valdyti savo K8s klasterį nuo nulio yra atskiras darbas
Service mesh technologijos, tokios kaip Istio ar Linkerd, prideda dar vieną sluoksnį – jos valdo tinklo srautą tarp servisų, suteikia stebėjimą, saugumo politikas ir apkrovos balansavimą be kodo pakeitimų. Tai galingas įrankis, bet pradedantiesiems gali būti per daug sudėtingas – geriau pradėti be jo ir pridėti vėliau, kai tikrai prireiks.
Stebėjimas ir derinimas: kai viskas sudėtingiau nei monolite
Vienas iš didžiausių iššūkių, su kuriais susiduria komandos perėjusios prie mikroservisų, – kaip suprasti, kas vyksta sistemoje. Monolite turite vieną žurnalą (log), vieną stack trace, vieną metrikų rinkinį. Mikroservisų pasaulyje viena vartotojo užklausa gali pereiti per 10-15 skirtingų servisų, ir kiekvienas iš jų generuoja savo žurnalus.
Distributed tracing – tai technologija, leidžianti sekti vieną užklausą per visus servisus. Kiekviena užklausa gauna unikalų trace ID, kuris perduodamas per visus servisus. Populiariausi sprendimai: Jaeger, Zipkin, AWS X-Ray. Integruoti juos nėra labai sudėtinga, bet reikia disciplinos – kiekvienas servisas turi perduoti trace kontekstą toliau.
Centralizuotas žurnalų rinkimas yra būtinybė. ELK stack (Elasticsearch, Logstash, Kibana) arba modernesnės alternatyvos kaip Grafana Loki leidžia surinkti žurnalus iš visų servisų į vieną vietą ir juos analizuoti. Svarbus patarimas: nuo pat pradžių naudokite struktūrizuotus žurnalus (JSON formatu) – tai dramatiškai palengvina paiešką ir analizę.
Metrikų stebėjimas su Prometheus ir Grafana tapo standartine kombinacija. Prometheus renka metrikas iš jūsų servisų, Grafana jas vizualizuoja. Kiekvienas servisas turėtų eksponuoti savo metrikas – užklausų skaičių, atsakymo laiką, klaidų procentą. Tai leidžia greitai pastebėti problemas ir reaguoti prieš tai, kai vartotojai pradeda skųstis.
Dar vienas svarbus konceptas – Circuit Breaker pattern. Tai mechanizmas, kuris „atjungia” servisą, jei jis pradeda grąžinti per daug klaidų. Vietoj to, kad sistema lauktų timeout’ų ir kaupiamų klaidų, circuit breaker greitai grąžina klaidą ir leidžia sistemai likti stabilia. Hystrix (nors jau nebeplėtojamas) ir Resilience4j yra populiariausi Java pasaulyje, o kitos kalbos turi savo analogus.
Saugumas mikroservisų aplinkoje
Saugumas mikroservisų architektūroje yra sudėtingesnis nei monolite, nes turite daug daugiau „durų”, pro kurias gali įeiti. Bet tuo pačiu, teisingai suprojektuota sistema gali būti saugesnė, nes kompromituotas vienas servisas nesuteikia prieigos prie visko.
Zero Trust principas – tai požiūris, kai nė vienas servisas nėra automatiškai patikimas, net jei jis yra tame pačiame tinkle. Kiekvienas servisas turi autentifikuotis ir autorizuotis. Tai skamba paranojiškai, bet yra labai svarbu, nes vidiniai tinklai nėra taip saugūs, kaip atrodo.
Praktiniai saugumo patarimai:
- Naudokite JWT tokenuose vartotojų autentifikacijai – API Gateway patikrina tokeną, o servisai gali pasitikėti, kad užklausa jau autentifikuota
- Servisų tarpusavio komunikacijai naudokite mTLS (mutual TLS) – abu servisai autentifikuoja vienas kitą
- Laikykite slaptažodžius ir raktus secrets management sistemose (HashiCorp Vault, AWS Secrets Manager) – niekada kode ar konfigūracijos failuose
- Taikykite principle of least privilege – kiekvienas servisas turi prieigą tik prie to, ko jam reikia
- Reguliariai skanuokite konteinerių vaizdus dėl pažeidžiamumų – Trivy, Snyk ar panašūs įrankiai gali tai automatizuoti CI/CD pipeline’e
Kada mikroservisai tampa problema ir kaip tai išvengti
Mikroservisų architektūra nėra sidabrinė kulka. Yra situacijų, kai ji sukuria daugiau problemų nei sprendžia, ir svarbu tai suprasti prieš nerdami į gilų vandenį.
Paskirstyto monolito problema – tai kai sukuriate daug servisų, bet jie yra taip stipriai susiję, kad bet koks pakeitimas viename reikalauja pakeitimų kituose. Tai reiškia, kad jūs turite visus mikroservisų trūkumus (sudėtingumas, tinklo skambučiai, paskirstytos transakcijos) be jokių privalumų. Sprendimas – aiškiai apibrėžti kiekvieno serviso ribas pagal Domain-Driven Design principus.
Per smulkūs servisai – tai kita kraštutinybė. Jei kiekviena funkcija yra atskiras servisas, valdymo sudėtingumas tampa nepakeliamas. Geras orientyras: servisas turėtų atlikti vieną aiškiai apibrėžtą verslo funkciją, ne vieną techninę funkciją. „Vartotojų servisas” yra geras. „Slaptažodžio hashavimo servisas” – per smulku.
Komandos dydžio ir architektūros nesutapimas – Conwayo dėsnis sako, kad organizacijos kuria sistemas, kurios atspindi jų komunikacijos struktūrą. Jei turite 5 žmonių komandą, bet bandote valdyti 30 mikroservisų, tai neveiks. Mikroservisai geriausiai veikia, kai kiekvienas servisas priklauso vienai komandai, kuri visiškai atsakinga už jį.
Jei pradedate naują projektą ir nežinote, ar jums reikia mikroservisų, čia yra paprastas patarimas: pradėkite su monolitu, bet rašykite kodą taip, lyg jis bus išskaidytas ateityje. Naudokite aiškius modulių ribas, vengkite stiprių priklausomybių tarp skirtingų domenų, ir kai ateis laikas – išskaidymas bus daug paprastesnis. Tai vadinama „modular monolith” požiūriu, ir daugeliui projektų tai yra idealus tarpinis žingsnis.
Nuo teorijos iki veikiančios sistemos: kur pradėti
Jei jau nusprendėte, kad mikroservisai yra tai, ko jums reikia, arba tiesiog norite geriau suprasti šią architektūrą praktiškai, čia yra konkretus kelias, kaip pradėti.
Pirmiausia – išmokite Docker. Tai nėra pasirinkimas mikroservisų pasaulyje, tai būtinybė. Docker oficiali dokumentacija yra puiki, ir per savaitgalį galite išmokti pagrindus. Sukurkite kelis paprastus konteinerius, supraskite, kaip veikia Docker Compose kelių servisų aplinkoje.
Tada – pasirinkite vieną paprastą projektą ir pabandykite jį suprojektuoti kaip mikroservisus. Klasikinis pavyzdys – el. parduotuvė su vartotojų, produktų, užsakymų ir mokėjimų servisais. Tai pakankamai sudėtinga, kad pajustumėte tikrus iššūkius, bet pakankamai paprasta, kad neapsikrautumėte.
Kubernetes mokymasis yra ilgesnis kelias. Pradėkite su Minikube arba Kind (Kubernetes in Docker) lokalioje aplinkoje. Katacoda (dabar Killercoda) turi puikių interaktyvių mokymosi scenarijų. Kubernetes oficiali dokumentacija yra viena geriausių technologijų dokumentacijų apskritai.
Stebėjimui – pradėkite su Prometheus ir Grafana. Yra paruoštų Docker Compose konfigūracijų, kurios leidžia per kelias minutes turėti veikiančią stebėjimo sistemą. Tai suteiks jums supratimą, kaip atrodo sistema iš vidaus.
Mikroservisų architektūra nėra galutinis tikslas – tai įrankis. Kaip ir bet kuris įrankis, jis turi savo tinkamas ir netinkamas situacijas. Kompanijos, kurios sėkmingai naudoja mikroservisus, ne tik suprojektavo tinkamą architektūrą, bet ir sukūrė kultūrą, procesus ir įrankius, kurie leidžia šiai architektūrai veikti. Techninis sprendimas yra tik dalis lygties – organizacinis pasirengimas yra lygiai toks pat svarbus. Ir galbūt tai yra svarbiausia pamoka iš viso šio mikroservisų pasaulio: architektūra atspindi organizaciją, ne atvirkščiai.






