Mikroservisy: Pár postřehů, jak to dělat (lépe)

Puzzle piecesNa konferenci NDC London jsem si letos vybral poměrně dost přednášek, které se zaměřovaly na to, jak nějakým způsobem rozbít složitý monolitický systém. Ano, řeč je o buzzwordu poslední doby – mikroservisách. :) Samozřejmě to není žádná silver bullet, ale z těch pár hodinových přednášek jsem si vyzobal nějaké informace, které nejsou z článků na internetu úplně zřejmé a stojí za to je mít na paměti.

Nejtěžší při rozpadu systému na mikroservisy je určení hranic, co všechno už má dělat jiná servisa. Služby by měly být co nejvíce autonomní, protože s hodně závislostmi roste chybovost a vrací se komplexita. Dobrým startem je rozdělení databáze na více a upravit stávající aplikaci tak, aby používala všechny najednou. Až potom začít s postupným oddělováním funkcionality. Hodně usnadní práci si předem připravit nějakou šablonu servisy, kde už bude vyřešené logování, připojení do databází, API vrstva atd., aby byl vývoj mikroservis maximálně jednoduchý (Dropwizard pro Javu) a byl podle Twelve-Factor metodiky. Všechen společný kód by měl být ve sdílených knihovnách kvůli snadné údržbě. Rozsáhlé systémy je dobré postupně řezat na větší celky, které se mohou postupně rozpadat dál na menší servisy. Nemá cenu dopředu rozložit celou obrovskou aplikaci na prvočinitele.

Hlavní výhoda mikroservis je ta, že dělají méně věcí. Složité systémy mají mnoho skrytých závislostí, které jsou při použití mikroservis „vidět“ přes jejich API. Je v nich také méně kódu, takže se rychle kompilují, testují, nasazují…  A je na nich celkově veselejší práce. Je tam ovšem více režie okolo infrastruktury a HW. Je potřeba brát v úvahu, že se teď celá aplikace může rozbít na mnohem více místech. Nejhoršímu se dá zabránit použitím asynchronních front pro komunikaci mezi službami, aby se případný výpadek dokázal vykrýt. Je nutné, aby tyto fronty měly vysokou dostupnost (high availability). Dobré je, aby každý přípojný bod API měl vlastní Connection pool, aby volání jiných služeb neovlivňovalo další. Pokud se služby volají na přímo, tak je nezbytné dobré nastavení timeoutů, aby nedostupná služba nevyřadila z provozu ostatní. I pro tento případ je dobré implementovat nějaké Circuit breaks mechanismy, tj. něco, jak služby případě problémů rychle rozpojit, aby nedošlo např. k šíření chybných dat systémem. Pokud máme dobrý monitoring, můžeme rozpojit (případně vypnout) problémové služby i automaticky (ZooKeeper).

Testovat jednotlivé mikroservisy jako celek lze pomocí nástroje Mountebank, ve kterém se vytvoří mock jiné napojené služby.

Co se týče logování a monitoringu, tak jediný smysl dává logy i metriky někde centrálně sbírat.  Do metrik nezapomenout posílat informace ze vstupu a výstupu jednotlivých servis, aby se dal udělat ucelený obraz o tom, kde je úzké hrdlo, případně kde přesně se děje nějaký problém. Pro logování se nám osvědčil LogStash, ElasticSearch, Kibana a pro sbírání metrik Statsite (rychlá implementace StatsD v C), Graphite, Grafana. Jde o to sbírat všechny různé metriky a až později si z nich skládat grafy podle potřeby. Jiné se hodí pro běžný monitoring a jiné pro usilovné hledání záhadných chyb. Jako velká pomůcka při hledání chyb poslouží identifikátor, který bude doprovázet data celým systémem – např. nějaké vygenerované ID requestu, které bude dál probublávat všemi službami a bude se vyskytovat ve všech logách. Tím, že se logy sbírají na jedno místo, půjdou velmi snadno vyfiltrovat události, které se týkaly určitého ID.

Při návrhu API je potřeba přistupovat k němu tak, jako by bylo veřejné. Ono svým způsobem taky je. Na vaše servisy se mohou napojovat jiné od lidí z jiných týmů a jiných oddělení. Takže API verzovat. Pro jeho návrh je dobré použít Consumer-driven contracts techniku, kde jde o to, že do API vystavíme jenom to, co potřebují klienti. Zabrání se tím zbytečnému nafukování o informace a metody, které se ve výsledku ani nepoužijí. Pro otestování, jestli se rozhraní služeb nezměnilo, existuje Pact. Tak nějak se předpokládá, že API bude komunikovat přes HTTP a ideálně bude REST. Kdyby to bylo jinak, tak bychom se ochudili o mnoho nástrojů, které můžeme použít.

Jak už jsem psal výše, mikroservisy mají vyšší nárok na infrastrukturu. Jde o to, že by se při své činnosti neměly ovlivňovat. Dedikovaný HW pro každou je poměrně velký luxus a virtualizace má zase velkou režii. Proto existují nástroje jako Docker, které vytváří oddělené kontejnery pro aplikace na jednom HW. Pro vytváření stejných virtualizovaných prostředí byl zmíněn Packer. Zajímavý je také Terraform, což by měl být nástroj na nastavování celé infrastruktury.

Zajímavá byla také přednáška o velké monolitické aplikaci Totaljobs.com, kterou rozbili do jednotlivých malých webových aplikací obsahujících všechny vrstvy včetně UI. Např. výsledek hledání je jedna aplikace a detail nabízené práce je zase jiná aplikace. Jediný problém tohoto přístupu je sdílení HTML a CSS kódu, který se zase nemění tak často jako kód na backendu.

A úplně na závěr odkaz na knížku Sama Newmana, která vyjde někdy v únoru: Building Microservices (zatím se dá koupit preview verze) a jeho přednášku se 14 praktickými tipy:

To je zatím vše k tématu mikroservis. Doufám, že příští rok k tomu budu moct přidat ještě pár osobních rad a tipů.