Úvod do loutky

Puppet je systém správy konfigurace. Používá se k uvedení hostitelů do požadovaného stavu a udržování tohoto stavu.

S Puppet pracuji už přes pět let. Tento text je v podstatě přeloženou a přeuspořádanou kompilací klíčových bodů z oficiální dokumentace, která začátečníkům umožní rychle pochopit podstatu Puppet.

Úvod do loutky

Základní informace

Operační systém Puppet je klient-server, i když také podporuje provoz bez serveru s omezenou funkčností.

Používá se provozní model pull: standardně jednou za půl hodiny klienti kontaktují server kvůli konfiguraci a použijí ji. Pokud jste pracovali s Ansible, pak používají jiný push model: administrátor zahájí proces použití konfigurace, samotní klienti nic nepoužijí.

Během síťové komunikace se používá obousměrné šifrování TLS: server a klient mají své vlastní soukromé klíče a odpovídající certifikáty. Server obvykle vydává certifikáty pro klienty, ale v zásadě je možné použít externí CA.

Úvod do manifestů

V loutkové terminologii na loutkový server připojit uzly (uzly). Konfigurace pro uzly je zapsána v manifestech ve speciálním programovacím jazyce - Puppet DSL.

Loutkové DSL je deklarativní jazyk. Popisuje požadovaný stav uzlu ve formě deklarací jednotlivých zdrojů, například:

  • Soubor existuje a má specifický obsah.
  • Balíček je nainstalován.
  • Služba byla spuštěna.

Zdroje lze propojit:

  • Existují závislosti, ovlivňují pořadí, ve kterém jsou zdroje využívány.
    Například „nejprve nainstalujte balíček, poté upravte konfigurační soubor a poté spusťte službu.“
  • Existují oznámení - pokud se zdroj změnil, odešle oznámení zdrojům, které jsou k němu přihlášeny.
    Pokud se například změní konfigurační soubor, můžete službu automaticky restartovat.

Kromě toho má Puppet DSL funkce a proměnné, stejně jako podmíněné příkazy a selektory. Podporovány jsou také různé šablonovací mechanismy – EPP a ERB.

Puppet je napsán v Ruby, takže mnoho konstruktů a termínů je převzato odtud. Ruby umožňuje rozšířit Puppet - přidat komplexní logiku, nové typy zdrojů, funkce.

Zatímco Puppet běží, manifesty pro každý konkrétní uzel na serveru jsou kompilovány do adresáře. Adresář je seznam zdrojů a jejich vztahů po výpočtu hodnoty funkcí, proměnných a rozšíření podmíněných příkazů.

Syntaxe a kódový styl

Zde jsou části oficiální dokumentace, které vám pomohou porozumět syntaxi, pokud uvedené příklady nestačí:

Zde je příklad toho, jak manifest vypadá:

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

Odsazení a zalomení řádků nejsou povinnou součástí manifestu, ale existuje doporučení průvodce stylem. Souhrn:

  • Odsazení dvěma mezerami, tabulátory se nepoužívají.
  • Složené závorky jsou odděleny mezerou, dvojtečky nejsou odděleny mezerou.
  • Čárky za každým parametrem, včetně toho posledního. Každý parametr je na samostatném řádku. Výjimku tvoří případ bez parametrů a jednoho parametru: můžete psát na jeden řádek a bez čárky (tj. resource { 'title': } и resource { 'title': param => value }).
  • Šipky na parametrech by měly být na stejné úrovni.
  • Před nimi jsou napsány šipky vztahu zdrojů.

Umístění souborů na pappetserveru

Pro další vysvětlení uvedu pojem „kořenový adresář“. Kořenový adresář je adresář, který obsahuje konfiguraci Puppet pro konkrétní uzel.

Kořenový adresář se liší v závislosti na verzi Puppet a použitých prostředích. Prostředí jsou nezávislé sady konfigurací, které jsou uloženy v samostatných adresářích. Obvykle se používá v kombinaci s git, v takovém případě jsou prostředí vytvořena z větví git. Podle toho je každý uzel umístěn v jednom nebo druhém prostředí. To lze nakonfigurovat na samotném uzlu nebo v ENC, o kterém budu mluvit v příštím článku.

  • Ve třetí verzi ("stará loutka") byl základní adresář /etc/puppet. Použití prostředí je volitelné – například u starého Puppetu je nepoužíváme. Pokud se používají prostředí, jsou obvykle uložena v /etc/puppet/environments, kořenový adresář bude adresář prostředí. Pokud se prostředí nepoužívají, bude kořenový adresář základním adresářem.
  • Počínaje čtvrtou verzí („nová loutka“) se používání prostředí stalo povinným a základní adresář byl přesunut do /etc/puppetlabs/code. V souladu s tím jsou prostředí uložena /etc/puppetlabs/code/environments, kořenový adresář je adresář prostředí.

V kořenovém adresáři musí být podadresář manifests, který obsahuje jeden nebo více manifestů popisujících uzly. Kromě toho by měl existovat podadresář modules, která obsahuje moduly. Jaké moduly jsou, vám řeknu o něco později. Kromě toho může mít starý Puppet také podadresář files, který obsahuje různé soubory, které zkopírujeme do uzlů. V nové loutce jsou všechny soubory umístěny v modulech.

Soubory manifestu mají příponu .pp.

Pár příkladů boje

Popis uzlu a zdroje na něm

Na uzlu server1.testdomain musí být vytvořen soubor /etc/issue s obsahem Debian GNU/Linux n l. Soubor musí být vlastněn uživatelem a skupinou root, musí být pří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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Vztahy mezi zdroji na uzlu

Na uzlu server2.testdomain nginx musí být spuštěn a pracovat s dříve připravenou konfigurací.

Pojďme si problém rozložit:

  • Balíček je potřeba nainstalovat nginx.
  • Je nutné, aby konfigurační soubory byly zkopírovány ze serveru.
  • Služba musí být spuštěna nginx.
  • Pokud je konfigurace aktualizována, je nutné službu restartovat.

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, potřebujete přibližně následující umístění souboru na loutkovém serveru:

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

Typy zdrojů

Kompletní seznam podporovaných typů zdrojů naleznete zde v dokumentaci, zde popíšu pět základních typů, které v mé praxi stačí k řešení většiny problémů.

soubor

Spravuje soubory, adresáře, symbolické odkazy, jejich obsah a přístupová práva.

Volby:

  • název zdroje — cesta k souboru (volitelné)
  • cesta — cesta k souboru (pokud není uvedena v názvu)
  • zajistit - typ souboru:
    • absent - smazat soubor
    • present — musí existovat soubor jakéhokoli typu (pokud soubor neexistuje, vytvoří se běžný soubor)
    • file - běžný soubor
    • directory - adresář
    • link - symbolický odkaz
  • obsah — obsah souboru (vhodné pouze pro běžné soubory, nelze použít společně s zdroj nebo cíl)
  • zdroj — odkaz na cestu, ze které chcete zkopírovat obsah souboru (nelze použít společně s obsah nebo cíl). Lze zadat buď jako URI se schématem puppet: (pak budou použity soubory z loutkového serveru) a se schématem http: (Doufám, že je jasné, co se stane v tomto případě), a dokonce i s diagramem file: nebo jako absolutní cesta bez schématu (pak bude použit soubor z lokálního FS na uzlu)
  • cíl — kam má symbolický odkaz směřovat (nelze použít společně s obsah nebo zdroj)
  • majitel — uživatel, který by měl soubor vlastnit
  • skupina — skupina, do které má soubor patřit
  • způsob — oprávnění k souboru (jako řetězec)
  • opakovat - umožňuje rekurzivní zpracování adresářů
  • purge - umožňuje mazání souborů, které nejsou popsány v Puppet
  • síly - umožňuje mazání adresářů, které nejsou popsány v Puppet

balíček

Instaluje a odstraňuje balíčky. Schopný zpracovávat oznámení - přeinstaluje balíček, pokud je zadán parametr reinstall_on_refresh.

Volby:

  • název zdroje – název balíčku (nepovinné)
  • název — název balíčku (pokud není uvedeno v názvu)
  • Poskytovatel — správce balíčků k použití
  • zajistit — požadovaný stav balení:
    • present, installed - nainstalovaná jakákoli verze
    • latest - nainstalovaná nejnovější verze
    • absent - smazáno (apt-get remove)
    • purged — odstraněn spolu s konfiguračními soubory (apt-get purge)
    • held - verze balíčku je uzamčena (apt-mark hold)
    • любая другая строка — je nainstalována zadaná verze
  • reinstall_on_refresh - pokud true, pak po obdržení upozornění bude balíček přeinstalován. Užitečné pro zdrojové distribuce, kde může být nutné přebudování balíčků při změně parametrů sestavení. Výchozí false.

servis

Spravuje služby. Schopnost zpracovat oznámení - restartuje službu.

Volby:

  • název zdroje — služba, která má být spravována (volitelné)
  • název — služba, kterou je třeba spravovat (pokud není uvedeno v názvu)
  • zajistit — požadovaný stav služby:
    • running - spuštěna
    • stopped - zastavil
  • umožnit — ovládá schopnost spustit službu:
    • true — automatické spouštění je povoleno (systemctl enable)
    • mask - převlečený (systemctl mask)
    • false — automatické spouštění je zakázáno (systemctl disable)
  • znovu - příkaz pro restart služby
  • postavení — příkaz pro kontrolu stavu služby
  • hasrestart — označte, zda initscript služby podporuje restartování. Li false a parametr je zadán znovu — použije se hodnota tohoto parametru. Li false a parametr znovu není zadáno - služba se zastaví a spustí se restart (ale systemd používá příkaz systemctl restart).
  • hasstatus — uveďte, zda initscript služby podporuje příkaz status. Pokud false, pak se použije hodnota parametru postavení. Výchozí true.

exec

Spouští externí příkazy. Pokud neuvedete parametry vytvoří, jen když, pokud nebo pouze osvěžení, příkaz se spustí při každém spuštění Puppet. Schopný zpracovat oznámení - spustí příkaz.

Volby:

  • název zdroje — příkaz, který se má provést (volitelné)
  • příkaz — příkaz, který se má provést (pokud není uveden v názvu)
  • cesta — cesty, ve kterých hledat spustitelný soubor
  • jen když — pokud je příkaz zadaný v tomto parametru dokončen s nulovým návratovým kódem, bude proveden hlavní příkaz
  • pokud — pokud je příkaz zadaný v tomto parametru dokončen s nenulovým návratovým kódem, bude proveden hlavní příkaz
  • vytvoří — pokud soubor zadaný v tomto parametru neexistuje, bude proveden hlavní příkaz
  • pouze osvěžení - pokud true, pak bude příkaz spuštěn pouze tehdy, když tento exec obdrží upozornění z jiných zdrojů
  • cwd — adresář, ze kterého se má příkaz spustit
  • uživatel — uživatel, od kterého se má příkaz spustit
  • Poskytovatel - jak spustit příkaz:
    • posix — je jednoduše vytvořen podřízený proces, nezapomeňte specifikovat cesta
    • skořápka - příkaz je spuštěn v shellu /bin/sh, nemusí být specifikováno cesta, můžete použít globbing, potrubí a další funkce shellu. Obvykle se automaticky zjistí, pokud existují nějaké speciální znaky (|, ;, &&, || atd).

cron

Ovládá cronjobs.

Volby:

  • název zdroje - jen nějaký druh identifikátoru
  • zajistit — stav korunního zaměstnání:
    • present - vytvořit, pokud neexistuje
    • absent - smazat, pokud existuje
  • příkaz - jaký příkaz spustit
  • životní prostředí — ve kterém prostředí spustit příkaz (seznam proměnných prostředí a jejich hodnot přes =)
  • uživatel — od kterého uživatele spustit příkaz
  • minuta, hodina, všední den, měsíc, měsíc den — kdy spustit cron. Pokud některý z těchto atributů není zadán, jeho hodnota v crontab bude *.

V loutce 6.0 cron jako by vyjmuto z krabice v loutkovém serveru, takže na obecných stránkách není žádná dokumentace. Ale on je v krabici v puppet-agent, takže není potřeba jej instalovat samostatně. Můžete se podívat na dokumentaci k němu v dokumentaci k páté verzi PuppetNebo na GitHubu.

O zdrojích obecně

Požadavky na jedinečnost zdroje

Nejčastější chybou, se kterou se setkáváme, je Duplicitní prohlášení. K této chybě dochází, když se v adresáři objeví dva nebo více zdrojů stejného typu se stejným názvem.

Proto napíšu znovu: manifesty pro stejný uzel by neměly obsahovat zdroje stejného typu se stejným názvem!

Někdy je potřeba nainstalovat balíčky se stejným názvem, ale s různými správci balíčků. V tomto případě musíte použít parametr nameaby nedošlo k chybě:

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

Jiné typy zdrojů mají podobné možnosti, aby se zabránilo duplicitě − name у servis, command у exec, a tak dále.

Metaparametry

Každý typ zdroje má nějaké speciální parametry, bez ohledu na jeho povahu.

Úplný seznam meta parametrů v dokumentaci Loutky.

Krátký seznam:

  • vyžadovat — tento parametr udává, na kterých zdrojích tento zdroj závisí.
  • před - Tento parametr určuje, které zdroje závisí na tomto prostředku.
  • upsat — tento parametr určuje, ze kterých zdrojů tento zdroj přijímá upozornění.
  • oznámit — Tento parametr určuje, které zdroje dostávají upozornění z tohoto zdroje.

Všechny uvedené metaparametry akceptují buď jeden odkaz na zdroj, nebo pole odkazů v hranatých závorkách.

Odkazy na zdroje

Odkaz na zdroj je pouze zmínka o zdroji. Používají se hlavně k označení závislostí. Odkazování na neexistující zdroj způsobí chybu kompilace.

Syntaxe odkazu je následující: typ zdroje s velkým písmenem (pokud název typu obsahuje dvojité dvojtečky, pak každá část názvu mezi dvojtečkami je velká), pak název zdroje v hranatých závorkách (velká písmena názvu se nemění!). Neměly by být žádné mezery, hranaté závorky se píší bezprostředně za název typu.

Příklad:

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

Závislosti a upozornění

Dokumentace zde.

Jak bylo uvedeno dříve, jednoduché závislosti mezi zdroji jsou tranzitivní. Mimochodem, buďte opatrní při přidávání závislostí – můžete vytvořit cyklické závislosti, které způsobí chybu při kompilaci.

Na rozdíl od závislostí nejsou oznámení přechodná. Pro oznámení platí následující pravidla:

  • Pokud zdroj obdrží oznámení, je aktualizován. Akce aktualizace závisí na typu prostředku − exec spustí příkaz, servis restartuje službu, balíček přeinstaluje balíček. Pokud prostředek nemá definovanou akci aktualizace, nic se neděje.
  • Během jednoho spuštění Puppet se zdroj neaktualizuje více než jednou. To je možné, protože oznámení obsahují závislosti a graf závislostí neobsahuje cykly.
  • Pokud Puppet změní stav zdroje, zdroj odešle upozornění všem zdrojům, které má k odběru.
  • Pokud je zdroj aktualizován, odešle upozornění všem zdrojům, které jsou k němu přihlášeny.

Manipulace s nespecifikovanými parametry

Zpravidla platí, že pokud některý parametr prostředku nemá výchozí hodnotu a tento parametr není uveden v manifestu, pak Puppet tuto vlastnost pro odpovídající prostředek v uzlu nezmění. Například pokud zdroj typu soubor parametr neuveden owner, pak Puppet nezmění vlastníka odpovídajícího souboru.

Úvod do tříd, proměnných a definic

Předpokládejme, že máme několik uzlů, které mají stejnou část konfigurace, ale jsou zde také rozdíly - jinak bychom to všechno mohli popsat v jednom bloku node {}. Samozřejmě můžete jednoduše zkopírovat identické části konfigurace, ale obecně je to špatné řešení - konfigurace roste, a pokud změníte obecnou část konfigurace, budete muset na mnoha místech upravit to samé. Zároveň je snadné udělat chybu a obecně byl princip DRY (neopakujte se) vynalezen z nějakého důvodu.

K vyřešení tohoto problému existuje takový design jako třída.

Vyučování

Třída je pojmenovaný blok poppet kódu. K opětovnému použití kódu jsou potřeba třídy.

Nejprve je třeba popsat třídu. Samotný popis nikde žádné zdroje nepřidává. Třída je popsána v manifestech:

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

Poté lze třídu použít:

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

Příklad z předchozího úkolu – přesuneme instalaci a konfiguraci nginx do třídy:

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
}

Proměnné

Třída z předchozího příkladu není vůbec flexibilní, protože vždy přináší stejnou konfiguraci nginx. Udělejme cestu ke konfigurační proměnné, pak lze tuto třídu použít k instalaci nginx s libovolnou konfigurací.

To se dá zvládnout pomocí proměnných.

Pozor: proměnné v Puppet jsou neměnné!

Navíc k proměnné lze přistupovat až poté, co byla deklarována, jinak bude hodnota proměnné undef.

Příklad práce s proměnnými:

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

Loutka má jmenné prostorya proměnné podle toho mají oblast viditelnosti: Proměnná se stejným názvem může být definována v různých jmenných prostorech. Při řešení hodnoty proměnné se proměnná vyhledává v aktuálním jmenném prostoru, poté v obklopujícím jmenném prostoru a tak dále.

Příklady jmenného prostoru:

  • globální - jdou tam proměnné mimo popis třídy nebo uzlu;
  • jmenný prostor uzlu v popisu uzlu;
  • jmenný prostor třídy v popisu třídy.

Chcete-li se vyhnout nejednoznačnosti při přístupu k proměnné, můžete v názvu proměnné zadat jmenný prostor:

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

Shodněme se, že cesta ke konfiguraci nginx leží v proměnné $nginx_conf_source. Poté bude třída vypadat 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ý příklad je však špatný, protože existuje nějaká „tajná znalost“, že někde uvnitř třídy je použita proměnná s takovým a takovým názvem. Mnohem správnější je tuto znalost zobecnit – třídy mohou mít parametry.

Parametry třídy jsou proměnné ve jmenném prostoru třídy, jsou specifikovány v záhlaví třídy a lze je použít jako běžné proměnné v těle třídy. Hodnoty parametrů jsou zadány při použití třídy v manifestu.

Parametr lze nastavit na výchozí hodnotu. Pokud parametr nemá výchozí hodnotu a tato hodnota není při použití nastavena, způsobí chybu kompilace.

Pojďme parametrizovat třídu z výše uvedeného příkladu a přidat dva parametry: první, povinný, je cesta ke konfiguraci a druhý, nepovinný, je název balíčku s nginx (v Debianu jsou například 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 se zadávají proměnné. Jíst mnoho datových typů. Datové typy se obvykle používají k ověření hodnot parametrů předávaných třídám a definicím. Pokud předaný parametr neodpovídá zadanému typu, dojde k chybě kompilace.

Typ se zapisuje bezprostředně před název parametru:

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

Třídy: include classname vs class{'classname':}

Každá třída je zdrojem určitého typu třída. Stejně jako u jakéhokoli jiného typu prostředku nemohou být na stejném uzlu dvě instance stejné třídy.

Pokud se pokusíte přidat třídu do stejného uzlu dvakrát pomocí class { 'classname':} (bez rozdílu, s různými nebo stejnými parametry), dojde k chybě kompilace. Pokud ale použijete třídu ve stylu prostředku, můžete okamžitě explicitně nastavit všechny její parametry v manifestu.

Pokud však použijete include, pak lze třídu přidat tolikrát, kolikrát chcete. Faktem je, že include je idempotentní funkce, která kontroluje, zda byla do adresáře přidána třída. Pokud třída v adresáři není, přidá ji, a pokud již existuje, nedělá nic. Ale v případě použití include Parametry třídy nelze nastavit během deklarace třídy - všechny požadované parametry musí být nastaveny v externím zdroji dat - Hiera nebo ENC. O nich si povíme v dalším článku.

Definuje

Jak bylo řečeno v předchozím bloku, stejná třída nemůže být na uzlu přítomna více než jednou. V některých případech však musíte být schopni použít stejný blok kódu s různými parametry na stejném uzlu. Jinými slovy, existuje potřeba vlastního typu zdroje.

Abychom například nainstalovali modul PHP, v Avitu provedeme následující:

  1. Nainstalujte balíček s tímto modulem.
  2. Vytvořme konfigurační soubor pro tento modul.
  3. Vytvoříme symbolický odkaz na konfiguraci pro php-fpm.
  4. Vytvoříme symbolický odkaz na konfiguraci pro php cli.

V takových případech je vhodné provedení jako např definovat (definovat, definovaný typ, definovaný typ zdroje). Define je podobná třídě, ale existují rozdíly: za prvé, každá definice je typ zdroje, nikoli zdroj; za druhé, každá definice má implicitní parametr $title, kam přejde název zdroje, když je deklarován. Stejně jako v případě tříd musí být nejprve popsána definice a poté může být použita.

Zjednodušený příklad s modulem pro 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' }
}

Nejjednodušší způsob, jak zachytit chybu duplicitní deklarace, je v Define. K tomu dochází, pokud má definice prostředek s konstantním názvem a na některém uzlu existují dvě nebo více instancí této definice.

Je snadné se před tím chránit: všechny zdroje uvnitř definice musí mít název v závislosti na $title. Alternativou je idempotentní přidávání zdrojů, v nejjednodušším případě stačí zdroje společné všem instancím definice přesunout do samostatné třídy a tuto třídu zahrnout do definice - funkce include idempotentní.

Existují další způsoby, jak dosáhnout idempotence při přidávání zdrojů, a to pomocí funkcí defined и ensure_resources, ale o tom vám povím v příštím díle.

Závislosti a upozornění pro třídy a definice

Třídy a definice přidávají následující pravidla pro zpracování závislostí a oznámení:

  • závislost na třídě/definice přidává závislosti na všech prostředcích třídy/definice;
  • závislost třídy/definice přidává závislosti ke všem zdrojům třídy/definice;
  • oznámení třídy/definice upozorní všechny zdroje třídy/definice;
  • class/define předplatné odebírá všechny prostředky třídy/define.

Podmíněné příkazy a selektory

Dokumentace zde.

if

Zde je vše jednoduché:

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

pokud

if je if obráceně: blok kódu bude proveden, pokud je výraz nepravdivý.

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

případ

Ani zde není nic složitého. Jako hodnoty můžete použít regulární hodnoty (řetězce, čísla atd.), regulární výrazy a datové typy.

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

Selektory

Selektor je jazyková konstrukce podobná case, ale místo provedení bloku kódu vrátí hodnotu.

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

Moduly

Když je konfigurace malá, lze ji snadno uchovat v jednom manifestu. Ale čím více konfigurací popisujeme, tím více tříd a uzlů je v manifestu, roste a práce s ním je nepohodlná.

Navíc je tu problém opětovného použití kódu – když je všechen kód v jednom manifestu, je těžké tento kód sdílet s ostatními. K vyřešení těchto dvou problémů má Puppet entitu zvanou moduly.

Moduly - jedná se o sady tříd, definic a dalších entit Puppet umístěných v samostatném adresáři. Jinými slovy, modul je nezávislá část loutkové logiky. Může tam být například modul pro práci s nginx a ten bude obsahovat co a jen to, co je potřeba pro práci s nginx, nebo může existovat modul pro práci s PHP a podobně.

Moduly jsou verzovány a jsou podporovány i vzájemné závislosti modulů. Existuje otevřené úložiště modulů - Puppet Forge.

Na loutkovém serveru jsou moduly umístěny v podadresáři modules kořenového adresáře. Uvnitř každého modulu je standardní adresářové schéma – manifesty, soubory, šablony, lib a tak dále.

Struktura souboru v modulu

Kořen modulu může obsahovat následující adresáře s popisnými názvy:

  • manifests - obsahuje manifesty
  • files - obsahuje soubory
  • templates - obsahuje šablony
  • lib — obsahuje kód Ruby

Toto není úplný seznam adresářů a souborů, ale pro tento článek to prozatím stačí.

Názvy zdrojů a názvy souborů v modulu

Dokumentace zde.

Prostředky (třídy, definice) v modulu nelze pojmenovat, jak chcete. Kromě toho existuje přímá shoda mezi názvem zdroje a názvem souboru, ve kterém bude Puppet hledat popis tohoto zdroje. Pokud porušíte pravidla pro pojmenování, Puppet jednoduše nenajde popis zdroje a dostanete chybu kompilace.

Pravidla jsou jednoduchá:

  • Všechny prostředky v modulu musí být ve jmenném prostoru modulu. Pokud je modul volán foo, pak by měly být všechny prostředky v něm pojmenovány foo::<anything>, nebo prostě foo.
  • Zdroj s názvem modulu musí být v souboru init.pp.
  • U ostatních zdrojů je schéma pojmenování souborů následující:
    • prefix s názvem modulu se zahodí
    • všechny dvojité dvojtečky, pokud existují, jsou nahrazeny lomítky
    • je přidáno rozšíření .pp

Ukážu na příkladu. Řekněme, že píšu modul nginx. Obsahuje následující zdroje:

  • třída nginx popsané v manifestu init.pp;
  • třída nginx::service popsané v manifestu service.pp;
  • definovat nginx::server popsané v manifestu server.pp;
  • definovat nginx::server::location popsané v manifestu server/location.pp.

šablony

Jistě sami víte, co jsou šablony, nebudu je zde podrobně popisovat. Ale pro každý případ to nechám odkaz na Wikipedii.

Jak používat šablony: Význam šablony lze rozšířit pomocí funkce template, kterému je předána cesta k šabloně. Pro zdroje typu soubor používá se společně s parametrem content. Například takto:

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

Zobrazit cestu <modulename>/<filename> implikuje soubor <rootdir>/modules/<modulename>/templates/<filename>.

Kromě toho existuje funkce inline_template — jako vstup přijímá text šablony, nikoli název souboru.

V rámci šablon můžete použít všechny proměnné Puppet v aktuálním rozsahu.

Puppet podporuje šablony ve formátu ERB a EPP:

Krátce o ERB

Řídící struktury:

  • <%= ВЫРАЖЕНИЕ %> — vložte hodnotu výrazu
  • <% ВЫРАЖЕНИЕ %> — vypočítat hodnotu výrazu (bez jeho vložení). Obvykle sem jdou podmíněné příkazy (if) a cykly (každý).
  • <%# КОММЕНТАРИЙ %>

Výrazy v ERB jsou psány v Ruby (ERB je ve skutečnosti Embedded Ruby).

Chcete-li získat přístup k proměnným z manifestu, musíte přidat @ na název proměnné. Chcete-li odstranit zalomení řádku, které se objeví po konstrukci ovládacího prvku, musíte použít uzavírací značku -%>.

Příklad použití šablony

Řekněme, že píšu modul pro ovládání ZooKeeperu. Třída odpovědná za vytvoření konfigurace vypadá 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 odpovídající šablona zoo.cfg.erb - Tak:

<% 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 -%>

Fakta a vestavěné proměnné

Konkrétní část konfigurace často závisí na tom, co se právě děje na uzlu. Například v závislosti na vydání Debianu musíte nainstalovat jednu nebo druhou verzi balíčku. To vše můžete sledovat ručně, přepisování manifestů, pokud se uzly změní. Ale to není seriózní přístup, automatizace je mnohem lepší.

Pro získání informací o uzlech má Puppet mechanismus zvaný fakta. Fakta - toto jsou informace o uzlu dostupné v manifestech ve formě běžných proměnných v globálním jmenném prostoru. Například název hostitele, verze operačního systému, architektura procesoru, seznam uživatelů, seznam síťových rozhraní a jejich adresy a mnoho, mnoho dalšího. Fakta jsou k dispozici v manifestech a šablonách jako běžné proměnné.

Příklad práce s fakty:

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

Formálně řečeno má fakt název (řetězec) a hodnotu (k dispozici jsou různé typy: řetězce, pole, slovníky). Jíst soubor vestavěných faktů. Můžete si také napsat vlastní. Jsou popsáni sběratelé faktů jako funkce v Rubybuď jako spustitelné soubory. Ve formuláři mohou být uvedena i fakta textové soubory s daty na uzlech.

Během provozu loutkový agent nejprve zkopíruje všechny dostupné kolektory faktů z pappetserveru do uzlu, poté je spustí a odešle shromážděná fakta na server; Poté server začne kompilovat katalog.

Fakta ve formě spustitelných souborů

Tyto skutečnosti jsou umístěny v modulech v adresáři facts.d. Soubory samozřejmě musí být spustitelné. Při spuštění musí vydávat informace na standardní výstup buď ve formátu YAML nebo klíč=hodnota.

Nezapomeňte, že tato fakta platí pro všechny uzly, které jsou řízeny poppet serverem, na který je váš modul nasazen. Proto ve skriptu dbejte na to, abyste zkontrolovali, zda má systém všechny programy a soubory potřebné k tomu, aby váš fakt fungoval.

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

Fakta o Ruby

Tyto skutečnosti jsou umístěny v modulech v adresáři 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á fakta

Tyto skutečnosti jsou umístěny na uzlech v adresáři /etc/facter/facts.d ve staré Loutce nebo /etc/puppetlabs/facts.d v nové Loutce.

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

Dostat se k faktům

Existují dva způsoby, jak přistupovat k faktům:

  • přes slovník $facts: $facts['fqdn'];
  • pomocí názvu faktu jako názvu proměnné: $fqdn.

Nejlepší je použít slovník $facts, nebo ještě lépe, uveďte globální jmenný prostor ($::facts).

Zde je příslušná část dokumentace.

Vestavěné proměnné

Kromě faktů existuje také některé proměnné, dostupný v globálním jmenném prostoru.

  • důvěryhodná fakta — proměnné, které jsou převzaty z certifikátu klienta (protože certifikát je obvykle vydáván na poppet serveru, agent nemůže jen převzít a změnit jeho certifikát, takže proměnné jsou „důvěryhodné“): název certifikátu, název certifikátu hostitele a domény, přípony z certifikátu.
  • fakta o serveru —proměnné související s informacemi o serveru—verze, název, IP adresa serveru, prostředí.
  • fakta o agentovi — proměnné přidané přímo loutkovým agentem, nikoli faktorem — název certifikátu, verze agenta, verze loutky.
  • hlavní proměnné - Proměnné Pappetmaster (sic!). Je to asi stejné jako v fakta o serveru, plus hodnoty konfiguračních parametrů jsou k dispozici.
  • proměnné kompilátoru — proměnné kompilátoru, které se liší v každém rozsahu: název aktuálního modulu a název modulu, ve kterém byl aktuální objekt zpřístupněn. Mohou být použity například ke kontrole, zda vaše soukromé třídy nejsou používány přímo z jiných modulů.

Dodatek 1: jak to všechno spustit a odladit?

Článek obsahoval mnoho příkladů loutkového kódu, ale vůbec nám neřekl, jak tento kód spustit. No, opravuji se.

Ke spuštění Puppet postačí agent, ale ve většině případů budete potřebovat také server.

Agent

Přinejmenším od verze XNUMX jsou balíčky puppet-agent oficiální repozitář Puppetlabs obsahují všechny závislosti (ruby a odpovídající drahokamy), takže neexistují žádné potíže s instalací (mluvím o distribucích založených na Debianu - nepoužíváme distribuce založené na RPM).

V nejjednodušším případě pro použití konfigurace loutky stačí spustit agenta v režimu bez serveru: pokud je kód loutky zkopírován do uzlu, spusťte 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 samozřejmě lepší nastavit server a spustit agenty na uzlech v režimu démona - pak jednou za půl hodiny použijí konfiguraci staženou ze serveru.

Můžete napodobit push model práce - přejděte na uzel, který vás zajímá, a začněte sudo puppet agent -t. Klíč -t (--test) ve skutečnosti obsahuje několik možností, které lze povolit jednotlivě. Tyto možnosti zahrnují následující:

  • nespouštět v režimu démona (ve výchozím nastavení se agent spouští v režimu démona);
  • vypnout po použití katalogu (ve výchozím nastavení bude agent pokračovat v práci a použije konfiguraci jednou za půl hodiny);
  • napsat podrobný pracovní deník;
  • zobrazit změny v souborech.

Agent má provozní režim beze změn - můžete jej použít, když si nejste jisti, že jste napsali správnou konfiguraci a chcete si ověřit, co přesně agent během provozu změní. Tento režim je povolen parametrem --noop na příkazovém řádku: sudo puppet agent -t --noop.

Kromě toho můžete povolit protokol ladění práce - v něm loutka píše o všech akcích, které provádí: o zdroji, který právě zpracovává, o parametrech tohoto zdroje, o tom, jaké programy spouští. To je samozřejmě parametr --debug.

Server

V tomto článku nebudu uvažovat o úplném nastavení pappetserveru a nasazení kódu na něj; řeknu pouze, že z krabice je plně funkční verze serveru, která nevyžaduje další konfiguraci pro práci s malým počtem uzly (řekněme až sto). Větší počet uzlů bude vyžadovat ladění - standardně loutkový server nespouští více než čtyři pracovníky, pro větší výkon je třeba zvýšit jejich počet a nezapomeňte zvýšit limity paměti, jinak bude server většinu času sbírat odpadky.

Nasazení kódu – pokud to potřebujete rychle a snadno, podívejte se (na r10k)[https://github.com/puppetlabs/r10k], pro malé instalace by to mělo stačit.

Dodatek 2: Pokyny pro kódování

  1. Umístěte veškerou logiku do tříd a definic.
  2. Udržujte třídy a definice v modulech, nikoli v manifestech popisujících uzly.
  3. Použijte fakta.
  4. Nevytvářejte if na základě názvů hostitelů.
  5. Klidně přidejte parametry pro třídy a definice – to je lepší než implicitní logika skrytá v těle třídy/definice.

Proč to doporučuji udělat, vysvětlím v dalším článku.

Závěr

Skončíme s úvodem. V příštím článku vám povím o Hiera, ENC a PuppetDB.

Průzkumu se mohou zúčastnit pouze registrovaní uživatelé. Přihlásit se, prosím.

Ve skutečnosti je materiálu mnohem více – mohu psát články na následující témata, hlasovat o tom, o čem byste si rádi přečetli:

  • 59,1%Pokročilé loutkové konstrukce – nějaké sračky další úrovně: smyčky, mapování a další lambda výrazy, sběrače zdrojů, exportované zdroje a komunikace mezi hostiteli přes Puppet, značky, poskytovatelé, abstraktní datové typy.13
  • 31,8%„Jsem admin mé matky“ aneb jak jsme se v Avitu spřátelili s několika poppet servery různých verzí a v zásadě část o správě poppet serveru.7
  • 81,8%Jak píšeme kód loutky: instrumentace, dokumentace, testování, CI/CD.18

Hlasovalo 22 uživatelů. 9 uživatelů se zdrželo hlasování.

Zdroj: www.habr.com