- CoreOS – moderní clusterovaný OS pro datová centra (1) – Úvod a instalace
- CoreOS – moderní clusterovaný OS pro datová centra (2) – Etcd, distribuovaný state
- CoreOS – moderní clusterovaný OS pro datová centra (3) – Etcd a konzistence
- CoreOS – moderní clusterovaný OS pro datová centra (4) – Fleet
- CoreOS – moderní clusterovaný OS pro datová centra (5) – pokročilý Fleet
- CoreOS – moderní clusterovaný OS pro datová centra (6) – je to Docker orchestrátor?
Minule jsme se seznámili s Etcd – distribuovaným key-value store ideálním pro řešení věcí jako je volba mastera clusterů, synchronizace stavů distribuvaných aplikací a tak podobně. Etcd se zaměřuje na vysokou míru konzistence a dnes se na to podíváme.
Konzistence v Etcd
Etcd se velmi zaměřuje na konzistenci a dosahuje velmi vysokých hodnot na hranici teoretických možností distribuovaného systému. To platí zejména pro zápisy, kde Etcd implementuje velmi zajímavý algoritmus Raft (Raft konsenzus – alternativa ke známějšímu a podstatně nepochopitelnějšímu Paxos). Detaily jsou velmi zajímavé, ale vrátíme se k nim jindy (pro úvod do CAP teorému třeba poslouží starší článek). Soustřeďme se na vybrané praktické implikace.
Etcd, díky využití Indexů, které se dají zjednodušeně představit jako jakési globální číslo operace, nabízí konzistenci zápisů. Kromě toho, že jednotlivé operace jsou atomické, tak máte k dispozici i atomický Compare-and-Swap a Compare-and-Delete (jakási složenina čtení a akce, nicméně taková, že je garantována její neoddělitelnost a izolovanost, tedy nikdo jiný do toho v ten moment nemůže vstoupit). Etcd má sekvenční konzistenci, tedy garantuje, že operace proběhnou (a dají se číst) na všech nodech ve stejném pořadí (což ale nemusí nutně znamenat stejné časování, což je u distribuovaného systému nemožné), tedy dostanou se do identického stavu. Ve výchozím nastavení operace read negarantuje ze všech nodů návrat nejaktuálnější verze hodnoty, garantuje ovšem, že události budou ve správném pořadí (byť třeba o chvilku opožděně, ale nijak nenabourávají učebnice dějepisu). Pro čtení (a jen pro čtení) volí Etcd při roztržení clusteru na dvě poloviny Availability místo Consistency (viz článek o CAP teorému). Pokud vám to pro vaše účely vadí, dá se Etcd přepnout do používání Raft konsenzu i pro čtení (tedy garantujeme poslední hodnotu za cenu nižšího výkonu). Ale na detaily Raft si nechme čas jindy.
Pojďme si něco zkusit, ať vidíe, že to není složté.
Uložte si hodnotu klíče a všimněte si vráceného Indexu (což pro dnešek berme jako pořadové číslo všehomíra).
core@core-01 ~ $ etcdctl -o extended set /klic tomas-domluvil Key: /klic Created-Index: 16136 Modified-Index: 16136 TTL: 0 Index: 16136 tomas-domluvil
Operace kterou jsme provedli, je atomární set-get sekvence, tedy hned se nám vrátila uložená hodnota (kterou je “tomas-domluvil”). Provedli jsme tedy čtení. Dejme tomu, že jsme na jeho základě přijali nějaké rozhodnutí. Předpokládejme například, že potřebujeme v Etcd implementovat to, že ke slovu se dostane jeden (a právě jeden) proces (tedy jen jeden z mnoha procesů/kontejnerů/aplikací apod. si může vzít slovo). Do našeho /klic jsme zapsali “tomas-domluvil”, čímž identifikujeme, že jsme ochotni se slova vzdát. To, že si někdo vezme slovo se projeví tím, že sem zapíše svoje jméno. Co když si dva procesy přečetly, že tomas domluvil, a po nějakém přemýšlení se rozhodnou vzít si slovo? Co když to udělají oba současně? Martin si zapíše slovo, ale Standa si stále myslí, že jsme ve stavu “tomas-domluvil” a také se do hodnoty zapíše. Máme problém. Jak se ujistit, že se v době našeho přemýšlení situace nezměnila? Pomohlo by před zápisem hodnotu pro jistotu ještě jednou načíst? A co když nám do toho zase znova někdo skočí mezi tím co hodnotu čteme a než zapíšeme novou? Určitě nechceme dát možnost aplikacím uzamknout databázi v okamžiku, kdy aplikace přemýšlí (a může také havarovat a přemýšlet do nekonečna) – zámky v single node relační DB hodně bolí, ale v distribuvaném systému ještě daleko víc. Potřebujeme raději garantovat atomicitu operace “přečti a pokud dobrý, tak zapiš” (ale ne tak, že tyto fáze spouští aplikace a může mezi nimi havarovat a nechat distribuované zámky otevřené – aplikace to musí brát jako jedinou atomární operaci).
Martin má také přečteno, že “tomas-domluvil”, ale zaznamenal si index 16136. Martin teď v jediné operaci řekne: “pakliže platí stále to, co jsem si přečetl (tedy že aktuální index je stále 16136), zapiš tam co chci já a nesmí ti do toho nikdo skočit”.
core@core-01 ~ $ etcdctl -o extended set /klic martin --swap-with-index 16136 Key: /klic Created-Index: 16136 Modified-Index: 16150 PrevNode.Value: tomas-domluvil TTL: 0 Index: 16150 martin
Martin následně svůj (jistě vřele přijatý) proslov dokončil.
core@core-01 ~ $ etcdctl -o extended set /klic martin-domluvil --swap-with-index 16150 Key: /klic Created-Index: 16136 Modified-Index: 16160 PrevNode.Value: martin TTL: 0 Index: 16160 martin-domluvil
Tomáš chce říct ještě něco dalšího. Ví, že Martin už domluvil a byl nejrychlejší.
core@core-01 ~ $ etcdctl -o extended set /klic tomas --swap-with-index 16160 Key: /klic Created-Index: 16136 Modified-Index: 16171 PrevNode.Value: martin-domluvil TTL: 0 Index: 16171 tomas
Standa také ví, že Martin už domluvil. Jenže si slovo vzít nestihl, systém ho varuje a nezačnou tak mluvit oba najednou.
core@core-01 ~ $ etcdctl -o extended set /klic standa --swap-with-index 16160 Error: 101: Compare failed ([16160 != 16171]) [16201]
Implementace fronty s garantovaným pořadím
Znáte to. Přijdete na poštu, vezmete si pořadový lísteček a pokud vás někdo předběhne, jste naštvaní. Tyhle lístečky prostě musí skutečně odpovídat pořadí v jakém lidé přicházejí. Ve většině případů jsou automaty jen terminál a ve skutečnosti lístečky eviduje jeden jediný systém. Ten díky tomu má jen jedny hodiny. To je snadné, ale neškáluje to. Co když potřebujeme z různých důvodů distribuovaný systém? V něm globální hodiny neexistují (a ne – ani NTP nebo PTP nemůže nahradit krystalový oscilátor v CPU sběrnici). Nicméně Raft nám umožní v distribuovaném systému řešit shodu na úrovni pořadí operací – a to nám stačí.
V zásadě jde o to, že můžete v adresáři automaticky vytvořit klíč ve formě čísla (to se stále zvyšuje, ale všelijak – ne nutně o jedničku). Můžete zadávat hodnoty na odlišných nodech v téměř stejné okamžiky a přesto vám Etcd zajistí férové seřazení příchozích.
V době psaní článku pro tohle operaci nebylo CLI, tak jsem použil cURL a přístup do API.
core@core-01 ~ $ curl http://127.0.0.1:4001/v2/keys/fronta -XPOST -d value=tomas {"action":"create","node":{"key":"/fronta/00000000000000016579","value":"tomas","modifiedIndex":16579,"createdIndex":16579}} core@core-02 ~ $ curl http://127.0.0.1:4001/v2/keys/fronta -XPOST -d value=martin {"action":"create","node":{"key":"/fronta/00000000000000016586","value":"martin","modifiedIndex":16586,"createdIndex":16586}} core@core-03 ~ $ curl http://127.0.0.1:4001/v2/keys/fronta -XPOST -d value=marek {"action":"create","node":{"key":"/fronta/00000000000000016588","value":"marek","modifiedIndex":16588,"createdIndex":16588}} core@core-01 ~ $ etcdctl ls /fronta /fronta/00000000000000016579 /fronta/00000000000000016586 /fronta/00000000000000016588 core@core-01 ~ $ etcdctl get /fronta/00000000000000016579 tomas