Mikroservisi i kontejnerizacija

Uporaba mikroservisa nije nužan preduvjet za spremanje isporučenog koda u kontejnere, ali način na koji se koriste kontejneri i host uvelike ovisi o tome je li ciljna aplikacija monolit ili mikroservis.

 

Monolitna aplikacija u kontejneru

 

Kako kontejneri, te shodno time orkestratori poput Azure Service Fabric-a ili Kubertnetes-a, nisu potrebni za izgradnju mikroservisne arhitekture, tako i mikroservisi nisu neophodni kao sadržaj kontejnera. Naime, bez problema se neka klasična monolitna aplikacija može smjestiti u kontejner – to je jednostavnije pa čak u nekim slučajevima i primjenjivije.

 

Monolitna aplikacija u kontejneru

 

Recimo, ako imamo klasičnu troslojnu web aplikaciju koja se sastoji od REST API-a, sloja poslovne logike i podatkovnog sloja, istu možemo smjestiti vrlo lako u jedan kontejner. Pritom će nam biti svejedno smještamo li bazu podataka uz aplikaciju, ili možda fleksibilnije, u poseban kontejner. Ovdje, kao i općenito, nije toliko važno govorimo li o Windows kontejneru, ili Docker kontejneru, ali nadalje, zbog toga što je više korišten od strane zajednice te pokriva više operativnih sustava, koristit ćemo ipak Docker kontejner.

Prednosti smještanja monolitne aplikacije u kontejner su te što možemo vrlo lako skalirati rješenje, pogotovo ako odijelimo bazu podataka, a i druge neke komponente u posebne kontejnere, kombinirajući stoga procesorsku snagu, memoriju, propusnost mreže s jedne strane – i cijenu s druge.

Neću skrivati da i mi u Ekobitu često koristimo taj pristup s jednim kontejnerom na hostu, pogotovo na početku razvojnog ciklusa, gdje su stvari dosta jednostavne – ili kod prototipiranja proizvoda – ili rješenje neće biti razvijano da bude kompleksno zbog prirode poslovnog procesa. Recimo, naša interna evidencija radnih sati zaposlenika bi se mogla smjestiti u jedan kontejner jer se radi o jednostavnom poslovnom procesu, a korištena baza podataka u zasebni kontejner, jer se navedena baza podataka može koristiti u nekim drugim servisima (što bi predstavljalo u mikroservisnoj arhitekturi svojevrsni anti-pattern).

U slučaju da želimo „strpati“ cijelu aplikaciju u jedan kontejner, do potencijalnog problema najčešće dolazi kada tu aplikaciju želimo dosta granulirano skalirati. Primjerice postoji veliki pritisak na REST API koji aplikacija objavljuje zbog ogromnog broja korisnika koji trenutno pregledavaju aplikaciju. Ako želimo balansirati učitavanje, moramo replicirati kompletni kontejner i pritom dobivamo dupliciranu aplikaciju na više mjesta. U slučaju da je baza podataka unutar kontejnera, to je nemoguće skalirati.

 

Mikroservisi u kontejneru

 

Ako je aplikacija složena, jedna od mjera rješavanja kompleksnosti je upravo dekompozicija. Dekompozicija je mogući scenarij migracije na mikroservisnu arhitekturu u kojem svaka komponenta u aplikaciji postaje „svijet za sebe“ te određuje svoju poslovnu domenu. Svaki dio poslovnog okružja je u isto vrijeme i izoliran i povezan, a granice premošćuje kanal komunikacije (message broker) između tih aplikativnih dijelova (servisa). Kao posljedica uvođenja odvojenih servisa ujedno je i nužnost osiguranja da svaki taj zasebni dio poslovnog procesa ima mogućnost rada u zasebnom okružju – što je i cilj kontejnera.

 

Mikroservisi u kontejneru

 

Većina aplikacija koje radimo pripada ovoj kategoriji jer, neovisno radi li se o SOA (service-oriented architecture) aplikacijama ili pravim mikroservisima, sve ove komponente sustava morat ćemo adekvatno instalirati na ili on-premise ili cloud servere.

Arhitektura poslovnog rješenja koju trenutno radim zajedno s mojim timom je mikroservisna arhitektura u kojoj imamo izdvojene servise za autentikaciju, autorizaciju, praćenje korisnika, sistemsku administraciju te niz modularnih servisa koji predstavljaju razne poslovne procese organizacije. Svaki od ovih servisa ima svoj domenski podatkovni spremnik (neki i više tipova spremnika) te neki od njih klijentske web aplikacije (većinom SPA) s kojima komuniciraju putem REST API-a. Nadalje, servisi međusobno komuniciraju asinkrono putem AMQP poruka, ali i sinkrono putem HTTP-a (razmatramo uporabu gRPC-a), a klijenti također komuniciraju putem WebSockets protokola – ako se radi o potrebi za real-time prijenosom podataka. Specifičnost je i to da, s obzirom da radimo i IoT rješenje, moramo uključiti Azure IoT Hub te ostale resurse u ponudi Azure-a koje rade u skladu s uređajima, a koristimo i jedan broj komponenti treće strane primjerice za vremensku prognozu, biblioteke za rad sa poslovnim paketima, geografski servis i slično.

 

mikroservisna arhitektura

 

U načelu, svaki mikroservis smjestit će se u zasebni kontejner, a sve što on koristi, smjestit će se također u posebni kontejner. Pritom, treba posebno paziti na mogućnosti i ograničenja što takav pristup daje. Neke komponente koje koristi mikroservis, kao što su baza podataka, message broker, međuspremnik, su vrlo lako prilagodljive za smještanje u kontejner preko gotovih slika, dok neke komponente ili teško ili nikako ne možemo smjestiti u kontejner te je potrebna dodatna prilagodba.

Važno je odrediti hoće li se nešto staviti na isti ili na različit host, a možda i nije nužno da se svaki mikroservis nalazi na istom hostu. To je prirodna posljedica dekompozicije i ujedno samodostatnosti mikroservisa (self-suficient service paradigme). Naime, ako se resursi dijele ili služe za međusobno povezivanje dijelova aplikacija, ne nužno samo servisa, oni se mogu nalaziti na zasebnom hostu ili više hostova radi skaliranja.

Kao i kod monolitnih aplikacija, možemo kreirati i tipizirane slike unaprijed, koje možemo višestruko koristiti u više kompozicija kontejnera. To možemo objaviti na Docker Hub ili na vlastiti oblak, recimo iskoristiti Azure Container Registry (također postoje opcije za on-prem spremnik slika, ali pretpostavljam da ćete više koristiti oblak). Te slike obično već koristimo za smještanje povezanih komponenti, kao što su baze podataka ili razni enterprise-level alati. Ali, također možemo i sami izgraditi tipiziranu sliku i isporučiti ju na naš cloud spremnik čineći ga ga dostupnim i drugima.

Što se tiče pitanja dijeljenja podataka, tu imamo nekoliko opcija. Pošto su nam mikroservisi u posebnim kontejnerima, ali mogu biti u istom hostu koji ih drži, možemo koristiti specifične spremnike (kao što je Azure Data Volume), koji mogu dijeliti svoje podatke. Alternativa koju koristimo je Redis Cache ili jednostavno možemo smjestiti čitavu bazu podataka u poseban kontejner. Ako govorimo o manjem skupu podataka, tada to čak i nije loše rješenje. U protivnom, prilikom više hostova kod instalacije kontejnera koristimo ili provizionirane baze podataka, obavezno na produkciji, ili opet kontejnerizirane servere koji su dijeljeni.

Za korištenje event-driven paradigme, izgradili smo event bus pattern, koji predstavlja apstraktni sloj iznad konkretnih implementacija message broker rješenja kao što je Apache Kafka, Azure Service Bus, RabbitMQ. Na taj način putem konfiguracije određujemo što će se koristiti putem kontejnera. Varijanta je i korištenje nekog enterprise-level alata koji rješava to pitanje kao što je MassTransit (preporučujem!), jer osim što daje mogućnost korištenja višestrukih message brokera, daje i dodatne mogućnosti za obradu poruka koje nipošto nemojte odbiti.

 

Alati za kontejneriziranje aplikacija u radnom okružju

 

Već je jasno da ćemo za kreiranje i pogon slika u kontejneru koristiti Docker. Za početak sastavljanja Docker slika te pogon kontejnera izvrsno nam može poslužiti razvojno okruženje Visual Studio. Ono naime ima dosta dobru podršku za Docker i rad sa kompozicijom kontejnera, tako da ga za razvojno okruženje definitivno preporučam.

Recimo, za podršku rada s Dockerom u Visual Studiju, dovoljno je odabrati opciju „Add Docker Support…“ ili „Container Orchestration Support“ i alat će kreirati sve potrebno za Docker ili kompoziciju Docker kontejnera, kako je prikazano na slijedećim slikama:

 

Container Orchestration Support

 

U ovom slučaju, ciljna aplikacija je system mikroservis, koja sadrži jednu krajnju točku, koja predstavlja njezin REST API. Taj servis komunicira sa svojom domenskom bazom podataka, koja je relacijski SQL Server, a isto tako komunicira s drugim mikroservisima putem event-driven arhitekture.

 

Docker Desktop

 

Isto tako, i sam Docker ima svoje okruženje Docker Desktop (za Windows ili drugi operativni sustav), te i preko njega možete pratiti stanje svojih pokrenutih slika, trošenje memorije, konfiguraciju komponenti i slično.

Ukoliko ćemo navedenu kompoziciju Docker kontejnera povezati s našim DevOps procesom, koristit ćemo pipeline koji će nam izraditi potrebne slike i popuniti kontejnere te izvesti konfiguraciju i pokretanje kontejnera. Nadalje, koristit ćemo zasigurno i neki od orkestratora za kontejnere, koji će znati povezati sve mikroservise i pripadne komponente u kontejnerima i pratiti njihovo stanje i rješavati pitanja dostupnosti i konzistentnosti.

 

Izazovi mikroservisa i kontejnerizacije

 

Prvi izazov na kojeg smo naišli je manjak informacija i početno nesnalaženje u pojmovima i koncepciji kontejnerizacije. Naime, vrlo je teško otkriti što je sve dostupno u pogledu kontejnera i hostova i kako to sve instalirati i pratiti kroz odabrani orkestrator. S druge strane, jednom kad se shvati princip i određene zakonitosti kako i što staviti u kontejner, stvar više-manje funkcionira.

Ukoliko postoji dobra slika komponente koja se koristi od strane mikroservisa, primjerice SQL server baza podataka, RabbitMQ, Azurite, i slično uz određene prilagodbe u aplikaciji i konfiguraciju, izvođenje i rad mikroservisa je moguće. Ali pritom nemojte zaboraviti da to nisu iste komponente te da postoji mogućnost da neke odlike ne rade ili rade na drugačiji (većinom restriktivniji) način. Za neke stvari potrebno je dosta prilagodbe, tako da bi planiranje kontejnerizacije moralo uslijediti što prije u razvojnom procesu.

Nadalje, kako sam spomenuo i ranije, za neke komponente jednostavno nema prikladnog rješenja i u tom slučaju potrebno je nadomjestiti funkcionalnost s nekom drugom, sličnijom, koja će efikasno raditi isti (ili slični) posao za nas. To je slučaj, na primjer s Azure IoT Hub-om, ili sa servisima treće strane, gdje nemamo nekih pretjeranih alternativa već da ih pokušamo ‘lažirati’ i omogućiti mikroservisima da rade neometano bez njih. Jedna od mogućnosti koju koristimo u slučaju vanjskih servisa je i ‘lažni’ API servis, primjerice Postman mock server, kojeg podmećemo kod izvođenja integracijskih (mikroservisnih) testova.

Kod testiranja rješenja koristit ćemo alternative, jer za razliku od monolita gdje se sve smješta na istom kontejneru, ovdje će biti problem dohvatiti sve potrebne komponente kako bi se integracijski test izveo na uspješan način. To će, naravno, biti drukčija okolina od produkcijske, pa čak i od lokalne radne okoline. Stoga inicijalizacija i orkestracija testnog ili QA host(ov)a postaje složenija i može dosta oduzimati vremena. S druge strane, mikroservisi se moraju testirati i koliko god uložimo u testiranje, bez obzira na kojem nivou ga izvedemo – jedinično, integracijsko, sistemsko. U konačnici se trud i vrijeme isplati.

Možda pitanje na kraju je: isplati li se kontejnerizirati mikroservise? Odgovor bi mogao biti jednostavan: ne vidim neku alternativu tome ako izrađujemo enterprise-level rješenje. Sagledamo li da se upravo i zbog razloga kompleksnosti, modularnosti, skalabilnosti i povećanja kvalitetnih faktora aplikacije i uvode mikroservisi – iz istih razloga će se oni i kontejnerizirati.

Smještanje mikroservisa i korištenih komponenti je nužnost ako se radi o razvijenom DevOps ciklusu koji uključuje testiranje, kontrolu kvalitete, razine stabilnosti i produkciju (možda i više etapa). Jer, u protivnom, proces konfiguracije etapa postaje zamoran posao i podložan greškama, te se on često manualno mora kontrolirati. Uporaba provizioniranih resursa na oblaku može biti i skup posao, jer se za svaki resurs plaća. Pojeftinjenja resursa – kao što su korištenja dijeljenih aplikacijskih planova ili zajedničkih spremničkih računa – mogu donijeti beneficije, ali ujedno i probleme skalabilnosti i lošije performanse. Ukoliko kroz kontejnerizaciju se konfiguracija i održavanje ostavlja orkestratoru, primjerice Azure Kubernetes Servisu, utoliko je posao administracije lakši, moguće su veće uštede kod resursa na oblaku pogotovo ako se kontejneri, to jest čvorovi, gase nakon testiranja, a i uvođenje novih okolina je znatno lakše.

Dodatne prednosti kao što su dijagnostika stanja mikroservisa, automatsko prebacivanje rada u slučaju pada sustava novim čvorovima, veća preglednost sustava, povezivanje kontejnera s DevOps procesom, i mnoge druge nikako ne bismo trebali zanemariti. Stoga je uporaba kontejnera u enterprise-level sustavima logičan i ispravan korak kod mikroservisne arhitekture, uz sve što ona nudi i kojim se izazovima moramo susresti.

Ratko Ćosić
Latest posts by Ratko Ćosić (see all)