Úvod

Záměrem TA ČR je vybudovat a provozovat SISTA jako cloud native systém. Takový systém má být odolný, snadno kontrolovatelný, robustní a nezávislý na konkrétních dodavatelích technologií. Základní architektonické uspořádání SISTA jako množiny spolupracujících služeb vyžaduje kvalitní a aktuální dokumentaci jednotlivých částí.

Tento dokument nastavuje pravidla pro platformu SISTA, na které jednotlivé mikroslužby poběží, aby se měli dodavatelé o co opřít.

Cílem je:

  • Mít na jednom místě popis pravidel platformy SISTA.

  • Ustanovit si základní best practices pro vývoj platformy SISTA.

  • Nastavit pravidla pro monitoring, logging nebo metriky, která jsou dále využita pro alerting.

  • Nastavit bezpečnostní pravidla pro vývoj platformy SISTA.

  • Vytvořit platformu, která umožní snadné nasazování (mikro)služeb do SISTA.

Dokument se do budoucna může (a bude) měnit v závislosti na potřebách dodavatelů a TA ČR.

1. Platforma SISTA

1.1. Poskytované služby Dev týmům

  • GIT repozitáře.

  • GitLab Pipelines - separátní build a deployment služeb.

  • Postgres DB - stačí uvést do konfiguračního souboru deploymentu a databáze se v clusteru inicializuje pomocí platformy.

  • Možnost zobrazit si logy v konzoli Google Cloudu.

  • Možnost zobrazit si monitoring v konzoli Google Cloudu.

  • Možnost vytočit více testovacích clusterů (prosíme s mírou).

  • Máte-li nějaké aditivní požadavky ve prospěch SISTA, neváhejte kontaktovat IDP tým.

  • Nejasnosti lze řešit v rámci SISTA DevOps nebo schůzek ArchaTýmu.

Note Pokud bude potřeba něco navíc, tak je možné domluvit se s platformním týmem prostřednictvím ops@tacr.cz.

1.2. Internal Developer Platform (IDP)

Platformní tým poskytuje Dev týmům Internal Developer Platform (IDP) zahrnující správu vrstev kompletního provozu technologické platformy včetně CI/CD pipelines.

Internal Developer platform zahrnuje správu:

  • infrastruktury,

  • monitoringu,

  • alertingu,

  • nasazování prostředí, clusterů a služeb,

  • CI/CD pipelines,

  • repozitářů,

  • uživatelů a jejich oprávnění,

  • pravidel a zásad pro psaní dokumentace,

  • pravidel a zásad pro psaní integračních testů,

  • Site Reliability Engineering (dohled, incident response).

Platformní tým připravuje šablony předpisů kontejnerů a poskytuje konzultace Dev týmům při prvotních nasazeních.

Samotná IDP platforma má tyto zásady:

  • zásady před procesy (policies over processes),

  • automatizace před manuálností (automation over manual),

  • sdílení před kopírováním (shared over copy & paste),

  • spokojenost vývojářů (developer experience - DX):

    • snadné pro použití:

      • dva kroky maximálně - konfigurace a aplikace,

    • bezpečnost:

      • idempotentní,

      • samoozdravné (self-healing),

    • možnost přepnout na nižší kontext, pokud je to potřeba,

  • podpora vývojářských týmů:

    • dohled nad kontrolou = ask for forgiveness, not permission.

Note Celou platformu se snažíme koncipovat tak, aby se dodavatelé mohli soustředit na agilní vývoj služeb. Každopádně kooperace mezi Dev a Ops týmy je nezbytná pro pěstování kultury DevOps.

1.3. Repozitáře – GitLab

Ops tým zajistí repozitáře a přístupy do nich pro Dev týmy.

Co služba, to repozitář. Ops tým připraví šablonu jak pro repozitář, tak pro pipeliny.

Repozitář platformy, kterou mohou vyvíjet i Dev týmy.

SISTA konzumuje prostředky a zdroje Google v rámci Google Cloud. S určitou mírou využíváme i managed komponenty Google.

1.4. Messaging

Messaging se řeší pomocí Google Pub/Sub. Služby vysílají (Publisher) informaci na nějakém topicu a jiné služby, které na daném topicu poslouchají, si informaci zpracují po svém (Subscriber).

“Např. služby, které pracují s informací o osobách, poslouchají na daném topicu, a pokud se v rejstříku osob (RO) změní informace o osobě, tak RO vyšle informaci a Subscriber služby si změněnou informaci uloží.”

Pro komunikaci s messaging službou se používá DAPR middleware, který je součástí platformy, viz podrobnosti v HOWTO Platform.

2. Běhová prostředí

2.1. Architektura

Platforma je vytvářena jako Infrastructure as Code (IaC). Je tvořena pomocí třech stavebních kamenů:

  1. Environment - část zajišťující tvorbu jednotlivých projektů v Google Cloudu, včetně databázového stroje,

  2. Cluster - do environmentu se může nasadit více clusterů,

  3. Service - uvnitř clusterů může běžet více services.

Platforma by měla být do budoucna připravena k využití i pro jiné státní agentury a zvažujeme její uvedení do open-source.

2.2. Prostředí

Pro potřeby projektu SISTA udržuje TA ČR v Google Cloud (GC) běhová prostředí TESTING, STAGING a PRODUCTION. Využití kontejnerizace umožňuje, aby se jednotlivé komponenty spouštěné v různých prostředích lišily pouze konfigurací a dostupnými zdroji – takové uspořádání považujeme za optimální:

  • Typ PRODUKČNÍ (PRODUCTION) = produkční, ostré běhové prostředí (SISTA pro koncové uživatele). Existuje pouze jedno.

  • Typ NEPRODUKČNÍ = instancí těchto prostředí může být mnoho, např.:

    • subtyp STAGING = prostředí určené pro integrační testy mezi službami:

      • mělo by obsahovat skutečná data z produkčního systému, podle potřeby anonymizovaná nebo pseudonymizovaná, a to pravidelně aktualizovaná,

    • subtyp TESTING = prostředí určené pro testy nad úrovní jednotkových testů:

      • musí obsahovat pouze testovací data.

  • Ops tým je zodpovědný za přípravu a údržbu všech těchto typů prostředí. Dev týmy jsou spoluzodpovědné za přípravu a údržbu dat a aplikací ve všech typech prostředí.

  • Podle specifických potřeb služby může být přidána další instance neprodukčního prostředí (např. IMPORT pro výpočetně náročnou přípravu dat, která se následně přesunou do PRODUCTION prostředí).

  • DevOps týmy jsou společně zodpovědné za určení podmínek, při jejichž splnění je možné nasazení do každého prostředí. Oba týmy jsou také zodpovědné za kontrolu, že dané podmínky jsou splněny.

2.3. Deployment

  • Adresáře GIT (repozitáře) obsahují definice deploymentů konkrétních služeb. Co soubor, to služba.

  • Definovanou pipeline je možné spustit v prostředí GitLab Pipelines.

  • Pro kontinuální nasazování (CD) se používá nástroj ArgoCD.

  • Detailnější popis souboru deployment.yaml lze nalézt zde.

2.4. CI/CD

  • Platformní tým poskytne Dev týmům ve službě GitLab Pipelines nástroje pro deploy do prostředí GC.

  • Existuje automatické nasazení služeb do clusterů (více zde).

  • Soubor .gitlab-ci.yml bude možný upravit tak, aby se mohly spustit jednotkové testy nasazovaných služeb.

  • Platformní tým zakazuje definovat proměnné deklarující konkrétní verze (např. imagů nebo knihoven) na úrovni grup nebo repozitářů. Tyto věci budou definované v souboru .gitlab-ci.yml (popřípadě v příslušném souboru pro CI/CD).

  • Platformní tým poskytne ukázky souborů deploymentů, tedy předpisy pro nasazení jednotlivých služeb. Environment variables se zapisují právě do tohoto deployment souboru.

  • Platformní tým bude zajišťovat správu a řízení tajemství (secrets).

2.5. Zabezpečení komunikačních kanálů mezi službami

Pro komunikaci mezi službami je zavedeno ověření se pomocí API klíčů, který autentizuje služba Apigov.

2.6. Databáze

  • Momentálně podporujeme pouze Postgres DB. Pokud do budoucna vznikne používat nějakou další, tak kontaktujte ops tým.

  • Každá služba, která potřebuje databázi musí obsahovat v souboru deploymentu

    • POSTGRES_USER: $(.POSTGRES_USER)

    • POSTGRES_URL: "jdbc:postgresql://127.0.0.1:5432/$(.POSTGRES_DB)?charSet=UTF-8&prepareThreshold=0"

Komunikace služby a databáze je řešena uvnitř v Google Cloud přes CloudSQLProxy.

3. Kubernetes doporučení

  • přeloženo pomocí DeepL (z důvodu vyžadované češtiny).

Note TODO: Upřesnit obecné security požadavky.

3.1. Datum a čas

Všechny uzly Kubernetes musí být synchronizovány s NTP.

V celém clusteru a všech jeho součástech by se mělo používat jedno časové pásmo (nejlépe UTC). (To může vyžadovat specifickou konfiguraci časové zóny v imagích.) Zdraví celého clusteru a jeho systémových komponent by mělo být neustále monitorováno prostřednictvím logování, metrik a událostí Kubernetes a upozorňování na jejich anomálie.

3.2. Uzly a zóny

Pracovní uzly mají být řádně označeny zónami dostupnosti, jak je to pro danou platformu vhodné. Zatížení aplikace má tato označení používat pro omezení proti afinitě a šíření topologie podů.

Tainty uzlů a další konstrukce omezující využití uzlů podem by se měly používat jen v případě, že je to opravdu nutné, protože omezují volnost clusteru při přidělování pracovní zátěže a komplikují správu.

Funkce, jako jsou vícezónové clustery, lze opatrně používat pro zvýšení odolnosti, ale neměly by být považovány za významné zvýšení čisté spolehlivosti. Zkušenosti ukazují, že vzhledem k tomu, že architektura aplikací v systému Kubernetes obecně nezná základní topologii, a k tomu, že je běžné, že systém Kubernetes nedokáže správně identifikovat výpadek zóny (a zejména jeho částečnou degradaci) jako takový, je skutečná hodnota vícezónových klastrů mnohem nižší, než by mohlo být naivní očekávání. Konkrétně je třeba pečlivě zvážit omezení používání trvalých svazků na vlastní zónu, které ukládají někteří poskytovatelé, a jejich potenciální dopad na stabilitu pracovní zátěže.

Škálování clusteru (přidělování nebo uvolňování pracovních uzlů za účelem správy kapacity clusteru) by mělo být prováděno s maximální opatrností a omezeními rozsahu a rychlosti, aby se zabránilo plýtvání zdroji, rozbíjení clusteru a nestabilitě.

3.3. Jmenné prostory (Namespaces)

Jmenné prostory Kubernetes je obor názvů, doména kvót zdrojů a koncept ochrany přístupu. Jako takové by se měly používat. Konkrétně v produkčních a stagingových prostředích:

  • Přednostně je jmenný prostor určen ke spuštění kódu aplikace pouze od jednoho vývojového týmu (neuvažuje se o skladových komponentách třetích stran nebo o těch, které jsou výslovně navrženy jako sdílené) . Jeden vývojový tým může používat více jmenných prostorů, pokud je to odůvodněno technickými, architektonickými nebo organizačními argumenty.

  • Každý jmenný prostor má mít definované kvóty prostředků.

  • Přístup do jiných jmenných prostorů má být omezen jak pro účty služeb, tak pro uživatele.

Konkrétně by komponenty měly být nasazeny do samostatných jmenných prostorů, pokud:

  • patří k nesouvisejícím službám;

  • jsou vyvíjeny oddělenými týmy (pokud neexistuje jiný technický důvod, například potřeba nasadit více kontejnerů v jednom podkladu);

  • mají nekompatibilní vzorce využití prostředků (např. služba se stabilními prostředky a dávková úloha s dynamickými špičkami prostředků);

  • Pokud není možné nebo praktické oddělit prostředí (dev, testing/staging, prod) do nezávislých clusterů, měly by mít nezávislé jmenné prostory;

  • Pokud existují argumenty pro oddělení z hlediska bezpečnosti / přístupu.

Naopak by se mělo uvažovat o nasazení komponent ve stejném jmenném prostoru, pokud

  • to vyžaduje architektura nebo implementace;

  • jsou logicky nebo organizačně propojeny;

  • mohou oprávněně soutěžit o stejný fond prostředků.

3.4. Logování a metriky

Všechny logy z clusteru by měly být shromažďovány v jediném zásobníku logů s přiměřeným uchováváním historie (alespoň týden). Implementace zásobníku by měla být nezávislá na clusteru, aby bylo možné shromažďovat logy i v případě, že je cluster přetížen nebo degradován.

Podobně mají být systémové metriky (využití prostředků, stav Kube a metriky aplikací) přiváděny do nezávislého zásobníku metrik.

Metriky a logy by měly být k dispozici mechanismům pro výstrahy založeným na pravidlech.

Logy a zásobníky metrik mají být centrálně spravovány týmem platformy / provozu / SRE, včetně monitorování jejich stability / nadměrného využívání / zneužívání.

Má být definován soubor zásad, které se týkají: Konvence pro logování:

  • preferované formáty zpráv (ideální je JSON, musí být validní). Tam, kde to není možné (např. SW třetích stran), nakonfigurovat výstup logování, pokud je to možné, pro maximální užitečnost a snadné rozebírání používanými vyhledávacími prostředky logů.

  • limity (délka) zpráv logu

  • práce s víceřádkovými zprávami - možnost logovat víceřádkové zprávy je užitečná např. pro zpětné dohledávání. Často nelze předpokládat, že zprávy se stejným časovým razítkem si zachovají pořadí v zásobnících logů.

  • úrovně logů (definované, s preferovaným použitím pro každou z nich - např. žádná legitimní operace, včetně chyb uživatele, by neměla vytvářet logy na úrovni ERROR, nebo každá transakce by měla být loggována na úrovni INFO).

  • objem logů (např. limity pro jednotlivé komponenty , obsah zpráv - jak žádoucí (ID transakcí a další podstatný kontext), tak nežádoucí (tajemství, osobní informace, zprávy s nízkou informační hodnotou nebo vysoce opakující se zprávy).

  • Používání štítků zpráv (v zásobnících logů založených na sadách štítků) - žádné neomezené hodnoty štítků a omezení počtu tuplů štítků.

Konvence pro metriky aplikací (předpokládá se, že systémové metriky spravuje platforma):

  • četnost škrábání (scraping);

  • konvence pojmenování a sémantické konvence;

  • jednotky, škálování, typy metrik (měřidla vs. čítače, absolutní / relativní);

  • omezení týkající se štítků, jako je tomu výše u logů;

  • celková produkce metrik a problémy s šířkou pásma a náklady na scraping.

4. Zatížení služby Kubernetes

Následují požadavky na užitečné zatížení aplikace běžící v systému Kubernetes:

4.1. Používejte pouze verzované, neměnné image kontejnerů

Veškeré užitečné zatížení běžící v systému Kubernetes musí používat neměnné, předem sestavené image kontejnerů s jedinečným označením verze. Pokud je to možné, má být stejný image použit pro testování vývoje, staging (pokud existuje) a produkci. Běhový kontejner by neměl provádět žádnou kompilaci ani dynamické načítání kódu mimo kontejner.

To umožňuje i certifikace neměnných imagů, pokud je to potřeba/účelné.

4.2. Načítání všech imagů z dobře definované sady úložišť imagů

Každé vydání image musí mít jedinečnou identifikaci.

Interně vytvářené image se mají načítat z interního úložiště image.

Všechny image třetích stran by měly být načítány z důvěryhodného úložiště. Nedoporučuje se automatická aktualizace nebo používání značky „nejnovější„ – každé vydání image by mělo být nejprve otestováno v nižších prostředích a poté explicitně použito.

4.3. Komunikační protokoly

Pokud je to možné, měly by služby používat komunikační protokoly založené na jediném portu TCP/UDP a pro zjišťování služeb používat Kube DNS.

Veškerá komunikace by měla využívat schéma opakování / opětovného připojení při výpadku komunikace, protože kontejnery a pody mohou kdykoli vypadnout. Vzhledem k tomu, že služba může být po opětovném připojení převzata jinou replikou serveru, musí být všechny relace sdíleny napříč sadou replik nebo musí být k dispozici jiné mechanismy pro zpracování relací.

Mechanismy opakování musí pečlivě rozlišovat mezi obnovitelnými a neobnovitelnými chybami a používat vhodnou formu prevence opakovacích bouří, např. exponenciální backoff a omezený počet opakování.

Pokud jsou používány koncové body, které se překládají na více adres DNS, musí být dána šance všem adresám. Ve schématech opakování spojení nesmí opakování přijít stále na stejnou IP adresu a musí se provést nové rozlišení DNS a výběr adresy. To má chránit před scénáři, kdy se jedna IP adresa koncového bodu služby stane nedosažitelnou (z důvodu jejího zhoršení, výpadku nebo zastarání), ale klient nikdy nevyzkouší ostatní možnosti.

Pokud se používá více připojení, je třeba dbát na omezení počtu připojení na rozumně omezené hodnoty, např. pomocí fondů připojení. (Zvláště důležité je to u databází.)

Tam, kde se jedná o vyrovnávání zátěže, je třeba dbát na to, aby bylo porušeno tradiční synchronní paradigma (jedna probíhající transakce na jedno spojení TCP). Konkrétně se to často děje v případě protokolu HTTP/2 a různých dalších asynchronních komunikačních vzorů, např. postavených nad WebSocketem.

TODO: zabezpečení (security)

4.4. Nezávislost na komponentách

Komponenty musí být možné nasadit nezávisle. Musí existovat jasná, dobře definovaná cesta k návratu jakéhokoli nasazení do předchozího stavu. Pokud nasazení komponenty přichází s nevratnými změnami, jako je změna schématu SQL, musí být stále zachována kompatibilita s komponentou. Nekompatibilní změny musí být řešeny podporou verzovaného API nebo explicitním migračním procesem.

4.5. Stav aplikace

Aplikace nasazené v Kubernetes musí být buď bezstavové, nebo pokud jsou stavové, musí být jejich stav uložen ve vhodném úložišti nezávislém na životnosti podů (databáze, fronta zpráv, trvalý svazek souborového systému, COS …​). Bezstavové komponenty kritické pro běh musí mít více replik (nejlépe pomocí Kube Deployment). Pokud je to možné, mělo by být dodrženo pravidlo N+2 pro počet replik produkční služby (tj. počet replik má být takový, jaký je potřeba ke zvládnutí zátěže + alespoň 2). Stavové komponenty, pokud je to možné, by měly být založeny na redundantní implementaci v clusteru Kube nebo mimo něj.

Konkrétně by se ve stavu síťových spojení neměl skrývat žádný „implicitní stav komponenty“, protože ta se mohou kdykoli přerušit a po opětovném připojení může spojení vést k jiné replice. Pokud se tedy používají nějaké relace nebo jiný druh kontextu, je třeba je sdílet mezi replikami serverové komponenty.

4.6. Životní cyklus podů (Pod Lifetime)

Všechny kontejnery musí být navrženy a nakonfigurovány se správnou životností a správou stavu. Konkrétně mají mít všechny kontejnery

  • implementována sonda připravenosti, která potvrzuje, že kontejner je připraven přijímat provoz (je inicializován, všechny závislosti jsou k dispozici, není přetížen);

  • sondu životnosti, která tvrdí, že kontejner je ve zdravém nebo obnovitelném stavu (inicializace, inicializace nebo obnovitelná chyba);

  • PreStop hook nebo perioda ukončení nakonfigurovaná tak, aby umožnila dokončení čekající transakce při šetrném vypnutí;

  • volitelně počáteční nasazení nebo startup hook pro ochranu spuštění kontejneru, je-li zdlouhavé.

Je důležité zjistit správné závislosti sondy připravenosti. Ty by měly pocházet od nejjednodušších (bez závislostí) služeb až po ty postupně složitější, přičemž každá z nich by měla záviset pouze na jedné vrstvě svých závislostí, aby se předešlo různým závislostním deadlockům.

4.7. Konfigurace

Veškeré konfigurační a integrační parametry kontejnerů specifické pro dané prostředí je třeba zohlednit v proměnných prostředí nebo konfiguračních souborech uvedených ve specifikaci kontejneru v nasazení nebo jiném objektu Kube, případně v přidružené mapě konfigurace nebo v tajence. Pouze skutečné přihlašovací údaje by měly být v Secretech. Konkrétně by do Secrets neměly jít žádné informace o koncových bodech / topologii / závislostech (typickým negativním příkladem je kompletní řetězec pro připojení k databázi, který kombinuje koncový bod IP s pověřeními).

Veškerá (netajná) konfigurace by měla být považována za kód a měla by být verzována.

4.8. HA model a odolnost

Nejdůležitější je, aby služba byla navržena s ohledem na HA a aby bylo jasné, jak může na příslušných vstupech a výstupech pracovat více replik.

Pokud je to možné, měly by všechny komponenty existovat ve více replikách a pro vyrovnávání zátěže používat konstrukci Kubernetes Service. Všechny komponenty by měly implementovat vhodný mechanismus obnovy při ztrátě spojení ze závislosti, např. opakování pokusu s exponenciálním backoffem. Stavové komponenty mají implementovat HA způsobem specifickým pro danou architekturu.

Pody by měly případně používat vhodná anti-afinitní pravidla, aby se omezily scénáře závislosti celé služby na jednom pracovním uzlu.

4.9. Zdroje (Resources)

Všechny kontejnery mají deklarovat hodnoty požadavků a limitů na zdroje CPU a paměti, a pokud jsou k dispozici, také efemérní úložiště. Poměr limitu a požadavku pro CPU by neměl být větší než předem stanovená konstanta (často používaná hodnota je 5).

Hodnoty požadavku a limitu paměti by měly být stejné pro všechny dlouhodobě běžící komponenty, aby se předešlo scénářům selhání smyčky na pracovních uzlech s nedostatkem paměti. Nedodržení tohoto pravidla může vést ke zbytečným nákladům na zdroje nebo k nestabilnímu prostředí s neustálými restarty podů. Výjimky jsou možné, ale je třeba je zdůvodnit a podložit analýzou.

Efemérní (rychle pomíjející - pozn. Mistra Hanuše) úložiště je dočasný diskový prostor používaný pro image kontejnerů, některé logy, mezipaměť a svazky emptyDir.

Přítomnost a neporušenost těchto limitů je dobré vynutit vhodným mechanismem (výchozí nastavení pro jednotlivé jmenné prostory, kontrola přípustnosti, monitorování).

Snažte se tyto hodnoty velmi přesně specifikovat a ověřit je pomocí skutečného testování zátěže.

Při specifikaci prostředků vyjádřených v bajtech (paměť a efemérní úložiště) nepoužívejte zlomková čísla v kombinaci s jednotkami jako M nebo Mi (tj. místo 1,3G použijte 1300M) - jinak to vede k tomu, že skutečná hodnota prostředku je veličina „zlomek bajtu“, což znečišťuje různé stavové informace nečitelnými hodnotami „milibajtů“.

4.10. Viditelnost (Visibility)

Všechny kontejnery mají dodržovat společnou sadu pravidel pro logování, aby bylo možné sledovat normální i chybový provoz. Konkrétně:

  • formát řádků logů by se měl řídit dohodnutým vzorem

  • Logů by měly mít úrovně (např. debug, info, warn, error).

  • Zprávy s úrovní chyby mají být vyhrazeny pro stavy, na které lze upozornit (např. žádné chyby uživatele).

  • Zprávy by měly umožňovat sledování jednotlivých transakcí, např. uvedením jedinečného ID transakce.

  • Celkový objem logů by se měl pohybovat v nějakých dohodnutých mezích, aby nedošlo k přetížení zásobníku logů.

  • Úroveň verbozity logů by měla být konfigurovatelná alespoň na úrovni konfigurace kontejneru.

  • Nemají být zaznamenávány žádné přihlašovací údaje ani jiná tajemství. Tam, kde je to nevyhnutelné, by měly být tyto informace redigovány.

  • Pokud je to možné, nemají být zaznamenávány žádné osobní údaje, aby se předešlo obavám o ochranu soukromí. Místo toho lze použít interní ID záznamů.

Pokud je to vhodné, pody by měly vytvářet metriky aplikace (např. objem transakcí, zatížení, latence, chyby).

5. Kubernetes Recommendations - v originále

Note TODO: Specify general security requirements.

5.1. Date and Time

All Kubernetes nodes must be NTP-synchronized.

Single timezone (preferably UTC) should be used throughout the cluster and all its components. (this may require a specific timezone configuration in the images.) The health of the cluster overall and its system components should be constantly monitored via logging, metrics and Kubernetes events, and alerting on their anomalies.

5.2. Nodes and Zones

The worker nodes are to be properly labelled with availability zones, as appropriate for the platform. The application payload is to use these labels for anti-affinity and pod topology spread constraints.

Node taints and other constructs restricting the node usage by a pod should be used only when really necessary, as they limit the freedom of the cluster to allocate workload and complicate the administration.

Features like multi-zone clusters can be cautiously used for increased resiliency, but should not be considered a significant net reliability increase. Experience shows that due to the application architecture in Kubernetes being generally unaware of the underlying topology, and of the fact that it is common for Kubernetes not to be able to identify a zone outage (and especially its partial degradation) as such properly, the real value of multi-zone is much lower than what a naive expectation might be. Specifically, the restrictions on persistent volume usage to their own zone, imposed by some providers, and their potential impact on the workload stability are to be weighed carefully.

Cluster scaling (allocation or releasing worker nodes to manage the cluster capacity), should be performed with extreme caution and limits on scope and speed, to avoid resource waste, cluster thrashing and instability.

5.3. Namespaces

Kubernetes namespace is a naming scope, a resource quota domain and an access protection concept. They should be used as such. Specifically, in production and staging environments:

  • Preferably, a namespace is to run application code from a single development team only (not considering stock third-party components or ones explicitly designed as shared) . A single development team may use multiple namespaces where justified by technical, architectural or organizational arguments.

  • Each namespace is to have resource quotas defined.

  • Access to other namespaces is to be restricted both for service accounts and for human users.

Specifically, components should be deployed to separate namespaces if:

  • They belong to unrelated services;

  • They are developed by separate teams (unless there is a technical reason otherwise, like needing to deploy multiple containers in a single pod);

  • They have incompatible resource usage patterns (e.g. a resource-stable service and a batch job with dynamic resource peaks);

  • Where separation of environments (dev, testing/staging, prod) into independent clusters is not possible or practical, these should have independent namespaces;

  • Where there are security / access arguments for separation.

Conversely, the components should be considered for deploying in the same namespace if

  • This is required by architecture or implementation;

  • They are logically or organizationally related;

  • They can legitimately compete for the same resource pool.

5.4. Logging and Metrics

All the logs from the cluster should be collected in a single logging stack, with reasonable history retention (at least a week). The implementation of the stack should be independent of the cluster, to be able to collect logs even when the cluster is overloaded or degraded.

Similarly, the system metrics (resource utilization, Kube state and application metrics) are to be fed into an independent metrics stack.

The metrics and logs should be available to rule-based alerting engines.

The logging and metrics stacks is to be centrally managed by the platform / ops / SRE team, including the monitoring for its stability / overuse / abuse.

A set of policies is to be defined, relating: The conventions for logging:

  • preferred message formats (JSON is ideal, must be valid). Where this is not possible (e.g. third party SW), configure the logging output, if possible, for maximum usefulness and ease of parsing by the log search facilities used.

  • log message limits (length)

  • dealing with multiline messages - ability to log multiline messages is useful for things like backtraces. Often messages with the same timestamp cannot be assumed to retain order in logging stacks.

  • log levels (defined, with preferred usage for each - e.g. no legitimate operation, including user errors, should produce ERROR-level logs, or each transaction should be logged on INFO level)

  • log volume (e.g. per-component limits for , message content - both desirable (transaction IDs and other essential context) and undesirable (secrets, personal info, low-information or highly repetitive messages).

  • Usage of message labels (in logging stacks based on label sets) - no unconstrained label values and constraints on the number of label tuples.

The conventions for app metrics (system metrics are assumed to be managed by the platform): - scraping frequency; - naming and semantic conventions; - units, scaling, metric types (gauges vs. counters, absolute / relative); - restrictions on labels like above with the logs; - overall metric production and scraping bandwidth and cost concerns.

6. Kubernetes Payload

The following are the requirements for application payload running in Kubernetes:

6.1. Use Versioned, Immutable Container Images Only

All the payload running in Kubernetes is to use immutable, pre-built container images, with a unique version tag. Where possible, the same image is to be used for development testing, staging (if any) and production. The runtime container should not perform any compilation or dynamic loading of the code from outside the container.

This even allows for certification of the immutable images where needed/applicable.

6.2. Load All Images from a Well-Defined Set of Image Repositories

Every image release must have a unique identification.

The internally produced images are to be loaded from an internal image repository.

All the third-party images should be loaded from a trusted repository. Automatic updating or using the ‘latest’ tag is not recommended - every image release should be tested in the lower environments first and then explicitly applied.

6.3. Communication Protocols

Where possible, the services should use communication protocols based on a single TCP/UDP port and use Kube DNS for service discovery.

All communication should use a retry / reconnect scheme on communication breakdown, as containers and pods can go down any time. As a service may be picked up by a different server replica after reconnect, all sessions must be shared across the replica set, or other session handling mechanisms be in place.

The retry mechanisms must carefully distinguish between recoverable and non-recoverable errors, and use a suitable form of preventing the retry storms, e.g. exponential backoff and a limited retry number.

Where endpoints are in use that resolve to multiple DNS addresses, all the addresses must be given a chance. In connection retry schemes, the retry must not keep coming to the same IP address, and must perform a new DNS resolution and address selection. This is to protect against scenarios where one service endpoint IP address becomes unreachable (because of being degraded, down or stale), but the client never tries the other options.

Where multiple connections are in use, care must be taken to limit the connection counts to reasonable limited values, e.g. by using connection pools. (Especially important with databases.)

Where load balancing is involved, care must be taken when the traditional synchronous paradigm (one pending transaction per TCP connection) is violated. Specifically, that’s frequently the case with HTTP/2 and various other asynchronous communication patterns, e.g. built on top of WebSocket.

TODO: security

6.4. Component Independence

The components must be deployable independently. There must be a clear, well-defined path to rollback of any deployment to the previous state. Where a component deployment comes with irreversible changes, like a change of SQL schema, the component compatibility still must be maintained. Incompatible changes must be handled by supporting versioned API or an explicit migration process.

6.5. Application State

The applications deployed in Kubernetes have to be either stateless, or if stateful, their state has to be stored in a suitable storage independent of the pod lifetime (database, message queue, persistent filesystem volume, COS …). Runtime-critical stateless components must have multiple replicas (preferably using Kube Deployment). Where possible, the N+2 rule for the number of replicas of production service should be followed (i.e. the number of replicas is to be whatever it takes to handle the load + at least 2). Stateful components, where possible, should be based on redundant implementation in or outside the Kube cluster.

Specifically, no “implicit component state” should be hidden in the state of the network connections, as these can break any time and after reconnection, the connection may lead to a different replica. Therefore, if any sessions or other kind of context are in use, these need to be shared between the replicas of the server component.

6.6. Pod Lifetime

All containers have to be designed and configured with proper lifetime and health management. Specifically, all container are to have

  • readiness probe implemented, asserting that the container is ready to take traffic (it is initialized, all dependencies are available, it is not overloaded);

  • liveness probe asserting that the container is in a healthy or recoverable state (initializing, initialized, or recoverable error);

  • PreStop hook or termination period configured to allow pending transaction completion on graceful shutdown;

  • Optionally, initial deploy or startup hook to protect the container startup, if lengthy.

It is important to ascertain correct readiness probe dependencies. These should come from the most simple (dependency-less) services to those gradually more complex, each depending only on a single layer of its dependencies to avoid various dependency deadlocks.

6.7. Configuration

All the environment-specific configuration and integration parameters of the containers have to be factored out into environment variables or configuration files specified in the container specification in the deployment or other Kube object, or an associated Configmap or Secret. Only the actual credentials should go in Secrets. Specifically, no endpoint / topology / dependency information should go in Secrets (the complete database connect string, combining the IP endpoint with credentials, is a typical negative example).

All (non-secret) configuration should be treated as code and versioned.

6.8. HA Model and Resiliency

Most importantly, service should be architected with HA in mind and it should be clear how multiple replicas can operate on the respective inputs and outputs.

Where possible, all components should exist in multiple replicas and use the Kubernetes Service construct for load balancing. All components should implement a suitable recovery mechanism on connection loss from a dependency, e.g. retry with exponential backoff. Stateful components are to implement HA in architecture-specific ways.

Where applicable, the pods should use appropriate anti-affinity rules to reduce the scenarios of whole service dependency on a single worker node.

6.9. Resources

All containers are to declare CPU and memory resource request and limit values, and where available, also ephemeral storage. The limit to request ratio for CPU should not be more than a predetermined constant (5 is a value frequently used).

The memory request and limit values should be the same for all long-running components, to avoid crash-loop scenarios on worker nodes tight on memory. Not adhering to this rule can lead to unnecessary cost of resources or an unstable environment with continuous pod restarts. Exceptions are possible, but need to be justified and backed up by an analysis.

Ephemeral storage is the temporary disk space used for container images, some logs, caching and emptyDir volumes.

It is a good idea to enforce the presence and integrity of these limits through a suitable mechanism (per-namespace defaults, admission control, monitoring).

Try to be very specific about these values and verify them through actual load testing.

When specifying the resources expressed in bytes (memory and ephemeral storage), do not use fractional numbers in combination with units like M or Mi (i.e. instead of 1.3G use 1300M) - else this results in the actual resource value being a “fractional bytes” quantity, polluting various status information with unreadable “milibyte” values.

6.10. Visibility

All containers are to follow a common set of logging rules, to allow visibility into both normal and error operation. Specifically:

  • The logs line format should follow an agreed pattern

  • The log messages should have log levels (e.g. debug, info, warn, error)

  • The error-level messages are to be reserved for alertable conditions (e.g. no user errors)

  • The messages should allow tracing individual transactions, e.g. by including a unique transaction ID

  • The overall volume of the log messages should be within some agreed limits, not to overload the logging stack

  • The log verbosity level should be configurable at least on the container configuration level.

  • No credentials or other secrets are to be logged. Where inevitable, the information should be redacted.

  • If possible, no personal information is to be logged to avoid privacy concerns. Internal record IDs can be used instead.

Where appropriate, the pods should produce application metrics (e.g. transaction volume, load, latencies, errors).

7. Release notes dokumentu

Verze: 1.12.
Datum vydání: 20. 8. 2024.
Autor: Jiří Hanuš, Lukáš Kulička, jiri.hanus@tacr.cz, lukas.kulicka@tacr.cz.
Poznámka k verzi: Revize 08/24 konceptu platformy SISTA.


Detailní popis změn:

  • Revize k 08/24.

  • Oprava názvů souvisejících s GitLabem.

  • Změna již neexistujícího názvu Google Cloud Platform (GCP) na Google Cloud (GC).

Závěr

Dokument byl vytvořen pro potřeby TA ČR a jejich dodavatele pro projekt SISTA „Sdílený Informační Systém Technologické Agentury“.

V dokumentu se popisuje platforma vytvořená od IDP týmu a ukazuje se v něm možnosti a pravidla platformy. Pravidla obsahují správu GitLabu, pipelines, deployment a doporučení pro platformu v rámci Kubernetes.

Dokument byl vytvořen s využitím open-source projektů:

Termíny a definice

DevOps meeting

Exekutivní skupina zodpovědná za kontrolu a údržbu strategické a technologické architektury. Rada by měla reprezentovat všechny klíčové subjekty zainteresované v architektuře (stakeholder) a typicky sestává z členů Ops zodpovědných za dohled a údržbu celkové architektury z úhlu pohledu svých zodpovědností a relevantních členů Dev dodavatelských týmů.

Dev tým

Tým vývojářů, inženýrů, testerů a dalších potřebných rolí dodavatele.

IDP

Internal Developer Platforma - lidé ze strany TA ČR vyvíjející platformu SISTA pro vývojáře.

Ops tým

Tým vývojářů, správců, testerů a poučených uživatelů TA ČR.

Platforma SISTA

Platforma pro vývojáře, která umožňuje vývojářům běh a správu jejich vytvořených služeb.

Odkazy a zdroje

Rejstřík

Příloha A: Seznam příloh

Table 1. Internetové zdroje
ID Název přílohy s odkazem repozitář Verze

01

02

03

04

05

06

07

08

09

10