SaltStack – sůl pro vaše servery

saltstack

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).

highstatepak 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.

Tomáš programuje v PHP, rád se učí pracovat s novými nástroji a technologiemi. Je blázen do Gitu a produktivity. Nejspíš ho potkáte na některém z vývojářských meetupů a hackathonů v Praze. 

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 7

Přehled komentářů

paja jazyk pro konfiguraci
Tomáš Fejfar Re: jazyk pro konfiguraci
PrymekM Re: jazyk pro konfiguraci
Tomáš Fejfar Re: jazyk pro konfiguraci
me
Tomáš Fejfar Re:
Tomáš Fejfar Práce v DevOps
Zdroj: https://www.zdrojak.cz/?p=9712