V dnešnej dobe je bežnou požiadavkou správa väčšieho množstva serverov. Spravovanie by samozrejme malo byť jednoduché, automatizované a opakovateľné. Inak povedané, je dobré mať na správu nejaký softvér, ktorý to uľahčí. Takéto softvéry na správu sú tu s nami už nejakú dobu. Napríklad Puppet, Chef alebo Ansible. Ale na tomto poli pribudol pred časom aj nový hráč s názvom SaltStack. Poďme sa teraz na neho bližšie pozrieť.

SaltStack je softvér vytvorený v Pythone, ktorý je určený na automatizované spravovanie menšieho aj väčšieho množstva serverov. Tieto servery sú označované ako miniony. Okrem nich by mal v sieti (ale nie je úplne nutná podmienka) existovať aj jeden (alebo viac) server master. Master je riadiaci server, ktorý minionom posiela príkazy, a z ktorého je možné celý systém ovládať.

Na masterovi aj minionovi bežia softvérové komponenty SaltStacku (aj keď u miniona to nie je úplne nutná podmienka, ale k tomu neskôr). Tieto systémy komunikujú pomocou ZeroMQ knižnice. Na rozdiel od softvérov, ktoré na toto používajú http, je to jedna z výhod Saltu, pretože vďaka tomu vie obsluhovať aj relatívne veľké množstvo serverov bez značného performance nárastu.

Komunikácia medzi masterom a minionom je kryptovaná. Preto ak sa pridáva nový minion do siete, tak posiela masterovi svoj kľúč a ten kľúč je nutné príkazom masterovi akceptovať. Následne môže začať posielanie príkazov. A ako posielanie vyzerá? Tu je ukážka jedného takého príkazu:

  salt '*' disk.usage

Poďme to rozobrať postupne:

salt – týmto samozrejme začínajú všetky príkazy, keďže ide o volanie samotného programu
‚*‘ – prvý parameter hovorí o tom, pre ktorých minionov chceme príkaz spustiť. Možnosti ako ich zacieliť je veľa, o chvíľu sa na nich pozrieme
disk.usage – to pred bodkou je modul, to za ňou je funkcia. Týmto vlastne hovorím, čo majú vybraní minioni spraviť.

Za modulom a funkciou ešte môžu nasledovať ďalšie parametre, ktoré predstavujú vstupné dáta do funkcie. Takže takto viem posielať minionom príkazy. Ale ako vyberiem, ktorým minionom chcem príkaz poslať? Na to je niekoľko možností:

  1. vymenujem minionov za sebou: ‚dev1, dev2, int1‘
  2. použijem glob matching podobné linuxovému shellu: ‚dev[1-2]‘ alebo ‚int*‘
  3. podobne ako glob matching viem použiť aj regulárne výrazy: ‚dev-(local|remote)‘
  4. minionov zacielim pomocou grains: ‚os:CentOS‘

Asi sa po poslednom bode pýtate, čo je to grains. To je sada dvojíc kľúč-hodnota, ktorú majú priradenú všetci minioni. V tej sade viete nájsť naozaj rôzne veci. Od operačného systému miniona, cez jeho typ procesora, až po nejaké ľubovoľné údaje, ktoré tomu minionu viete priradiť. Sú to vlastnosti miniona, ktoré viete následne používať v rôznych situáciách, ako v príklade vyššie, keď chcete vybrať nejakú podmnožinu minionov.

Ukázali sme si teda aplikáciu ľubovoľného príkazu. Ale toto je naozaj operatíva. Niekto (správca) sedí za počítačom a vydáva príkazy. Alebo to robí cron, alebo niečo podobné. Ten, čo to ale vykonáva, musí vedieť, čo sa deje a podľa toho posielať príkazy. Nebolo by lepšie prikazovať a nestarať sa o to, či ten príkaz môžem alebo nemôžem spustiť? Jednoducho vyžiadať od Saltu, aby dostal miniona do nejakého stavu (nainštalovaný softvér, vyčistené logy, spustená služba) a samotný Salt sa už postará o to, aby sa udiali len potrebné kroky. Nie nadarmo sa táto funkcionalita volá states.

Salt stavy fungujú jednoducho. Namiesto imperatívneho prikazovania Saltu, čo má spraviť, mu len deklaratívne poviete, v akom stave chcete, aby sa minion ocitol. Napríklad:

salt 'xyz.lan' state.apply install-vim.sls

Z predchádzajúceho vysvetlenia je verím jasné, že spúšťam Salt, cielim na minion xyz.lan.sk a spúšťam funkciu apply v module state (to je funkcia, ktorá slúži práve na aplikovanie stavov). Ten posledný parameter je názov súboru, v ktorom mám uloženú definíciu cieľového stavu. Chcem, aby na minionovi bol nainštalovaný vim. Install-vim.sls vyzerá takto:

install_vim:       <- jedinečný identifikátor stavu
  pkg:             <- názov stavu
    - installed:   <- názov funkcie stavu
      - name: vim  <- naázov balíka, ktorý chcem inštalovať

Syntax Salt stavov je naozaj svojská, ale aj mnohoraká a mocná. My len škrtneme povrch toho, čo umožňuje.

Základom je jazyk Yaml (Yet Another Markup Language). To je východzí jazyk pre všetky Salt konfiguračné súbory, aj keď je možné ho zmieniť napríklad za JSON alebo iné. Prvý riadok je nejaký jedinečný identifikátor, ktorý stavu priradíme (jeho meno). Druhý hovorí o tom, ktorý stav chceme použiť (stav je akási logická množina funkcií). Tretím parametrom je funkcia zo stavu, ktorú chcem použiť a posledným je názov balíka.

A to je vlastne všetko. Stačí to spustiť a Salt zistí, či na minionovi taký balík existuje a ak nie, tak ho nainštaluje. Ak tam taký balík je, tak nevykoná nič (všimnite si, že názov funkcie je „inštalovaný“, nie „inštaluj“ – ja Saltu hovorím, do akého stavu sa má dostať, nie čo má spraviť). Toto bola ukážka veľmi jednoduchého Salt stavového súboru. V skutočnosti však vedia byť omnoho komplikovanejšie.

Jednou zo základných požiadaviek je, aby sme vedeli ten istý stav aplikovať na rôznych minionov. Ale čo je, keď je napríklad súčasťou stavu vytvorenie účtu používateľa, ktorý ma byť ale na každom minionovi iný? V tomto prípade nám pomôže Jinjapillary.

Pillary sú súbory štruktúrovaných údajov (opäť zapísané v Yaml), ktoré sa dajú priradiť jednému alebo viacerým minionom. Je to podobné ako grains, ale zatiaľ čo grains je len kľúč-hodnota, pillary sú ľubovoľná dátová štruktúra. Zoznam alebo slovník kľúč – hodnota, kde hodnota môže byť zoznam alebo opäť slovník.

Jinja (presnejšie Jinja2) je jazyk na generovanie šablón. Umožňuje vám napísať do šablóny riadiace znaky a potom pomocou nich vygenerovať konečnú šablónu. Okrem dynamického vkladania hodnôt do šablóny podporuje napríklad vetvenie (IF) alebo generovanie častí šablóny v cykle (FOR).

Spojením toho všetkého dostanete súbor Salt stavu, ktorý vytvorí na minionovi účet s loginom definovaným v pillare:

create_user:
  user.present:
    - name: {{ salt['pillar.get']('user:login') }}
    - fullname: {{ salt['pillar.get']('user:fullname') }}
    - shell: /bin/zsh
    - home: /home/{{ salt['pillar.get']('user:login') }}
    - uid: 4000
    - gid: 4000

Text {{ salt[‚pillar.get‘](‚user:login‘) }} je Jinja výraz, ktorý zavolá príkaz Saltu na získanie pillaru user:login (teda objekt user a jeho vlastnosť login) a vloží ho na dané miesto do šablóny. Výsledok je, že ak minionom priradím správne pillary a následne pustím tento stav, tak na každom vznikne iný používateľ s iným loginom a menom.

Tento článok bol krátkym úvodom do toho, čo to SaltStack je a na čo je dobrý. Ani zďaleka sme nevyčerpali všetky témy, ktoré sa Saltu týkajú. Išlo skôr len ukázať základ Saltu, ktorým sú príkazy a stavy a tiež to, že je to pomerne flexibilný nástroj, ktorý vie pokryť rôzne scenáre. Keďže automatizácia spravovania je tu s nami už nejakú dobu, konkurencia na tom poli je veľká. Ale SaltStack určite stojí za pokus.