Úvod do bábky

Puppet je systém riadenia konfigurácie. Používa sa na privedenie hostiteľov do požadovaného stavu a udržiavanie tohto stavu.

S Puppet pracujem už viac ako päť rokov. Tento text je v podstate preložený a preusporiadaný kompilácia kľúčových bodov z oficiálnej dokumentácie, ktorá začiatočníkom umožní rýchlo pochopiť podstatu Puppet.

Úvod do bábky

Základné informácie

Operačný systém Puppet je klient-server, aj keď podporuje aj bezserverovú prevádzku s obmedzenou funkčnosťou.

Používa sa ťahový model prevádzky: štandardne raz za pol hodinu klienti kontaktujú server kvôli konfigurácii a použijú ju. Ak ste pracovali s Ansible, potom používajú iný model push: správca iniciuje proces aplikácie konfigurácie, samotní klienti nič nepoužijú.

Počas sieťovej komunikácie sa používa obojsmerné šifrovanie TLS: server a klient majú svoje vlastné súkromné ​​kľúče a zodpovedajúce certifikáty. Typicky server vydáva certifikáty pre klientov, ale v zásade je možné použiť externú CA.

Úvod do manifestov

V bábkarskej terminológii na bábkový server pripojiť uzly (uzly). Konfigurácia pre uzly je napísaná v manifestoch v špeciálnom programovacom jazyku - Puppet DSL.

Bábkové DSL je deklaratívny jazyk. Popisuje požadovaný stav uzla vo forme deklarácií jednotlivých zdrojov, napr.

  • Súbor existuje a má špecifický obsah.
  • Balík je nainštalovaný.
  • Služba sa začala.

Zdroje môžu byť vzájomne prepojené:

  • Existujú závislosti, ovplyvňujú poradie, v ktorom sa zdroje využívajú.
    Napríklad „najprv nainštalujte balík, potom upravte konfiguračný súbor a potom spustite službu“.
  • Existujú upozornenia - ak sa zdroj zmenil, odošle upozornenia na zdroje, ktoré sú k nemu prihlásené.
    Ak sa napríklad zmení konfiguračný súbor, službu môžete automaticky reštartovať.

Okrem toho má Bábkové DSL funkcie a premenné, ako aj podmienené príkazy a selektory. Podporované sú aj rôzne šablónovacie mechanizmy – EPP a ERB.

Bábka je napísaná v Ruby, takže veľa konštruktov a výrazov je prevzatých odtiaľ. Ruby vám umožňuje rozšíriť Puppet - pridať komplexnú logiku, nové typy zdrojov, funkcie.

Keď je spustená Puppet, manifesty pre každý konkrétny uzol na serveri sa skompilujú do adresára. adresár je zoznam zdrojov a ich vzťahov po výpočte hodnoty funkcií, premenných a rozšírenia podmienených príkazov.

Syntax a kódový štýl

Tu sú časti oficiálnej dokumentácie, ktoré vám pomôžu pochopiť syntax, ak uvedené príklady nestačia:

Tu je príklad toho, ako manifest vyzerá:

# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
  # Конфигурация по сути является перечислением ресурсов и их параметров.
  #
  # У каждого ресурса есть тип и название.
  #
  # Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
  #
  # Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
  # Про разные типы ресурсов написано ниже.
  #
  # После типа в фигурных скобках пишется название ресурса, потом двоеточие,
  # дальше идёт опциональное перечисление параметров ресурса и их значений.
  # Значения параметров указываются через т.н. hash rocket (=>).
  resource { 'title':
    param1 => value1,
    param2 => value2,
    param3 => value3,
  }
}

Odsadenie a zlomy riadkov nie sú povinnou súčasťou manifestu, ale odporúča sa štýl sprievodca. Zhrnutie:

  • Dvojmiestne zarážky, tabulátory sa nepoužívajú.
  • Zložené zátvorky sú oddelené medzerou, dvojbodky nie sú oddelené medzerou.
  • Čiarky za každým parametrom vrátane posledného. Každý parameter je na samostatnom riadku. Výnimku tvorí prípad bez parametrov a jedného parametra: môžete písať na jeden riadok a bez čiarky (t.j. resource { 'title': } и resource { 'title': param => value }).
  • Šípky na parametroch by mali byť na rovnakej úrovni.
  • Pred nimi sú napísané šípky vzťahu zdrojov.

Umiestnenie súborov na pappetserver

Pre ďalšie vysvetlenie uvediem pojem „koreňový adresár“. Koreňový adresár je adresár, ktorý obsahuje konfiguráciu Puppet pre konkrétny uzol.

Koreňový adresár sa líši v závislosti od verzie Puppet a použitých prostredí. Prostredia sú nezávislé sady konfigurácií, ktoré sú uložené v samostatných adresároch. Zvyčajne sa používa v kombinácii s git, v takom prípade sú prostredia vytvorené z vetiev git. Podľa toho je každý uzol umiestnený v jednom alebo inom prostredí. Toto je možné nakonfigurovať na samotnom uzle alebo v ENC, o ktorom budem hovoriť v ďalšom článku.

  • V tretej verzii ("stará bábka") bol základný adresár /etc/puppet. Použitie prostredí je voliteľné – napríklad pri starom Puppet ich nepoužívame. Ak sa používajú prostredia, zvyčajne sa ukladajú /etc/puppet/environments, koreňovým adresárom bude adresár prostredia. Ak sa prostredia nepoužívajú, koreňový adresár bude základným adresárom.
  • Počnúc štvrtou verziou („nová bábka“) sa používanie prostredí stalo povinným a základný adresár bol presunutý do /etc/puppetlabs/code. Podľa toho sú prostredia uložené v /etc/puppetlabs/code/environments, koreňový adresár je adresár prostredia.

V koreňovom adresári musí byť podadresár manifests, ktorý obsahuje jeden alebo viac manifestov popisujúcich uzly. Okrem toho by mal existovať podadresár modules, ktorý obsahuje moduly. Aké moduly sú, vám poviem o niečo neskôr. Okrem toho môže mať starý Puppet aj podadresár files, ktorý obsahuje rôzne súbory, ktoré kopírujeme do uzlov. V novom Puppet sú všetky súbory umiestnené v moduloch.

Súbory manifestu majú príponu .pp.

Pár príkladov boja

Popis uzla a zdroja na ňom

Na uzle server1.testdomain musí byť vytvorený súbor /etc/issue s obsahom Debian GNU/Linux n l. Súbor musí vlastniť používateľ a skupina root, musia byť prístupové práva 644.

Píšeme manifest:

node 'server1.testdomain' {   # блок конфигурации, относящийся к ноде server1.testdomain
    file { '/etc/issue':   # описываем файл /etc/issue
        ensure  => present,   # этот файл должен существовать
        content => 'Debian GNU/Linux n l',   # у него должно быть такое содержимое
        owner   => root,   # пользователь-владелец
        group   => root,   # группа-владелец
        mode    => '0644',   # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Vzťahy medzi zdrojmi na uzle

Na uzle server2.testdomain nginx musí byť spustený a musí pracovať s predtým pripravenou konfiguráciou.

Rozložme si problém:

  • Balík je potrebné nainštalovať nginx.
  • Je potrebné, aby konfiguračné súbory boli skopírované zo servera.
  • Služba musí byť spustená nginx.
  • Ak je konfigurácia aktualizovaná, služba sa musí reštartovať.

Píšeme manifest:

node 'server2.testdomain' {   # блок конфигурации, относящийся к ноде server2.testdomain
    package { 'nginx':   # описываем пакет nginx
        ensure => installed,   # он должен быть установлен
    }
  # Прямая стрелка (->) говорит о том, что ресурс ниже должен
  # создаваться после ресурса, описанного выше.
  # Такие зависимости транзитивны.
    -> file { '/etc/nginx':   # описываем файл /etc/nginx
        ensure  => directory,   # это должна быть директория
        source  => 'puppet:///modules/example/nginx-conf',   # её содержимое нужно брать с паппет-сервера по указанному адресу
        recurse => true,   # копировать файлы рекурсивно
        purge   => true,   # нужно удалять лишние файлы (те, которых нет в источнике)
        force   => true,   # удалять лишние директории
    }
  # Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
  # подписаться на изменения ресурса, описанного выше.
  # Волнистая стрелка включает в себя прямую (->).
    ~> service { 'nginx':   # описываем сервис nginx
        ensure => running,   # он должен быть запущен
        enable => true,   # его нужно запускать автоматически при старте системы
    }
  # Когда ресурс типа service получает уведомление,
  # соответствующий сервис перезапускается.
}

Aby to fungovalo, potrebujete približne nasledujúce umiestnenie súboru na bábkovom serveri:

/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│   └── site.pp
└── modules/
    └── example/
        └── files/
            └── nginx-conf/
                ├── nginx.conf
                ├── mime.types
                └── conf.d/
                    └── some.conf

Typy zdrojov

Kompletný zoznam podporovaných typov zdrojov nájdete tu v dokumentácii, tu popíšem päť základných typov, ktoré v mojej praxi stačia na vyriešenie väčšiny problémov.

rezeň

Spravuje súbory, adresáre, symbolické odkazy, ich obsah a prístupové práva.

Parametre:

  • názov zdroja — cesta k súboru (voliteľné)
  • cesta — cesta k súboru (ak nie je uvedená v názve)
  • zabezpečiť - typ súboru:
    • absent - vymazať súbor
    • present — musí existovať súbor akéhokoľvek typu (ak súbor neexistuje, vytvorí sa bežný súbor)
    • file - bežný súbor
    • directory - adresár
    • link - symbolický odkaz
  • obsah — obsah súboru (vhodné len pre bežné súbory, nemožno ho použiť spolu s zdroj alebo terč)
  • zdroj — odkaz na cestu, z ktorej chcete skopírovať obsah súboru (nedá sa použiť spolu s obsah alebo terč). Môže byť špecifikovaný buď ako URI so schémou puppet: (potom sa použijú súbory z bábkového servera) a so schémou http: (Dúfam, že je jasné, čo sa stane v tomto prípade) a dokonca aj s diagramom file: alebo ako absolútna cesta bez schémy (vtedy sa použije súbor z lokálneho FS na uzle)
  • terč — kam má smerovať symbolický odkaz (nedá sa použiť spolu s obsah alebo zdroj)
  • majiteľ — používateľ, ktorý by mal súbor vlastniť
  • skupina — skupina, do ktorej by mal súbor patriť
  • spôsob — oprávnenia na súbor (ako reťazec)
  • opakovať - umožňuje rekurzívne spracovanie adresárov
  • purge - umožňuje mazanie súborov, ktoré nie sú popísané v Bábke
  • pevnosť - umožňuje mazanie adresárov, ktoré nie sú popísané v Puppet

balíček

Nainštaluje a odstráni balíky. Schopný spracovať upozornenia - preinštaluje balík, ak je zadaný parameter reinstall_on_refresh.

Parametre:

  • názov zdroja — názov balíka (voliteľné)
  • názov — názov balíka (ak nie je uvedený v názve)
  • Poskytovateľ — správca balíkov na použitie
  • zabezpečiť — požadovaný stav balenia:
    • present, installed - nainštalovaná akákoľvek verzia
    • latest - nainštalovaná najnovšia verzia
    • absent - odstránené (apt-get remove)
    • purged — odstránené spolu s konfiguračnými súbormi (apt-get purge)
    • held - verzia balíka je uzamknutá (apt-mark hold)
    • любая другая строка — je nainštalovaná špecifikovaná verzia
  • reinstall_on_refresh - keby true, potom sa po prijatí upozornenia balík preinštaluje. Užitočné pre distribúcie založené na zdrojovom kóde, kde môže byť potrebné prebudovanie balíkov pri zmene parametrov zostavenia. Predvolené false.

služba

Spravuje služby. Schopný spracovať upozornenia - reštartuje službu.

Parametre:

  • názov zdroja — služba, ktorá sa má spravovať (voliteľné)
  • názov — služba, ktorú je potrebné spravovať (ak nie je uvedená v názve)
  • zabezpečiť — požadovaný stav služby:
    • running - spustený
    • stopped - zastavený
  • umožniť — ovláda schopnosť spustiť službu:
    • true — automatické spustenie je povolené (systemctl enable)
    • mask - zamaskovaný (systemctl mask)
    • false — automatické spúšťanie je zakázané (systemctl disable)
  • reštart - príkaz na reštartovanie služby
  • postavenie — príkaz na kontrolu stavu služby
  • hasrestart — uveďte, či initscript služby podporuje reštart. Ak false a parameter je špecifikovaný reštart — použije sa hodnota tohto parametra. Ak false a parametrom reštart nie je zadané - služba sa zastaví a spustí sa reštart (ale systemd používa príkaz systemctl restart).
  • hasstatus — uveďte, či initscript služby podporuje príkaz status, ak false, potom sa použije hodnota parametra postavenie. Predvolené true.

exec

Spúšťa externé príkazy. Ak neuvediete parametre vytvorí, iba ak, ak alebo len osvieženie, príkaz sa spustí pri každom spustení Puppet. Schopný spracovať upozornenia - spustí príkaz.

Parametre:

  • názov zdroja — príkaz, ktorý sa má vykonať (voliteľné)
  • povel — príkaz, ktorý sa má vykonať (ak nie je uvedený v názve)
  • cesta — cesty, v ktorých sa má hľadať spustiteľný súbor
  • iba ak — ak sa príkaz špecifikovaný v tomto parametri dokončí s nulovým návratovým kódom, vykoná sa hlavný príkaz
  • ak — ak sa príkaz špecifikovaný v tomto parametri dokončí s nenulovým návratovým kódom, vykoná sa hlavný príkaz
  • vytvorí — ak súbor špecifikovaný v tomto parametri neexistuje, vykoná sa hlavný príkaz
  • len osvieženie - keby true, potom sa príkaz spustí len vtedy, keď tento exec dostane upozornenie z iných zdrojov
  • cwd — adresár, z ktorého sa má príkaz spustiť
  • užívateľ — používateľ, od ktorého sa má príkaz spustiť
  • Poskytovateľ - ako spustiť príkaz:
    • POSIX — jednoducho sa vytvorí podriadený proces, nezabudnite uviesť cesta
    • škrupina - príkaz sa spustí v shelli /bin/sh, nemusí byť špecifikované cesta, môžete použiť globbing, potrubia a ďalšie funkcie shellu. Zvyčajne sa automaticky zistí, ak existujú nejaké špeciálne znaky (|, ;, &&, || atď).

cron

Ovláda cronjobs.

Parametre:

  • názov zdroja - len nejaký druh identifikátora
  • zabezpečiť — stav korunného zamestnania:
    • present - vytvoriť, ak neexistuje
    • absent - odstrániť, ak existuje
  • povel - aký príkaz spustiť
  • prostredie — v akom prostredí spustiť príkaz (zoznam premenných prostredia a ich hodnôt cez =)
  • užívateľ — od ktorého používateľa spustiť príkaz
  • minúta, hodina, všedný deň, mesiac, mesačný deň — kedy spustiť cron. Ak niektorý z týchto atribútov nie je zadaný, jeho hodnota v crontab bude *.

V Bábke 6.0 cron ako keby vybraté z krabice v puppetserver, takže na všeobecnej stránke nie je žiadna dokumentácia. Ale on je v krabici v puppet-agent, takže nie je potrebné ho inštalovať samostatne. Môžete si k nemu pozrieť dokumentáciu v dokumentácii k piatej verzii PuppetAlebo na GitHub.

O zdrojoch všeobecne

Požiadavky na jedinečnosť zdrojov

Najčastejšou chybou, s ktorou sa stretávame, je Duplicitné vyhlásenie. Táto chyba sa vyskytuje, keď sa v adresári objavia dva alebo viac zdrojov rovnakého typu s rovnakým názvom.

Preto ešte raz napíšem: manifesty pre rovnaký uzol by nemali obsahovať zdroje rovnakého typu s rovnakým názvom!

Niekedy je potrebné nainštalovať balíky s rovnakým názvom, ale s rôznymi správcami balíkov. V tomto prípade musíte použiť parameter nameaby sa predišlo chybe:

package { 'ruby-mysql':
  ensure   => installed,
  name     => 'mysql',
  provider => 'gem',
}
package { 'python-mysql':
  ensure   => installed,
  name     => 'mysql',
  provider => 'pip',
}

Ostatné typy zdrojov majú podobné možnosti, aby sa vyhli duplicite − name у služba, command у exec, a tak ďalej.

Metaparametre

Každý typ zdroja má nejaké špeciálne parametre, bez ohľadu na jeho povahu.

Úplný zoznam meta parametrov v Bábkovej dokumentácii.

Krátky zoznam:

  • vyžadovať — tento parameter označuje, od ktorých zdrojov tento zdroj závisí.
  • pred - Tento parameter určuje, ktoré zdroje závisia od tohto zdroja.
  • predplatiť — tento parameter určuje, z ktorých zdrojov tento zdroj dostáva upozornenia.
  • oznámiť — Tento parameter určuje, ktoré zdroje dostávajú upozornenia z tohto zdroja.

Všetky uvedené metaparametre akceptujú buď odkaz na jeden zdroj alebo pole odkazov v hranatých zátvorkách.

Odkazy na zdroje

Odkaz na zdroj je jednoducho zmienka o zdroji. Používajú sa hlavne na označenie závislostí. Odkazovanie na neexistujúci zdroj spôsobí chybu kompilácie.

Syntax odkazu je nasledovná: typ zdroja s veľkým písmenom (ak názov typu obsahuje dvojité dvojbodky, potom každá časť názvu medzi dvojbodkami je veľká), potom názov zdroja v hranatých zátvorkách (veľké písmená názvu sa nemení!). Nemali by byť žiadne medzery, hranaté zátvorky sa píšu hneď za názvom typu.

Príklad:

file { '/file1': ensure => present }
file { '/file2':
  ensure => directory,
  before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']

Závislosti a upozornenia

Dokumentácia tu.

Ako už bolo uvedené, jednoduché závislosti medzi zdrojmi sú prechodné. Mimochodom, buďte opatrní pri pridávaní závislostí - môžete vytvárať cyklické závislosti, čo spôsobí chybu pri kompilácii.

Na rozdiel od závislostí nie sú upozornenia prechodné. Pre oznámenia platia nasledujúce pravidlá:

  • Ak zdroj dostane upozornenie, aktualizuje sa. Akcie aktualizácie závisia od typu zdroja − exec spustí príkaz, služba reštartuje službu, balíček preinštaluje balík. Ak zdroj nemá definovanú akciu aktualizácie, nič sa nestane.
  • Počas jedného spustenia Puppet sa zdroj neaktualizuje viac ako raz. Je to možné, pretože oznámenia obsahujú závislosti a graf závislostí neobsahuje cykly.
  • Ak Puppet zmení stav zdroja, zdroj odošle upozornenia všetkým zdrojom, ktoré má na odber.
  • Ak je zdroj aktualizovaný, odošle upozornenia všetkým zdrojom, ktoré sú k nemu prihlásené.

Manipulácia s nešpecifikovanými parametrami

Spravidla, ak niektorý parameter zdroja nemá predvolenú hodnotu a tento parameter nie je špecifikovaný v manifeste, potom Puppet nezmení túto vlastnosť pre zodpovedajúci zdroj v uzle. Napríklad, ak zdroj typu rezeň parameter nie je zadaný owner, potom Puppet nezmení vlastníka príslušného súboru.

Úvod do tried, premenných a definícií

Predpokladajme, že máme niekoľko uzlov, ktoré majú rovnakú časť konfigurácie, ale existujú aj rozdiely - inak by sme to všetko mohli opísať v jednom bloku node {}. Samozrejme, môžete jednoducho skopírovať identické časti konfigurácie, ale vo všeobecnosti je to zlé riešenie - konfigurácia rastie a ak zmeníte všeobecnú časť konfigurácie, budete musieť na mnohých miestach upraviť to isté. Zároveň je ľahké urobiť chybu a vo všeobecnosti bol princíp DRY (neopakujte sa) vynájdený z nejakého dôvodu.

Na vyriešenie tohto problému existuje taký dizajn ako trieda.

Vyučovanie

Trieda je pomenovaný blok kódu poppet. Na opätovné použitie kódu sú potrebné triedy.

Najprv je potrebné opísať triedu. Samotný popis nikde nepridáva žiadne zdroje. Trieda je opísaná v manifestoch:

# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
    ...
}

Potom je možné triedu použiť:

# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше

Príklad z predchádzajúcej úlohy - presunieme inštaláciu a konfiguráciu nginx do triedy:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    include nginx_example
}

Premenné

Trieda z predchádzajúceho príkladu nie je vôbec flexibilná, pretože vždy prináša rovnakú konfiguráciu nginx. Urobme cestu ku konfiguračnej premennej, potom túto triedu možno použiť na inštaláciu nginx s akoukoľvek konfiguráciou.

Dá sa to zvládnuť pomocou premenných.

Pozor: premenné v Puppet sú nemenné!

Navyše k premennej je možné pristupovať až po jej deklarovaní, inak bude hodnota premennej rovnaká undef.

Príklad práce s premennými:

# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"

Bábka má menné priestorya premenné podľa toho majú oblasť viditeľnosti: Premenná s rovnakým názvom môže byť definovaná v rôznych menných priestoroch. Pri riešení hodnoty premennej sa premenná hľadá v aktuálnom mennom priestore, potom v obklopujúcom mennom priestore atď.

Príklady menného priestoru:

  • globálne - tam idú premenné mimo popisu triedy alebo uzla;
  • menný priestor uzla v popise uzla;
  • priestor názvov triedy v popise triedy.

Aby ste sa vyhli nejednoznačnosti pri prístupe k premennej, môžete v názve premennej zadať priestor názvov:

# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var

Dohodnime sa, že cesta ku konfigurácii nginx leží v premennej $nginx_conf_source. Potom bude trieda vyzerať takto:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # здесь используем переменную вместо фиксированной строки
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    $nginx_conf_source = 'puppet:///modules/example/nginx-conf'
    include nginx_example
}

Uvedený príklad je však zlý, pretože existuje nejaký „tajný poznatok“, že niekde vo vnútri triedy sa používa premenná s takým a takým názvom. Oveľa správnejšie je tieto znalosti zovšeobecniť – triedy môžu mať parametre.

Parametre triedy sú premenné v mennom priestore triedy, sú špecifikované v hlavičke triedy a možno ich použiť ako bežné premenné v tele triedy. Hodnoty parametrov sú špecifikované pri použití triedy v manifeste.

Parameter je možné nastaviť na predvolenú hodnotu. Ak parameter nemá predvolenú hodnotu a táto hodnota nie je pri použití nastavená, spôsobí to chybu kompilácie.

Parametrizujme triedu z vyššie uvedeného príkladu a pridáme dva parametre: prvý, povinný, je cesta ku konfigurácii a druhý, voliteľný, je názov balíka s nginx (napríklad v Debiane sú balíky nginx, nginx-light, nginx-full).

# переменные описываются сразу после имени класса в круглых скобках
class nginx_example (
  $conf_source,
  $package_name = 'nginx-light', # параметр со значением по умолчанию
) {
  package { $package_name:
    ensure => installed,
  }
  -> file { '/etc/nginx':
    ensure  => directory,
    source  => $conf_source,
    recurse => true,
    purge   => true,
    force   => true,
  }
  ~> service { 'nginx':
    ensure => running,
    enable => true,
  }
}

node 'server2.testdomain' {
  # если мы хотим задать параметры класса, функция include не подойдёт* — нужно использовать resource-style declaration
  # *на самом деле подойдёт, но про это расскажу в следующей серии. Ключевое слово "Hiera".
  class { 'nginx_example':
    conf_source => 'puppet:///modules/example/nginx-conf',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

V Puppet sa zadávajú premenné. Jedzte veľa dátových typov. Typy údajov sa zvyčajne používajú na overenie hodnôt parametrov odovzdaných triedam a definíciám. Ak odovzdaný parameter nezodpovedá zadanému typu, dôjde k chybe kompilácie.

Typ sa zapisuje bezprostredne pred názov parametra:

class example (
  String $param1,
  Integer $param2,
  Array $param3,
  Hash $param4,
  Hash[String, String] $param5,
) {
  ...
}

Classes: include classname vs class{'classname':}

Každá trieda je zdrojom určitého typu trieda. Rovnako ako pri akomkoľvek inom type zdroja, na rovnakom uzle nemôžu existovať dve inštancie rovnakej triedy.

Ak sa pokúsite pridať triedu do rovnakého uzla dvakrát pomocou class { 'classname':} (bez rozdielu, s rôznymi alebo rovnakými parametrami), dôjde k chybe pri kompilácii. Ale ak použijete triedu v štýle prostriedku, môžete okamžite explicitne nastaviť všetky jej parametre v manifeste.

Ak však použijete include, potom môže byť trieda pridaná toľkokrát, koľkokrát chcete. Faktom je, že include je idempotentná funkcia, ktorá kontroluje, či bola trieda pridaná do adresára. Ak trieda nie je v adresári, pridá ju, a ak už existuje, neurobí nič. Ale v prípade použitia include Počas deklarácie triedy nemôžete nastaviť parametre triedy - všetky požadované parametre musia byť nastavené v externom zdroji údajov - Hiera alebo ENC. O nich si povieme v ďalšom článku.

Definuje

Ako bolo povedané v predchádzajúcom bloku, tá istá trieda nemôže byť prítomná na uzle viac ako raz. V niektorých prípadoch však musíte byť schopní použiť rovnaký blok kódu s rôznymi parametrami na rovnakom uzle. Inými slovami, existuje potreba vlastného typu zdroja.

Napríklad, aby sme nainštalovali modul PHP, v Avito urobíme nasledovné:

  1. Nainštalujte balík s týmto modulom.
  2. Vytvorme konfiguračný súbor pre tento modul.
  3. Vytvoríme symbolický odkaz na konfiguráciu pre php-fpm.
  4. Vytvoríme symbolický odkaz na konfiguráciu pre php cli.

V takýchto prípadoch je vhodný dizajn ako napr definovať (definovať, definovaný typ, definovaný typ zdroja). Define je podobná triede, ale existujú rozdiely: po prvé, každá definícia je typ zdroja, nie zdroj; po druhé, každá definícia má implicitný parameter $title, kam sa dostane názov zdroja, keď je deklarovaný. Rovnako ako v prípade tried, definícia musí byť najprv opísaná a potom môže byť použitá.

Zjednodušený príklad s modulom pre PHP:

define php74::module (
  $php_module_name = $title,
  $php_package_name = "php7.4-${title}",
  $version = 'installed',
  $priority = '20',
  $data = "extension=${title}.son",
  $php_module_path = '/etc/php/7.4/mods-available',
) {
  package { $php_package_name:
    ensure          => $version,
    install_options => ['-o', 'DPkg::NoTriggers=true'],  # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
  }
  -> file { "${php_module_path}/${php_module_name}.ini":
    ensure  => $ensure,
    content => $data,
  }
  file { "/etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
  file { "/etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
}

node server3.testdomain {
  php74::module { 'sqlite3': }
  php74::module { 'amqp': php_package_name => 'php-amqp' }
  php74::module { 'msgpack': priority => '10' }
}

Najjednoduchší spôsob, ako zachytiť chybu Duplicitnej deklarácie, je v Define. Toto sa stane, ak má definícia zdroj s konštantným názvom a na niektorom uzle existujú dve alebo viac inštancií tejto definície.

Je ľahké sa pred tým chrániť: všetky zdroje v definícii musia mať názov v závislosti od $title. Alternatívou je idempotentné pridávanie zdrojov, v najjednoduchšom prípade stačí zdroje spoločné pre všetky inštancie definície presunúť do samostatnej triedy a túto triedu zahrnúť do definície - funkcie include idempotentný.

Existujú aj iné spôsoby, ako dosiahnuť idempotenciu pri pridávaní zdrojov, a to pomocou funkcií defined и ensure_resources, ale o tom vám poviem v ďalšej epizóde.

Závislosti a upozornenia pre triedy a definície

Triedy a definície pridávajú nasledujúce pravidlá na spracovanie závislostí a upozornení:

  • závislosť na triede/definovať pridáva závislosti na všetkých zdrojoch triedy/definovať;
  • závislosť triedy/definovať pridáva závislosti ku všetkým zdrojom triedy/definovať;
  • upozornenie na triedu/definovať upozorní všetky zdroje triedy/definovať;
  • class/define predplatné prihlási na odber všetkých zdrojov triedy/define.

Podmienené príkazy a selektory

Dokumentácia tu.

if

Tu je to jednoduché:

if ВЫРАЖЕНИЕ1 {
  ...
} elsif ВЫРАЖЕНИЕ2 {
  ...
} else {
  ...
}

ak

if je if inverzne: blok kódu sa vykoná, ak je výraz nepravdivý.

unless ВЫРАЖЕНИЕ {
  ...
}

puzdro

Ani tu nie je nič zložité. Ako hodnoty môžete použiť regulárne hodnoty (reťazce, čísla atď.), regulárne výrazy a typy údajov.

case ВЫРАЖЕНИЕ {
  ЗНАЧЕНИЕ1: { ... }
  ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
  default: { ... }
}

Selektory

Selektor je jazyková konštrukcia podobná case, ale namiesto vykonania bloku kódu vráti hodnotu.

$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }

moduly

Keď je konfigurácia malá, môže sa ľahko uchovávať v jednom manifeste. Ale čím viac konfigurácií popisujeme, tým viac tried a uzlov je v manifeste, rastie a práca s ním sa stáva nepohodlnou.

Okrem toho je tu problém opätovného použitia kódu – keď je všetok kód v jednom manifeste, je ťažké zdieľať tento kód s ostatnými. Na vyriešenie týchto dvoch problémov má Puppet entitu nazývanú moduly.

moduly - sú to sady tried, definícií a iných entít Bábka umiestnené v samostatnom adresári. Inými slovami, modul je nezávislá časť Bábkovej logiky. Napríklad môže existovať modul na prácu s nginx a bude obsahovať to, čo a len to, čo je potrebné na prácu s nginx, alebo môže existovať modul na prácu s PHP atď.

Moduly sú verzované a podporované sú aj vzájomné závislosti modulov. Existuje otvorené úložisko modulov - Puppet Forge.

Na bábkovom serveri sú moduly umiestnené v podadresári modules koreňového adresára. Vo vnútri každého modulu je štandardná adresárová schéma - manifesty, súbory, šablóny, lib atď.

Štruktúra súboru v module

Koreň modulu môže obsahovať nasledujúce adresáre s popisnými názvami:

  • manifests - obsahuje manifesty
  • files - obsahuje súbory
  • templates - obsahuje šablóny
  • lib - obsahuje kód Ruby

Toto nie je úplný zoznam adresárov a súborov, ale pre tento článok to zatiaľ stačí.

Názvy zdrojov a názvy súborov v module

Dokumentácia tu.

Zdroje (triedy, definície) v module nemožno pomenovať akokoľvek chcete. Okrem toho existuje priama zhoda medzi názvom zdroja a názvom súboru, v ktorom bude Puppet hľadať popis tohto zdroja. Ak porušíte pravidlá pomenovania, Puppet jednoducho nenájde popis zdroja a dostanete chybu kompilácie.

Pravidlá sú jednoduché:

  • Všetky prostriedky v module musia byť v mennom priestore modulu. Ak sa modul volá foo, potom by mali byť pomenované všetky zdroje v ňom foo::<anything>, alebo len tak foo.
  • Zdroj s názvom modulu musí byť v súbore init.pp.
  • Pre ostatné zdroje je schéma pomenovania súborov nasledovná:
    • predpona s názvom modulu sa zahodí
    • všetky dvojité dvojbodky, ak nejaké existujú, sa nahradia lomkami
    • je pridané rozšírenie .pp

Ukážem na príklade. Povedzme, že píšem modul nginx. Obsahuje nasledujúce zdroje:

  • trieda nginx popísané v manifeste init.pp;
  • trieda nginx::service popísané v manifeste service.pp;
  • definovať nginx::server popísané v manifeste server.pp;
  • definovať nginx::server::location popísané v manifeste server/location.pp.

šablóny

Určite sami viete, čo sú šablóny, nebudem ich tu podrobne popisovať. Ale pre každý prípad to nechám odkaz na Wikipediu.

Ako používať šablóny: Význam šablóny možno rozšíriť pomocou funkcie template, ktorým je odovzdaná cesta k šablóne. Pre zdroje typu rezeň používa sa spolu s parametrom content. Napríklad takto:

file { '/tmp/example': content => template('modulename/templatename.erb')

Zobraziť cestu <modulename>/<filename> znamená súbor <rootdir>/modules/<modulename>/templates/<filename>.

Okrem toho existuje funkcia inline_template — prijme ako vstup text šablóny, nie názov súboru.

V rámci šablón môžete použiť všetky premenné bábky v aktuálnom rozsahu.

Puppet podporuje šablóny vo formáte ERB a EPP:

Stručne o ERB

Riadiace štruktúry:

  • <%= ВЫРАЖЕНИЕ %> — vložte hodnotu výrazu
  • <% ВЫРАЖЕНИЕ %> — vypočítať hodnotu výrazu (bez jeho vloženia). Sem sa zvyčajne nachádzajú podmienené príkazy (if) a cykly (každý).
  • <%# КОММЕНТАРИЙ %>

Výrazy v ERB sú napísané v Ruby (ERB je vlastne Embedded Ruby).

Ak chcete získať prístup k premenným z manifestu, musíte pridať @ na názov premennej. Ak chcete odstrániť zalomenie riadku, ktoré sa objaví po konštrukcii ovládacieho prvku, musíte použiť uzatváraciu značku -%>.

Príklad použitia šablóny

Povedzme, že píšem modul na ovládanie ZooKeepera. Trieda zodpovedná za vytvorenie konfigurácie vyzerá asi takto:

class zookeeper::configure (
  Array[String] $nodes,
  Integer $port_client,
  Integer $port_quorum,
  Integer $port_leader,
  Hash[String, Any] $properties,
  String $datadir,
) {
  file { '/etc/zookeeper/conf/zoo.cfg':
    ensure  => present,
    content => template('zookeeper/zoo.cfg.erb'),
  }
}

A príslušná šablóna zoo.cfg.erb -Takže:

<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>

dataDir=<%= @datadir %>

<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>

Fakty a vstavané premenné

Konkrétna časť konfigurácie často závisí od toho, čo sa práve deje na uzle. Napríklad v závislosti od vydania Debianu musíte nainštalovať jednu alebo druhú verziu balíka. Toto všetko môžete sledovať manuálne, prepisovanie manifestov, ak sa uzly zmenia. Ale to nie je seriózny prístup, automatizácia je oveľa lepšia.

Na získanie informácií o uzloch má Puppet mechanizmus nazývaný fakty. fakty - sú to informácie o uzle dostupné v manifestoch vo forme obyčajných premenných v globálnom mennom priestore. Napríklad názov hostiteľa, verzia operačného systému, architektúra procesora, zoznam používateľov, zoznam sieťových rozhraní a ich adries a oveľa, oveľa viac. Fakty sú dostupné v manifestoch a šablónach ako bežné premenné.

Príklad práce s faktami:

notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог

Formálne povedané, fakt má názov (reťazec) a hodnotu (k dispozícii sú rôzne typy: reťazce, polia, slovníky). Jedzte súbor zabudovaných faktov. Môžete napísať aj svoj vlastný. Opisujú sa zberatelia faktov ako funkcie v Rubybuď ako spustiteľné súbory. Fakty môžu byť prezentované aj vo formulári textové súbory s údajmi na uzloch.

Počas prevádzky bábkový agent najskôr skopíruje všetky dostupné kolektory faktov z pappetserveru do uzla, potom ich spustí a odošle zozbierané fakty na server; Potom server začne kompilovať katalóg.

Fakty vo forme spustiteľných súborov

Takéto skutočnosti sú umiestnené v moduloch v adresári facts.d. Samozrejme, súbory musia byť spustiteľné. Keď sú spustené, musia vydávať informácie na štandardný výstup buď vo formáte YAML alebo kľúč=hodnota.

Nezabudnite, že tieto fakty platia pre všetky uzly, ktoré sú riadené poppet serverom, na ktorý je váš modul nasadený. Preto v skripte skontrolujte, či má systém všetky programy a súbory potrebné na to, aby váš fakt fungoval.

#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'

Fakty o rubíne

Takéto skutočnosti sú umiestnené v moduloch v adresári lib/facter.

# всё начинается с вызова функции Facter.add с именем факта и блоком кода
Facter.add('ladvd') do
# в блоках confine описываются условия применимости факта — код внутри блока должен вернуть true, иначе значение факта не вычисляется и не возвращается
  confine do
    Facter::Core::Execution.which('ladvdc') # проверим, что в PATH есть такой исполняемый файл
  end
  confine do
    File.socket?('/var/run/ladvd.sock') # проверим, что есть такой UNIX-domain socket
  end
# в блоке setcode происходит собственно вычисление значения факта
  setcode do
    hash = {}
    if (out = Facter::Core::Execution.execute('ladvdc -b'))
      out.split.each do |l|
        line = l.split('=')
        next if line.length != 2
        name, value = line
        hash[name.strip.downcase.tr(' ', '_')] = value.strip.chomp(''').reverse.chomp(''').reverse
      end
    end
    hash  # значение последнего выражения в блоке setcode является значением факта
  end
end

Textové fakty

Takéto skutočnosti sú umiestnené na uzloch v adresári /etc/facter/facts.d v starej Bábke resp /etc/puppetlabs/facts.d v novej Bábke.

examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue

Dostať sa k faktom

Existujú dva spôsoby, ako pristupovať k faktom:

  • cez slovník $facts: $facts['fqdn'];
  • použitie názvu faktu ako názvu premennej: $fqdn.

Najlepšie je použiť slovník $factsalebo ešte lepšie, uveďte globálny menný priestor ($::facts).

Tu je príslušná časť dokumentácie.

Vstavané premenné

Okrem faktov existuje aj niektoré premennédostupné v globálnom mennom priestore.

  • dôveryhodné fakty — premenné, ktoré sú prevzaté z certifikátu klienta (keďže certifikát je zvyčajne vydaný na poppet serveri, agent nemôže len vziať a zmeniť jeho certifikát, takže premenné sú „dôveryhodné“): názov certifikátu, názov certifikátu hostiteľa a domény, prípony z certifikátu.
  • fakty zo servera —premenné súvisiace s informáciami o serveri — verzia, názov, IP adresa servera, prostredie.
  • fakty o agentovi — premenné pridané priamo bábkovým agentom a nie faktorom — názov certifikátu, verzia agenta, verzia bábky.
  • hlavné premenné - Premenné Pappetmaster (sic!). Je to približne rovnaké ako v fakty zo servera, plus hodnoty konfiguračných parametrov sú k dispozícii.
  • premenné kompilátora — premenné kompilátora, ktoré sa líšia v každom rozsahu: názov aktuálneho modulu a názov modulu, v ktorom sa pristupovalo k aktuálnemu objektu. Môžu byť použité napríklad na kontrolu, či sa vaše súkromné ​​triedy nepoužívajú priamo z iných modulov.

Dodatok 1: ako to všetko spustiť a ladiť?

Článok obsahoval veľa príkladov bábkového kódu, ale vôbec nám nepovedal, ako tento kód spustiť. No opravujem sa.

Na spustenie Puppet stačí agent, ale vo väčšine prípadov budete potrebovať aj server.

agent

Minimálne od verzie XNUMX, balíčky bábkového agenta od oficiálne úložisko Puppetlabs obsahujú všetky závislosti (rubín a zodpovedajúce drahokamy), takže neexistujú žiadne problémy s inštaláciou (hovorím o distribúciách založených na Debiane - nepoužívame distribúcie založené na RPM).

V najjednoduchšom prípade na použitie konfigurácie bábky stačí spustiť agenta v režime bez servera: za predpokladu, že kód bábky je skopírovaný do uzla, spustite puppet apply <путь к манифесту>:

atikhonov@atikhonov ~/puppet-test $ cat helloworld.pp 
node default {
    notify { 'Hello world!': }
}
atikhonov@atikhonov ~/puppet-test $ puppet apply helloworld.pp 
Notice: Compiled catalog for atikhonov.localdomain in environment production in 0.01 seconds
Notice: Hello world!
Notice: /Stage[main]/Main/Node[default]/Notify[Hello world!]/message: defined 'message' as 'Hello world!'
Notice: Applied catalog in 0.01 seconds

Je samozrejme lepšie nastaviť server a spustiť agentov na uzloch v režime démona - potom raz za pol hodinu použijú konfiguráciu stiahnutú zo servera.

Môžete napodobniť push model práce - prejdite na uzol, ktorý vás zaujíma, a začnite sudo puppet agent -t... kľúč -t (--test) v skutočnosti obsahuje niekoľko možností, ktoré je možné povoliť jednotlivo. Tieto možnosti zahŕňajú nasledujúce možnosti:

  • nespúšťajte v režime démona (v predvolenom nastavení sa agent spúšťa v režime démona);
  • vypnúť po použití katalógu (v predvolenom nastavení bude agent pokračovať v práci a použije konfiguráciu raz za pol hodinu);
  • napísať podrobný pracovný denník;
  • zobraziť zmeny v súboroch.

Agent má prevádzkový režim bez zmien - môžete ho použiť, keď si nie ste istí, že ste napísali správnu konfiguráciu a chcete si skontrolovať, čo presne agent počas prevádzky zmení. Tento režim je povolený parametrom --noop na príkazovom riadku: sudo puppet agent -t --noop.

Okrem toho môžete povoliť denník ladenia práce - v ňom bábka píše o všetkých akciách, ktoré vykonáva: o zdroji, ktorý práve spracováva, o parametroch tohto zdroja, o tom, aké programy spúšťa. Toto je samozrejme parameter --debug.

Server

V tomto článku sa nebudem zaoberať úplným nastavením pappetserveru a nasadením kódu naň, poviem len, že po vybalení je k dispozícii plne funkčná verzia servera, ktorá nevyžaduje dodatočnú konfiguráciu na prácu s malým počtom uzly (povedzme až sto). Väčší počet uzlov bude vyžadovať ladenie - štandardne bábkový server nespúšťa viac ako štyroch pracovníkov, pre väčší výkon musíte zvýšiť ich počet a nezabudnite zvýšiť limity pamäte, inak bude server väčšinu času zbierať odpadky.

Nasadenie kódu – ak to potrebujete rýchlo a jednoducho, pozrite sa (na r10k)[https://github.com/puppetlabs/r10k], pre malé inštalácie by to malo stačiť.

Dodatok 2: Usmernenia pre kódovanie

  1. Umiestnite všetku logiku do tried a definícií.
  2. Triedy a definície uchovávajte v moduloch, nie v manifestoch popisujúcich uzly.
  3. Použite fakty.
  4. Nevytvárajte if na základe názvov hostiteľov.
  5. Neváhajte pridať parametre pre triedy a definície – je to lepšie ako implicitná logika skrytá v tele triedy/definície.

Prečo to odporúčam urobiť, vysvetlím v nasledujúcom článku.

Záver

Skončíme s úvodom. V ďalšom článku vám poviem o Hiere, ENC a PuppetDB.

Do prieskumu sa môžu zapojiť iba registrovaní užívatelia. Prihlásiť saProsím.

V skutočnosti je toho oveľa viac – môžem písať články na nasledujúce témy, hlasovať o tom, o čom by ste si chceli prečítať:

  • 59,1%Pokročilé bábkové konštrukcie – nejaké sračky ďalšej úrovne: slučky, mapovanie a iné lambda výrazy, kolektory zdrojov, exportované zdroje a komunikácia medzi hostiteľmi cez bábku, značky, poskytovatelia, abstraktné dátové typy.13
  • 31,8%„Som admin mojej matky“ alebo ako sme sa v Avite spriatelili s niekoľkými poppet servermi rôznych verzií a v princípe časť o správe poppet servera.7
  • 81,8%Ako píšeme bábkový kód: prístrojové vybavenie, dokumentácia, testovanie, CI/CD.18

Hlasovalo 22 užívateľov. 9 užívateľov sa zdržalo hlasovania.

Zdroj: hab.com