Hlavní stránka Seznam.cz, pro mnoho lidí v ČR synonymem Internetu, měla již delší dobu pod každým článkem z feedu v spodní části stránky tlačítko „Líbí se“. Jedná se o jednoduchý a užitečný nástroj, kterým uživatelé mohou dát snadno najevo, jaký obsah se jim libí a tudíž o něj mají zájem.
Jelikož chceme mít ve feedu co nejkvalitnější výběr článků pro každého uživatele, logicky další krok bylo přidat toto tlačítko také na další služby Seznamu (Seznam Zprávy, Novinky, Sport…) a nabídnout ho partnerům, kteří také dodávají do feedu obsah.
V době, kdy jsme tento krok zvažovali, byla ale logika tlačítka Líbí se úzce svázána s fungováním samotné stránky seznam.cz, využívající tu samou databázi, backend, a s UI úzce integrovaným do frontendu celé služby.
Na první pohled nejsnazší cestou by bylo vytvořit samostatný frontend pro tlačítko, který by pak bylo možné integrovat do libovolné služby, ovšem tuto cestu jsme rychle zavrhli z jednoduchého důvodu: množství provozu.
Hlavní stránka Seznam.cz průměrně obsluhuje přibližně 1 400 požadavků za sekundu (požadavky na statický obsah nepočítaje), ve špičkách se toto číslo dokáže vyšplhat i nad 3 000. Náš odhad očekávané běžné zátěže v případě spuštění na všech službách Seznamu a službách partnerů byl přibližně 6 000 požadavků každou sekundu.
Jedním z problémů vyplývajících z takové zátěže je škálování databáze. Databázový cluster pro hlavní stránku seznam.cz je postaven způsobem a na technologiích, které vyhovují potřebám a způsobu použití pro hlavní stránku, ovšem pro toto tlačítko je vhodnější zvolit řešení, které neposkytuje sice některé rozšířené funkcionality, ale umožňuje lepší horizontální i vertikální škálování.
Jelikož jsme chtěli zvolit řešení, které bude možné flexibilněji a snadněji škálovat, vysoká zátěž nebude mít negativní dopad na hlavní stránku seznam.cz a bude postavené na vhodnějších technologiích pro tuto službu, rozhodli jsme se jít cestou samostatné služby, která bude úplně oddělená od hlavní stránky seznam.cz.
Vytvoření plánu boje
Než jsme se pustili do tvorby služby, nebo návrhu architektury, bylo nejdřív nutné si zjistit potřebné informace pro výběr vhodných technologií, zajistit dostupnost potřebného množství výpočetních zdrojů a udělat si seznam všech drobných úkolů, co se musí splnit, aby bylo možné službu spustit.
S výběrem vhodné databáze jsme zašli za sysadminy Skliku, jelikož mají nejvíc zkušeností s provozem úložiště pro službu, která má řádově tak vysokou zátěž, jako jsme očekávali v plném nasazení a spuštění. Tou dobou připravovali do Seznam cloudu SCIF (Seznam Compute Infrastructure) službu MySQL/MariaDB as a Service. Po krátké konzultaci ale vyplynulo, že použití MySQL/MariaDB pro tuto službu by nebylo ideálním, a bylo nám doporučeno použít CouchBase, do začátku se třemi servery zapojenými do clusteru v každém datacentru a XDCR replikací mezi datacentry.
Jelikož řeč byla o třech celých serverech na každé datacentrum, což se vymykalo provisioningu, který jsme tou dobou používali pro stroje v OpenStack části SCIFu, následovala porada s týmem SCIFu, abychom získali potřebné výpočetní zdroje. Navzdory tomu, že se náš požadavek vymykal běžným očekáváním, získali jsme potřebné servery bez problémů a zádrhelů ještě ten samý den.
Následně byli do diskuse zapojeni sysadmini provozující hlavní stránku seznam.cz, a došli jsme k dohodě, že databázi budou provozovat sys admini hlavní stránky s konzultací a pomocí sysadminů Skliku, zatím co komponenty služby (backend, frontend, …) bude ve SCIFu provozovat a také mít za ně odpovědnost přímo vývojový tým. Celý Seznam.cz postupně přechází na model, kde v interním cloudu provozuje každou službu vývojový tým, který za ní stojí a tato služba se jevila jako výborný kandidát pro seznámení celého týmu s tímto novým přístupem. Důvodem, proč razíme tuto filozofii, je snazší a efektivnější řešení případných problémů, které zároveň vyžaduje zapojení menšího množství lidí.
Po vyřešení těchto největších překážek jsme dali dohromady a prioritizovali seznam věcí, které je nutné zařídit nebo domluvit (včetně samotného vývoje). Tento jsme seřadili dle priorit a závislostí, seskupili do skupin úkolů pro jednotlivé členy týmu podílejících se na službě a pustili jsme se do vytváření služby.
Návrh architektury a komunikačního schématu
Prvním bodem na seznamu našich priorit bylo navrhnutí hrubé architektury celé služby a komunikačního schématu pro komunikaci mezi jednotlivými částmi. V této části jsme provedli pouze návrh z nejvyšší úrovně; jednak bylo zbytečné jít do hloubky vzhledem k jednoduchosti služby, a jednak bylo hlavním cílem tohoto kroku si ujasnit komunikační schéma.
V návrhu jsme izolovali tři hlavní komponenty:
- backend, který obsahuje business logiku a práci s databází
- statický skript pro vložení do stránky, komunikující s frontendem a backendem (dále jen klient)
- frontend, který zajišťuje hlasování pokud prohlížeč zablokuje přístup ke cookie identifikující přihlášeného uživatele
Pro service discovery mezi frontendem a backendem jsme zvolili DNS service discovery které poskytuje Kubernetes. Cluster databáze, postavený na virtuálních strojích v OpenStacku, dostal přidělenou statickou IP adresu v interní síti. Pro komunikaci s frontendem, Kubernetes pody servírujícími klienta a backendem zvenku jsme se rozhodli nastavit směrování na našem vlastním L7 load balanceru Labrador.
Labrador je load labancer postavený na Envoy proxy, vytvořený v Seznamu na míru našich potřeb. Důvodem, proč používáme vlastní load balancer místo Kubernetes Ingress controlleru, je uniformnější přístup k podům v Kubernetes a virtuálním strojům v OpenStacku, ale hlavně lepší podpora naší síťové infrastruktury, s lepší podporou provozu služby napříč vícero datacentry, s automatickým failover v případě výpadku a rychlejší reakcí na případné výpadky podů v Kubernetes nebo změny konfigurace routování.
Pro komunikaci mezi backendem a frontendem/klientem jsme zvolili GraphQL. I když má GraphQL jistý overhead v porovnání s alternativami, součástí každého GraphQL rozhraní je formální specifikace daného rozhraní, kterou musí splňovat implementace a kterou může použít každý klient jako formu dokumentace. To nám umožnilo domluvit se na podobě komunikačního rozhraní a následně už nezávisle na sobě pracovat na jednotlivých částech bez nutnosti synchronizace do doby dokončení všech dílčích částí.
Návrh struktury databáze, architektury backendu, frontendu a klienta, i následná implementace již probíhala nezávisle na sobě.
Příprava infrastruktury a vývoj služby
Pro backend jsme zvolili jazyk Go a knihovnu GraphQL-Go. Frontend jsme vyvinuli pro node.js v TypeScriptu kompilovaném do ES modulů a byl postaven za pomocí HTTP serveru Fastify. Klient je také psán v TypeScriptu, ovšem kompilován do ES5 bez potřeby polyfillů (cíleno na kompatibilitu s IE11) a ubalen pomocí Webpacku. Detaily ale nejsou předmětem tohoto již tak obsáhlého článku, takže možná příště.
Kromě napsání kódu bylo nutné ale zařídit i několik dalších věcí, aby bylo možné službu později spustit. Toto probíhalo současně s vývojem.
V první řadě jsme kontaktovali náš dedikovaný bezpečnostní tým a upozornili je, že připravujeme novou službu, kterou chceme v blízké době spustit. Bezpečnost je komplexní, rozsáhlá a neustále se měnící oblast, takže nám vyhovuje mít dedikovaný tým pro tuto oblast než doufat, že žádný vývojář nikdy nezpůsobí bezpečnostní problém během vývoje.
Následně jsme vyhradili IPv4 a IPv6 adresy pro novou službu pro každé data centrum, nastavili DNS záznamy a vygenerovali SSL certifikáty pomocí Let's Encrypt. Správu, generování a obnovu SSL certifikátů nám zajišťují interní nástroje, z kterých následně přebírá certifikáty Labrador a zajišťuje SSL terminaci pro služby. Jelikož všechny služby provozujeme pouze ve vlastním cloudu ve vlastních data centrech, nemusíme pak již řešit šifrování komunikace mezi komponentami v interní síti (provozní prostředí je síťově oddělené od vývojářského prostředí).
Zatím co sys admini připravovali provozní Couchbase cluster pro naše použití, poslali jsme žádost o vytvoření namespaců v Kubernetes clusterech. Obecně pracujeme se službou ve třech prostředích – dev (vývojové prostředí), kde probíhá vývoj a můžeme experimentovat, staging (předprodukční prostředí), kde můžeme v případě potřeby otestovat aktuální sestavení, jestli je vhodné pro nasazení, a production (produkční prostředí), které je veřejně přístupné z internetu. Pro každé prostředí máme, pochopitelně, jinak nastavené limity, např. pro vývoj nám stačí pouze jeden pod pro každou Kubernetes službu, a pro každé prostředí jsou adekvátně nastavena přístupová práva.
Během několika pracovních dní jsme měli prvotní implementaci jednotlivých komponent. Pro první test jsme je spustili na svých pracovních noteboocích a nasměrovali jejich komunikaci mezi sebou. I když se objevily nějaké drobné chyby, byli jsme schopni problémy odstranit ještě ten samý den.
Byl čas se přesunout do cloudu.
CI a CD ve vývojovém, předprodukčním a produkčním prostředí
Pro organizaci kódu používáme self-hosted GitLab, který již obsahuje podporu pro CI. Jelikož tuto podporu CI běžně využíváme i pro jiné služby a máme dedikovanou sadu CI runnerů v OpenStacku pro potřeby hlavní stránky seznam.cz, použili jsme ji i pro tuto službu.
Prvním krokem bylo vytvoření servisních účtů (service account) v namespacích Kubernetesu pro vývojové a předprodukční prostřední a následně nakonfigurování těchto clusterů v GitLabu pro skupinu projektů této služby (každá komponenta má vlastní projekt, které jsou spolu v jedné skupině).
Pro CI a CD do SCIFu máme v Seznamu existující sadu scriptů pro balení a nasazení, takže jsme zvolili tyto místo obecnějších Auto DevOps, které GitLab nabízí.
Během jednoho pracovního dne jsme tak připravili funkční CI pro vývojové a předprodukční prostředí pro všechny čtyři komponenty (konfiguraci pro load balancer spravujeme jako samostatnou komponentu v samostatném projektu) a měli jsme funkční aplikaci běžící v cloudu… pro interní testy. Pro lepší pohodlí jsme ještě nastavili anotace pro integraci s GitLab Environments abychom mohli z jednoho prostředí sledovat proces nasazení i stav nasazených podů, nebo v případě potřeby pohodlně spustit interaktivní terminál v libovolném z nasazených podů přímo z rozhraní GitLabu.
Pro účely nasazení do produkce do všech data center bylo nutné upravit generování manifestů a postupů nasazování, které se liší od nasazování do vývojového nebo předprodukčního prostředí. Kromě různých malých detailů se jedná např. o spuštění roll-outu nasazení manuálně.
Spuštění služby
Službu jsme měli v produkčním prostředí a dostupnou z internetu, ale ještě jsme nebyli připraveni ji spustit.
Pro každou provozovanou službu máme jisté SLA a SLO, se splněním kterých nám pomáhá monitoring komponent pomocí nástroje Prometheus, který sbírá monitorovací metriky a tyto zpřístupňuje pro další zpracování. Sbírané metriky vizualizujeme pomocí nástroje Grafana a používáme je pro alerting v případě problémů. Trik je nastavit vhodné metriky, monitoring a alerting tak, abychom měli čas reagovat již když se objevují první symptomy blížícího se problému.
Předposledním krokem před samostným spuštěním už bylo pouze se domluvit, kdo a jak bude reagovat v případě problémů v provozním prostředí. Následně jsme ještě provedli migraci dat ze starého řešení do nového a mohli jsme spustit službu.
Samotné spuštění služby pak proběhlo přepnutím flagu v nastavení hlavní stránky seznam.cz, načež začala hlavní stránka vykreslovat tlačítko za pomoci klientského skriptu místo používání vlastního UI. Pokud nesledujete pravidelně, jak se mění kód hlavní stránky seznam.cz, tak jste si spuštění ani nevšimli, což pro nás značí úspěch.
Současnost a budoucnost služby
Služba již nějakou dobu stabilně běží a postupně ji integrujeme do dalších služeb Seznamu. V současnosti je již používána např. na zpravodajských portálech Seznam Zprávy a Novinky.cz a lifestyle magazínech ProŽeny.cz či Garáž.cz. Pořád je v plánu, aby se tlačítko používalo také na službách, které nevlastní a neprovozuje Seznam, ale to je běh na delší trať.
Během provozu jsme ladili také technické problémy, ať už s replikací dat mezi clustery Couchbase v jednotlivých data centrech, nebo s ověřením přihlášení aktuálního uživatele, jelikož prohlížeče čím dál tím víc omezují přístup ke cookies v kontextech třetích stran… z pochopitelných a často dobrých důvodů. O tom ale zase někdy příště.