SaltStack – sůl pro vaše servery

SaltStack je nástroj pro provozování infrastructure-as-a-code (tedy Infrastruktury jako kódu). Nástroje podobného typu jsou například Chef nebo Puppet. SaltStack je z této skupiny nejmladší, což přináší výhody i nevýhody. Oboje se vám pokusím ukázat v následujícím článku.
Nejprve si pojďme říct, co to vlastně to IaaC je. Infrastukturou rozumíme soubor serverů a jejich nastavení, které potřebujeme pro běh našeho projektu. A „as a code“ znamená, že se k ní budeme chtít chovat jako ke kódu – tedy ji verzovat, dynamicky generovat atp. Dříve bylo zvykem, že konfigurace serveru byla udržována v lepším případě jedním dlouhým bash skriptem, který se pustil na čistém serveru. V horším případě ji znal jen správce, který ji měl rozepsanou na stick-it papírcích okolo monitoru, případně byl někde připravený image. Ty doby jsou už ale naštěstí dávno pryč (aspoň doufám). Jednotlivé IaaC nástroje pracují různými způsoby – buď na základě přesně daného postupu jako kuchařka (nasyp tam mouku, zamíchej, přidej mléko) nebo na základě stavů jako checklist (Musí tam být mléko a mouka, chybí něco z toho?). Prvním způsobem pracuje Chef. A do druhé skupiny spadá jak Puppet, tak SaltStack. SaltStack je nástroj napsaný v Pythonu a má za sebou momentálně dva roky vývoje. Jeho tvůrci se poučili z problémů, se kterými se potýkaly Chef a Puppet, a pokusili se ho navrhnout jako jejich spojení.
Základní stavební kameny
Salt může běžet jak v master-minion (slave) módu, kdy jeden centrální server řídí podřízené servery, tak v master-less módu, kdy se jednotlivé aplikační servery takříkajíc řídí samy. Já se budu věnovat master-minion módu, který je podle mého typičtější. Centrálním bodem infrastruktury je tedy salt-master, který rozděluje úkoly podřízeným minionům – deamonům běžícím na serverech. Komunikace mezi servery běží nad knihovnou ZeroMQ a samotná data jsou přenosem serializována pomocí formátu MessagePack („like JSON, but fast and small“). Hlavním atributem SaltStacku je právě tato rychlá asynchronní komunikace. Veškerý heavy-lifting provádí minion lokálně a po síti tečou jen nejnutnější data. Nestane se tedy, že byste si zahltili síť tím, že si necháte nahodit 20 serverů. Salt je navržen tak, aby byl co nejvíce modulární. Krom základní komunikační vrstvy je vlastně všechno modul. Dostupné jsou moduly pro všechny základní funkce, které očekáváte od konfiguračního managementu – práci s uživateli, soubory, složkami, balíčky, atp. Pojďme si ukázat, jak SaltStack zprovoznit.
Zprovoznění na Debianu
Na serverech používáme debian Wheezy, který je sice hezky stabilní, ale s tím bohužel souvisí i to, že nejsou často dostupné aktuální verze balíčků. Bude tedy potřeba si přidat SaltStack repository.
minion$ echo "deb http://debian.saltstack.com/debian wheezy-saltstack main" >> /etc/apt/sources.list
minion$ wget -q -O- "http://debian.saltstack.com/debian-salt-team-joehealy.gpg.key" | apt-key add
minion$ apt-get update
minion$ apt-get install salt-minion
Tím jsme na server nainstalovali salt-minion. Minion sice může pracovat sám (tzv. master-less setup), ale lepší je aby více minionů bylo řízeno salt-masterem. Takže na serveru, který bude salt-master musíme ještě nainstalovat druhý balíček (je také nutné přidat repo).
master$ apt-get install salt-master
Jakmile máme běžící salt-master, můžeme na něj napojit miniony. Na to bude potřeba jim dát vědět, kam se mají připojovat. To uděláme pomocí direktivy master
v /etc/salt/minion
minion$ sed -e 's/^#master: salt/master: salt-master.example.com/' -i /etc/salt/minion
minion$ service salt-minion restart
Po restartu služby si minion automaticky vytvoří připojení na salt-master. Ale je třeba udělat ještě poslední krok, abychom mohli začít rozesílat minionům úkoly – a to přidat na masteru klíče. Pomocí příkazu salt-key si můžeme vypsat všechny miniony, které se snaží k salt-masteru připojovat.
master$ salt-key
Accepted Keys:
Unaccepted Keys:
minion
Rejected Keys:
Vidíme, že náš minion je uvedený v sekci „Unaccepted“ a musíme ho nejdříve přidat.
master$ salt-key -a minion
master$ salt-key
Accepted Keys:
minion
Unaccepted Keys:
Rejected Keys:
Pokud se vše podařilo, tak nyní můžeme začít rozesílat příkazy.
master$ salt '*' test.ping
minion:
True
Pojďme se blíže podívat na to, co která část volání znamená. salt
je logicky název binárky saltu. '*'
je pattern názvů serverů, které mají na volání odpovídat – kromě obyčejného hvězdičkování je možné používat i jiná cílení – např. pomocí regulárních výrazů, nebo podle vlastností serveru (tzv. grains). test.ping
je samotné volání – voláme metodu ping
modulu test
. Pod voláním se pak objevují odpovědi od jednotlivých serverů. Zde, jak vidíme, odpovídá náš minion minion
na volání jen stručným True
– ping se zdařil. Ping je nejjednodušší možné volání, které testuje pouze zde minion poslouchá – hodí se například k testování, jestli pattern odpovídá serverům, na kterých chceme volání provést. Mezi další zajímavá volání patří třeba hosts.add_host
, pomocí kterého můžete přidávat záznamy do hosts souboru. Nejzajímavější volání jsou ovšem součástí modulu state
.
Vynucování stavů
I když hromadné volání přes víc serverů může být užitečné, tak to není to hlavní, čím si mě salt získal. Tím je možnost definice takzvaných salt states. State vždy vynucuje nějaký stav něčeho na serveru a může vypadat například takto
my_editor:
- pkg:
- installed
- name: vim
Tento stav slouží k zaručení toho, že na cílovém serveru bude nainstalovaný balíček vim
. Při jeho vynucení dojde ke kontrole, jestli je vim
mezi nainstalovanými balíčky, a pokud ne, je zavolán příslušný příkaz pro jeho instalaci. Stavový modul pkg
je navíc proxy pro jednotlivé balíčkovací nástroje a můžete pomocí něj můžete vynucovat nainstalovaný balíček napříč různými distribucemi. Definice jednotlivých stavů jsou definované v YAMLu – ne v nějakém „lepším yamlu“, ne v Neonu, prostě v obyčejném YAMLu se všemi jeho omezeními a výhodami. Díky tomu stačí nastavit ve vašem oblíbeném editoru zvýrazňování syntaxe na YAML a můžete začít. A je jedno, jestli jinak programujete v pythonu, ruby nebo javascriptu. O žádném dalším jazyku nemusíte prakticky nic vědět a vydržíte s tím poměrně dlouho. Jednotlivé stavy můžete napsat do jednoho souboru, který zašťiťuje nějakou větší funkcionalitu – například instalaci apache, soubor uložíme jako webserver/init.sls
do složky saltu (directiva files_root
). V takovém souboru pak je instalace balíčku, úprava httpd.conf, vygenerování vhostu a nastavení toho, že má běžet service apache. Soubor můžete na serveru vynutit voláním
master$ salt 'hostname' state.sls webserver
Kromě toho salt nabízí ještě možnost nadefinovat takzvaný highstate. Highstate určuje, ze kterých souborů bude složený výsledný stav serveru a je nakonfigurován v souboru top.sls
.
web*.example.com
- webserver
- firewall
- php
# won't match dbmonitoring.example.com
db[0-9]+.example.com
- match: pcre
- mysql
- firewall
Jak vidíte, tak pro definici pravidel je opět použit YAML. Máme také k dispozici podobnou sadu nástrojů na matchování serverů, jako pro přímé volání příkazů (hvězdičky, PCRE, grains, atd.). Díky tomu lze vystavět poměrně složitou strukturu serverů bez jakýchkoli dalších nástrojů.
Pokud si projdeme topfile, vidíme, že na prvním řádku píšeme pattern, kterému musí odpovídat FQDN serveru a pod ním jsou jednotlivé statefiles, které se na server mají použít (o tom, jak je možné statefiles pojmenovávat, se můžete dočíst v dokumentaci).
highstate
pak vynutíme následujícím voláním.
master$ salt '*' state.highstate
Pokud si vyzkoušíte s tím, co jste se naučili, nasaltovat server, zjistíte zajímavou věc – občas to funguje a občas ne. Čím to? Příčinou je neurčité pořadí jednotlivých stavů. Salt nezpracovává stavy v nějakém konkréntím pořadí – vezme prostě „nějaké“ a provede je. Jenže někdy se stane, že potřebujete, aby vynucení jednoho stavu proběhlo dříve než vynucení jiného. Typickým příkladem je nutnost mít nainstalovaný balík mysql
před tím, než se pokusíte vytvořit databázi a uživatele. K tomu slouží podklíč require
– ten umožňuje nadefinovat, že ten který stav ke svému vynucení potřebuje, aby byl nejprve vynucen jiný stav. Můžeme si to ukázat na příkladu nahrání vlastního httpd.conf:
/etc/apache2/httpd.conf:
file:
- managed
- source: salt://apache/conf/httpd.conf
- require:
- pkg: apache2
Říkáme, že chceme, aby se do /etc/apache2/httpd.conf
zapsal obsah ze souboru apache/conf/httpd.conf
v saltu, ale to pouze za předpokladu, že se podaří vynutit nejprve nainstalování balíčku apache2
. Opačným směrem funguje watch
:
apache2:
service
- running
- watch:
- file: /etc/apache2/httpd.conf
V tomto případě říkáme, že chceme, aby tento stav sledoval soubor httpd.conf
a pokud u něj dojde ke změně, tak aby se znovu vynutil (v případě služeb to znamená restart/reload).
Tady bychom mohli skončit. Už teď jsme schopni si nasaltovat databázový server a webserver kdekoli budeme chtít – na fyzických serverech, na AWS nebo u Virtual Masteru.
Nevýhodou naznačeného postupu je ovšem fakt, že pokud budeme chtít udělat 10 různých webserverů a k nim 10 různých uživatelů na ssh, tak to začne být otrava, protože každý state musíme zkopírovat, přejmenovat (jméno state musí být unikátní), změnit heslo a připsat ho do topfile. Naštěstí i na toto existuje v saltu řešení – Pillars (=solné sloupy). Pillars jsou vlastně libovolná data, která je možné stejně jako v případě jednotlivých stavů přiřazovat serverům.
Pojďme si to ilustrovat na příkladu instalace našich oblíbených balíčků. Na každém serveru, ať je to DB, webworker nebo loadbalancer určitě chceme mít některé package vždycky k dispozici. Pro mě to jsou vim
, htop
a wget
. Každý má oblíbené balíčky jiné, takže statefile fav_pkgs.sls
by pro každého člověka vypadal jinak. Přitom salt by měl být řešením pro sdílení konfigurace! Nebojte se, právě pro tento případ máme pillars.
Vytvoříme si soubor pillar/fav_pkgs.sls
(shoda názvu se statefile je vhodná pro přehlednost, nikoli však nutná) a do něj následující obsah
# shoda klíče s názvem souboru opět vhodná, nikoli nutná
fav_pkgs:
- vim
- htop
- wget
Dále si vytvoříme topfile (pillar/top.sls
):
base:
'*.example.com':
- fav_pkgs
Tímto jsme zajistili, že na všech serverech na doméně example.com budem v pillars mít k dispozici pole definované v fav_pkgs.sls
. Je třeba zmínit, jakým způsobem se data v pillars chovají v případě více souborů. Při kompilaci pillarů se spojí veškeré definice ze všech souborů přiřazených ke konkrétnímu serveru do jednoho. Naneštěstí spojení funguje jako úplně hloupé přepisování. Tedy pokud bychom měli jiný soubor, který by také na nejnižší úrovni obsahoval klíč fav_pkgs
, tak bude záležet čistě na pořadí, ve kterém se soubory načtou. Je proto dobré udržovat strukturu dat přibližně odpovídající struktuře pillar souborů, aby každá data měla svůj unikátní namespace. Bohužel v tomto směru neexistuje žádný coding standard, který by cokoli nařizoval.
Nyní, když máme k dispozici pillar data, je čas je využít. Vytvoříme si nový statefile v files/fav_pkgs/init.sls
.
fav_pkgs:
pkg.installed:
- names:
{% for pkg in pillar.fav_pkgs %}
- {{ pkg }}
{% endfor %}
Máme tady jednu zásadní novinku – statefile, který v sobě obsahuje cyklus. Zatím jsme nemluvili o tom, že jednotlivé statefiles jsou vlastně šablony v šablonovacím systému jinja a jako takové je tedy lze generovat za pomoci složitější logiky. Tagy v jinje se uzavírají do {%
a %}
a pro výstup jsou použity dvojité složené závorky ({{
a }}
).
Jak vidíte, tak zde používáme jednoduchý cyklus pro každý prvek pole. Procházíme pole pillar.fav_pkgs
(fav_pkgs
nijak nesouvisí s názvem souborů, ale je to klíč na druhém řádku souboru pillar/fav_pkgs.sls
) a pro každý prvek ho vypíšu jako položku pole. Výsledný zpracovávaný kód je tedy:
fav_pkgs:
pkg.installed:
- names:
- vim
- htop
- wget
Tento modul můžu nyní snadno sdílet s tím, že do dokumentace napíšu, že je třeba nastavit v pillars položky pole fav_pkgs
. Kdokoli tak může snadno upravit statefile tak, aby instaloval jeho oblíbené balíčky. V takto triviálním případě to nedává moc smysl, ale v případě složitějších konfigurací to umožní jednak znovupoužitelnost a také to, že samotné nastavení může provádět někdo, pro koho je konkrétní implementace moc složitá na pochopení. Přeci jen snadněji vysvětlíte vývojáři, že „tady do toho souboru přidáš nakonec klíč s názvem domény a pod to ip, db login, db heslo a api klíč“ než „vytvoříš statefile do kterého nadefinuješ přidání uživatele, databáze, nastavíš závislosti a je to“. Navíc díky tomu, že jsou data v otevřeném formátu, tak není problém je generovat – například z databáze (python-hackers si to mohou dokonce napsat rovnou v pythonu a YAML úplně přeskočit). Drobnou nevýhodou je, že YAML není možné (narozdíl třeba od XML) rozumně zvalidovat. Ale na druhou stranu můžete, pokud je to nutné, YAML zparsovat ve vašem oblíbeném jazyce a zkontrolovat ručně výsledná data.
Se znalostmi shrnutými v tomto článku a s pomocí dokumentace a API dokumentace by pro vás neměl být problém si zkusit nasaltovat třeba jednoduchý LAMP stack (inspiraci lze čerpat také z ukázkových modulů na Githubu). Pokud byste si chtěli popovídat o tom, jaké mám se Saltem zkušenosti (zatím v předprodukčním nasazení), tak mě můžete odchytit buď na twitteru @tomasfejfar nebo naživo třeba na ZFMeetupu. Na závěr bych rád zmínil, že SaltStack a IaaC používám přibližně rok, takže si nemyslím, že se mi podařilo proniknout do všech jeho možností. Pokud máte nějaké zajímavé tipy nebo SaltStack už používáte, tak se podělte v komentářích.
Prijde mi skoda, ze prestoze je software napsany v pythonu, tak na konfiguraci je pouzit YAML. Python sam o sobe je dost silny jazyk, aby se dala konfigurace provest primo v nem.
Dokonce je-li vyzdvihovan koncept infrastructure-as-a-code, proc je ten „code“ YAML a ne python samotny?
Nemůžu mluvit za autory, ale osobně to vítám. Tím, že je většina konfigurace v human-readable formátu, tak je možné se saltem začít bez znalosti Pythonu. K Pythonu se člověk poprvé dostane, když buď něco debuguje a nebo pokud si píše vlastní modul. Ale bez toho se dá vystačit dlouho. Pro práci s Puppetem nebo Chefem se člověk neobejde bez znalosti Ruby, resp. se musí naučit nějaké jejich proprietární DSL pseudojazyky. Já bych si troufl říct, že saltem se dá udělat jednoduchý LAMP bez znalosti čehokoli krom YAMLu – a naučit se YAML je zálěžitost na 15 minut (plus znalost toho, jak se instaluje ten samotný LAMP).
Tedy python to IMO není ne proto, že by něco neuměl nebo to nebyl dost silý jazyk, ale protože je naopak moc silný a umí toho moc. Velká výhoda YAMLu je také to, že se dá snadno generovat z nějakého toolu, pokud je třeba. Takže máš volnost – napsat si to v YAMLu, v YAMLu oživeném Jinjou nebo si to napsat třeba v PHP, Ruby nebo třeba Haskelu :) Jakýkoli jazyk, který umí generovat YAML (ověřil jsem c++, c#, python, ruby, php) může generovat konfiguraci. Mě to přijde ideální. U nás to zatím máme YAML+Jinja, ale myslím, že velmi rychle přejdeme na PHP generovaný YAML nebo aspoň PHP generované pillars, protože je zbytečné držet tu konfiguraci na dvou místech (naše projektová DB + pillars).
Ono ani tak nejde o human-readable (to je python celkem taky), ale o to, ze YAML je deklarativni, coz je hrozne dulezity kdyz bych chtel ty konfigurace nejak porovnavat, auditovat, validovat apod.
Jinak Salt nema jeden format, ale pluginy pro ruzne formaty (daji se napsat i vlastni) a jeden z nich je „PyDsl“ – DSL zalozeny na Pythonu a druhy je cisty python, ve kterem se proste napise funkce, ktera musi vracet data v definovanem formatu. Jak si je vyvori, to je ciste na ni.
Vsechny pluginy jsou v namespacu salt.renderers a daji se proklikat treba tady: http://docs.saltstack.com/salt-modindex.html
To mi neprijde jako moc dobry napad. Samozrejme zalezi na implementaci a use case, ale podle me to zbytecne prinasi dalsi vrstvu, ktera se da obvykle vyresit jinak. Generovani pillaru je v pohode, to delame taky.
Btw, uz je i nejaka integrace s hiera, to by taky mohlo nekoho zajimat.
Díky za upřesnění. Na ten PyDSL jsem narazil dřív při čtení dokumentace, ale úplně jsem na něj zapomněl.
Ohledně generování v PHP – jde tam především o to, že máme různé věci už v PHP vyřešeny. Máme v PHP+Phing např. one click deploy. Různé helper metody, které určují co je co a co se kam má dát. A jsem si v něm mnohem jistější v tom, co píšu. Třeba připojit se k MySQL, něco tam naparsovat (např. to, který server má jakou roli) a na základě toho něco ladit – prostě business logika – to mi přijde lepší udělat v PHP především proto, že většinou někde udělám nějakou chybu – myšleno logickou chybu (zapomenu závislost atp.) a když ladím tohle, python syntaxi a salt syntaxi, tak je toho fakt hodně najednou. Být v Pythonu zběhlejší, tak bych to asi psal v něm. Ale právě ta lehkost generování a parsování YAMLu se mi líbí – že je to o jednu věc méně o kterou je potřeba se starat.
No a jake problemy chefu a puppetu teda konretne SaltStack resi?
Záleží, kdo co vnímá jako chyby – mě třeba vadí, že u puppetu se defaultně refreshuje state furt dokola. Takže pokud potřebuju něco vyzkoušet a přepsat, tak musím killnout puppet demona a pak ho nezapomenout zapnout. U Chefu mi zas nevyhovuje způsob zápisu kuchařek – víc se mi líbí deklarativní zápis. U obou zmíněných se mi moc nelíbí DSL a to, že se neobejde člověk bez Ruby a obecně je to víc proprietární – salt je na tom podobně jako Symfony v poslední době – místo aby měli všechno vlastní používají hotové – ZMQ, msgpack, YAML. Ale jak říkám – to, že mě to vadí neznamená, že to musí být chyba :) Někdo může vnímat jako výhodu to, že může běžet master-less (tj. mužeš si nasaltovat samotný server) nebo to, že se dá použít k vzdálenému volání příkazů – např. když potřebuješ hromadně změnit v hosts IP adresu na některých ze svých 80ti serverů. To pokud vím třeba v puppetu tak snadno nejde (ale tak moc ho zas projitý nemám), v chefu to jde IMO přes knife.
Pokud se někdo zabýváte konfiguračním namagementem a sháníte práci, tak vizte, že se objevilo jedno zajímavé volné místo: http://lmc.jobs.cz/pd/728674757/