Puppet je systém pro správu konfigurace. Používá se k uvedení hostitelů do požadovaného stavu a k udržování tohoto stavu.
Pracuji s Puppet už více než pět let. Tento text je v podstatě přeložená a přeuspořádaná kompilace klíčových bodů z oficiální dokumentace, která umožní nováčkům rychle pochopit podstatu Puppet.

Základní informace
Architektura Puppet je klient-server, i když se podporuje i varianta fungování bez serveru s omezenou funkčností.
Používá se pull model: standardně každých půl hodiny se klienti obracejí na server pro konfiguraci a aplikují ji. Pokud jste pracovali s Ansible, ten používá jiný, push model: administrátor spouští proces aplikace konfigurace, sami klienti nic aplikovat nebudou.
Při síťové interakci se používá obousměrné TLS šifrování: server a klient mají své soukromé klíče a odpovídající certifikáty. Obvykle server vydává certifikáty pro klienty, ale v zásadě je možné využití i externího CA.
Seznámení s manifesty
V terminologii Puppet k puppet-serveru se připojují nody (nodes). Konfigurace pro nody se píše v manifestech ve speciálním programovacím jazyce – Puppet DSL.
Puppet DSL je deklarativní jazyk. Popisuje se v něm požadovaný stav nody ve formě deklarací jednotlivých zdrojů, například:
- Soubor existuje a má určité obsah.
- Balíček je nainstalován.
- Služba je spuštěna.
Zdroje mohou být vzájemně propojeny:
- Existují závislosti, které ovlivňují pořadí aplikace zdrojů.
Například, "nejprve nainstaluj balíček, pak uprav konfigurační soubor, poté spusť službu." - Existují upozornění – pokud se zdroj změní, pošle upozornění na zdroje, které jsou k němu přihlášeny.
Například, pokud se změní konfigurační soubor, může se automaticky restartovat služba.
Kromě toho má Puppet DSL funkce a proměnné, jakož i podmínkové operátory a selektory. Také podporuje různé mechanismy šablonování – EPP a ERB.
Puppet je napsán v Ruby, proto jsou mnohé konstrukce a termíny převzaty odtud. Ruby umožňuje rozšiřovat Puppet – dopisovat složitou logiku, nové typy zdrojů, funkce.
Během práce s Puppetem se manifesty pro každou konkrétní uzel na serveru kompilují do adresáře. Katalog — je to seznam zdrojů a jejich vzájemných vztahů po výpočtu hodnot funkcí, proměnných a rozbalení podmínkových operátorů.
Syntaxe a kódový styl
Zde jsou části oficiální dokumentace, které vám pomohou pochopit syntaxi, pokud uvedené příklady nebudou stačit:
Zde je příklad toho, jak vypadá manifest:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}Odstupy a přenosy řádků nejsou povinnou součástí manifestu, nicméně existuje doporučený . Stručné shrnutí:
- Dvou mezerové odsazení, tabulátory se nepoužívají.
- Složené závorky jsou odděleny mezerou, dvojtečka se mezerou neodděluje.
- Čárky za každým parametrem, včetně posledního. Každý parametr by měl být na samostatném řádku. Výjimka se dělá pro případ bez parametrů a jednoho parametru: může být psáno na jednom řádku a bez čárky (t.j.
resource { 'title': }aresource { 'title': param => value }). - Šipky u parametrů musí být na stejné úrovni.
- Šipky vztahů zdrojů jsou psány před nimi.
Umístění souborů na papetserveru
Pro další vysvětlení zavedu pojem „kořenový adresář“. Kořenový adresář je adresář, ve kterém se nachází Puppet konfigurace pro konkrétní uzel.
Kořenový adresář se liší v závislosti na verzi Puppetu a použití prostředí. Prostředí jsou nezávislé sady konfigurace, které jsou uchovávány v samostatných adresářích. Obyčejně se používají v kombinaci s gitem, v takovém případě se prostředí vytvářejí z větví gitu. Odpovídajícím způsobem se každý uzel nachází v tom či onom prostředí. To se nastavuje na samotném uzlu, nebo v ENC, o čemž budu mluvit v dalším článku.
- Ve třetí verzi („starý Puppet“) byla základní adresář
/etc/puppet. Použití prostředí je volitelné — my je například nepoužíváme se starým Puppetem. Pokud se prostředí používají, obvykle se uchovávají v/etc/puppet/environments, kořenovým adresářem bude adresář prostředí. Pokud se prostředí nepoužívají, kořenovým adresářem bude základní. - Od čtvrté verze („nový Puppet“) se používání prostředí stalo povinným, a základní adresář byl přesunut do
/etc/puppetlabs/code. Odpovídajícím způsobem jsou prostředí uložena v/etc/puppetlabs/code/environments, kořenový adresář – adresář prostředí.
V kořenovém adresáři by měla být podsložka manifests, ve které leží jeden nebo více manifestů s popisem uzlů. Kromě toho by tam měla být podsložka modules, ve které leží moduly. Co jsou to moduly, vysvětlím později. Kromě toho může ve starém Papetě být také podsložka files, ve které leží různé soubory, které kopírujeme na uzly. V novém Papetě jsou všechny soubory vyvedeny do modulů.
Soubory manifestů mají příponu .pp.
Pár příkladů v praxi
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 by měl patřit uživateli a skupině root, přístupová práva by měla být 644.
Píšeme manifest:
node 'server1.testdomain' { # blok konfigurace, vztahující se k uzlu server1.testdomain
file { ' /etc/issue ': # popisujeme soubor /etc/issue
ensure => present, # tento soubor by měl existovat
content => 'Debian GNU/Linux n l', # měl by mít tento obsah
owner => root, # uživatel-vlastník
group => root, # skupina-vlastník
mode => '0644', # práva na soubor. Ta jsou nastavena jako řetězec (v uvozovkách), protože jinak bude číslo se 0 na začátku považováno za zapsané v osmičkové soustavě, a všechno půjde jinak než zamýšleno
}
}Vztahy zdrojů na uzlu
Na uzlu server2.testdomain musí běžet nginx s připravenou konfigurací.
Decomponujeme úkol:
- Je třeba, aby byl nainstalován balíček
nginx. - Je třeba, aby byly zkopírovány konfigurační soubory ze serveru.
- Je třeba, aby byl spuštěn servis
nginx. - V případě aktualizace konfigurace je třeba restartovat servis.
Píšeme manifest:
node 'server2.testdomain' { # konfigurace vztahující se k nodu server2.testdomain
package { 'nginx': # popisujeme balíček nginx
ensure => installed, # musí být nainstalován
}
# Přímá šipka (-&) říká, že níže uvedený zdroj musí
# být vytvořen po zdroji popsaném výše.
# Takové závislosti jsou tranzitivní.
-> file { '/etc/nginx': # popisujeme soubor /etc/nginx
ensure => directory, # to musí být adresář
source => 'puppet:///modules/example/nginx-conf', # jeho obsah by měl být brán z puppet serveru na uvedené adrese
recurse => true, # kopírovat soubory rekurzivně
purge => true, # je třeba odstraňovat nadbytečné soubory (ty, které nejsou ve zdroji)
force => true, # odstranit nadbytečné adresáře
}
# Vlnitá šipka (~&) říká, že zdroj níže musí
# se přihlásit k změnám zdroje popsaného výše.
# Vlnitá šipka zahrnuje přímou (-&).
~> service { 'nginx': # popisujeme službu nginx
ensure => running, # musí být spuštěna
enable => true, # měla by se automaticky spouštět při startu systému
}
# Když typ zdroje service dostane oznámení,
# odpovídající služba se restartuje.
}Aby to fungovalo, je potřeba zhruba takové umístění souborů na puppet serveru:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.confTypy zdrojů
Úplný seznam podporovaných typů zdrojů se nachází , zde popíšu pět základních typů, které v mé praxi stačí k vyřešení většiny úloh.
soubor
Spravuje soubory, adresáře, symbolické odkazy, jejich obsah a práva přístupu.
Parametry:
- název zdroje — cesta k souboru (volitelně)
- path — cesta k souboru (pokud není určena v názvu)
- ensure — typ souboru:
absent— odstranit souborpresent— musí být soubor jakéhokoli typu (pokud soubor neexistuje, bude vytvořen obyčejný soubor)soubor— obyčejný soubordirectory— adresářlink— symbolický odkaz
- content — obsah souboru (platí pouze pro obyčejné soubory, nelze použít spolu s source nebo target)
- source — odkaz na cestu, ze které má být kopírován obsah souboru (nelze použít spolu s content nebo target). Může být zadán jak ve formátu URI se schématem
puppet:(pak budou použity soubory z puppet serveru), tak i se schématemhttp:(doufám, že je jasné, co bude v tomto případě), a dokonce se schématemfile:nebo ve formě absolutní cesty bez schématu (potom bude použit soubor z lokální FS na nodu) - target — kam by měl směřovat symlink (nelze použít s content nebo source)
- vlastník — uživatel, kterému by měl patřit soubor
- group — skupina, které by měl patřit soubor
- režim — oprávnění k souboru (ve formě řetězce)
- rekurzivně — zahrnuje rekurzivní zpracování adresářů
- vymazat — zahrnuje odstranění souborů, které nejsou popsány v Puppetu
- vynutit — zahrnuje odstranění adresářů, které nejsou popsány v Puppetu
balíček
Instaluje a odstraňuje balíčky. Může zpracovávat oznámení — přeinstaluje balíček, pokud je nastaven parametr přeinstalovat_při_osvěžení.
Parametry:
- název zdroje — název balíčku (volitelně)
- name — název balíčku (pokud není zadán v názvu)
- poskytovatel — správce balíčků, který je třeba použít
- ensure — požadovaný stav balíčku:
present,nainstalováno— nainstalována jakákoli verzelatest— nainstalována poslední verzeabsent— odstraněno (apt-get remove)vymazáno— odstraněno spolu s konfiguračními soubory (apt-get purge)zadrženo— verze balíčku je zablokována (apt-mark hold)jakýkoli jiný řetězec— nainstalována konkrétní verze
- přeinstalovat_při_osvěžení — pokud
true, pak bude balíček přeinstalován při obdržení oznámení. Užitečné pro distribuce založené na zdrojích, kde může být potřeba znovu sestavit balíčky při změně parametrů sestavení. Ve výchozím nastavenífalse.
service
Spravuje služby. Může zpracovávat oznámení — restartuje službu.
Parametry:
- název zdroje — služba, kterou je třeba spravovat (volitelně)
- name — služba, kterou je třeba spravovat (pokud není zadána v názvu)
- ensure — požadovaný stav služby:
běžící— spuštěnazastavená— zastavena
- umožnit — spravuje možnost spuštění služby:
true— povolený automatický start (systemctl enable)maskovat— zamaskováno (systemctl mask)false— zakázán automatický start (systemctl disable)
- restartovat — příkaz pro restart služby
- status — příkaz pro kontrolu stavu služby
- márestartování — uvádá, zda init skript služby podporuje restart. Pokud
falsea je zadán parametr restartovat — používá se hodnota tohoto parametru. Pokudfalsea parametr restartovat není zadán — služba se zastaví a spustí se znovu (ale v systemd se používá příkazsystemctl restart). - mástatus — uvádí, zda init skript služby podporuje příkaz
status. Pokudfalse, pak se používá hodnota parametru status. Ve výchozím nastavenítrue.
exec
Spouští externí příkazy. Pokud nezadáte parametry vytvoří, pouze_pokud, pokud_nikoli nebo pouze_při_osvěžení, příkaz bude spuštěn při každém běhu Puppetu. Může zpracovávat oznámení — spouští příkaz.
Parametry:
- název zdroje — příkaz, který je třeba provést (volitelně)
- command — příkaz, který má být proveden (pokud není uveden v názvu)
- path — cesty, ve kterých se má hledat spustitelný soubor
- pouze_pokud — pokud uvedený příkaz v tomto parametru skončil s nulovým návratovým kódem, hlavní příkaz bude proveden
- pokud_nikoli — pokud uvedený příkaz v tomto parametru skončil s nenulovým návratovým kódem, hlavní příkaz bude proveden
- vytvoří — pokud uvedený soubor v tomto parametru neexistuje, hlavní příkaz bude proveden
- pouze_při_osvěžení — pokud
true, pak bude příkaz spuštěn pouze v případě, že tento exec obdrží oznámení od jiných zdrojů - cwd — adresář, ze kterého se má příkaz spouštět
- uživatel — uživatel, který má příkaz spouštět
- poskytovatel — s pomocí čeho spouštět příkaz:
- posix — jednoduše se vytváří podproces, je nutné uvést path
- shell — příkaz se spouští v shellu
/bin/sh, není nutné uvádět path, lze používat globbing, trubky a další funkce shellu. Obvykle se určuje automaticky, pokud existují speciální znaky (|,;,&&,||a tak dále).
cron.
Spravuje cron úlohy.
Parametry:
- název zdroje — jednoduše nějaký identifikátor
- ensure — stav cron úlohy:
present— vytvořit, pokud neexistujeabsent— odstranit, pokud existuje
- command — jaký příkaz se má spustit
- environment — v jakém prostředí spustit příkaz (seznam proměnných prostředí a jejich hodnot oddělený
=) - uživatel — od jakého uživatele spustit příkaz
- minuta, hodina, den v týdnu, měsíc, den v měsíci — kdy spustit cron. Pokud není některý z těchto atributů uveden, jeho hodnota v crontabu bude
*.
V Puppet 6.0 cron. jakoby v puppetserver, proto neexistuje dokumentace na oficiální stránce. Ale je v puppet-agent, takže ho není třeba instalovat zvlášť. Dokumentaci k němu lze nalézt , nebo .
O zdrojích obecně
Požadavky na jedinečnost zdrojů
Nejčastější chyba, se kterou se setkáváme — Duplicitní deklarace. Tato chyba se objevuje, když do adresáře patří dva a více zdrojů stejného typu se stejným názvem.
Proto ještě jednou zdůrazňuji: v manifestech pro jednu node by neměly být zdroje stejného typu se stejným názvem (title)!
Někdy je nutné nainstalovat balíčky se stejným názvem, ale různými správci balíčků. V takovém případě je třeba použít parametr name, aby se předešlo chybě:
balíček { 'ruby-mysql':
zajistit => nainstalováno,
název => 'mysql',
poskytovatel => 'gem',
}
balíček { 'python-mysql':
zajistit => nainstalováno,
název => 'mysql',
poskytovatel => 'pip',
}V jiných typech zdrojů existují podobné parametry, které pomáhají vyhnout se duplicaci, — name u service, command u exec, a tak dále.
Metaparametry
Některé speciální parametry má každý typ zdroje, bez ohledu na jeho podstatu.
Úplný seznam metaparametrů .
Krátký seznam:
- vyžadovat — v tomto parametru je uvedeno, od jakých zdrojů závisí tento zdroj.
- before — v tomto parametru je uvedeno, jaké zdroje závisí na tomto zdroji.
- předplatit — v tomto parametru je uvedeno, od jakých zdrojů dostává tento zdroj oznámení.
- oznámit — v tomto parametru je uvedeno, jaké zdroje dostávají oznámení od tohoto zdroje.
Všechny uvedené metaparametry přijímají buď jeden odkaz na zdroj, nebo pole odkazů v čtvercových závorkách.
Odkazy na zdroje
Odkaz na zdroj je prostě zmínka o zdroji. Používají se především k určení závislostí. Odkaz na neexistující zdroj způsobí chybu kompilace.
Syntaxe odkazu je následující: typ zdroje s velkým písmenem (pokud v názvu typu jsou dvojité dvojtečky, pak se každá část názvu mezi dvojtečkami píše s velkým písmenem), dále v čtvercových závorkách název zdroje (velikost písmen názvu se nemění!). Mezi nimi nesmí být mezery, čtvercové závorky se píší hned za názvem typu.
Příklad:
soubor { 'file1': zajistit => přítomný }
soubor { 'file2':
zajistit => adresář,
před => Soubor['file1'],
}
soubor { 'file3': zajistit => nepřítomný }
Soubor['file1'] -> Soubor['file3']Závislosti a oznámení
Jak již bylo řečeno, jednoduché závislosti mezi zdroji jsou tranzitivní. Mimochodem, buďte opatrní při nastavování závislostí — můžete vytvořit cyklické závislosti, což způsobí chybu kompilace.
Na rozdíl od závislostí nejsou oznámení tranzitivní. Pro oznámení platí následující pravidla:
- Pokud zdroj dostává oznámení, je aktualizován. Akce při aktualizaci závisí na typu zdroje — exec spustí příkaz, service restartuje službu, balíček znovu nainstaluje balíček. Pokud pro zdroj není určena akce při aktualizaci, nic se neděje.
- Během jedné aktualizace Puppetu se zdroj neaktualizuje více než jednou. To je možné, protože oznámení zahrnují závislosti a graf závislostí neobsahuje cykly.
- Pokud Puppet změní stav zdroje, zdroj odešle oznámení všem zdrojům, které se na něj přihlásily.
- Pokud je zdroj aktualizován, odešle oznámení všem zdrojům, které se na něj přihlásily.
Zpracování nepopsaných parametrů
Obecně platí, že pokud nějaký parametr zdroje nemá výchozí hodnotu a tento parametr není uveden v manifestu, Puppet nebude měnit tuto vlastnost odpovídajícího zdroje na nodě. Například pokud je zdroji typu soubor nebyl uveden parametr vlastník, Puppet nebude měnit vlastníka odpovídajícího souboru.
Seznámení s třídami, proměnnými a definicemi
Představme si, že máme několik nodů, na kterých je stejná část konfigurace, ale také existují rozdíly - jinak bychom to všechno mohli popsat v jednom bloku node {}. Samozřejmě, že můžeme jednoduše zkopírovat stejné části konfigurace, ale v obecném případě je to špatné řešení - konfigurace se rozrůstá a při změně společné části konfigurace bude nutné upravit to samé na mnoha místech. Při tom se snadno dopustíte chyby, a vůbec princip DRY (don’t repeat yourself) nebyl vymyšlen nadarmo.
Pro řešení tohoto problému je taková konstrukce jako třída.
Třídy
jsou pojmenované bloky kódu Puppetu. Třídy slouží k opětovnému použití kódu.
Nejprve je třeba třídu definovat. Samo o sobě vymezení nepřidává žádné zdroje. Třída se popisuje v manifestech:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}Po tom lze třídu použít:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальшеPříklad z předchozího úkolu - vyjmeme instalaci a nastavení 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í flexibilní, protože vždy přináší stejnou konfiguraci nginx. Udělejme to tak, aby cesta ke konfiguraci byla proměnná, takže tuto třídu lze použít pro instalaci nginx s jakoukoli konfigurací.
To lze udělat .
Pozor: proměnné v Puppetu jsou neměnné!
Kromě toho lze na proměnnou odkazovat pouze po jejím vyhlášení, jinak bude hodnotou 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}"V Puppet existují jmenové prostory, a proměnné mají odpovídající oblast platnosti: proměnná se stejným názvem může být definována v různých jmenových prostorech. Při vyhledávání hodnoty proměnné se proměnná nejprve hledá v aktuálním jmenovém prostoru, poté ve svém obklopujícím a tak dále.
Příklady jmenových prostorů:
- globální — sem spadají proměnné mimo popis třídy nebo uzlu;
- jmenový prostor uzlu v popisu uzlu;
- jmenový prostor třídy v popisu třídy.
Abychom se vyhnuli nejednoznačnosti při odkazování na proměnnou, můžeme uvést jmenový prostor v názvu proměnné:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::varDohodneme se, že cesta k konfiguraci nginx leží v proměnné $nginx_conf_source. Třída tedy bude vypadat následovně:
class nginx_example {
package { 'nginx':
ensure => installed,
}
-> file { '/etc/nginx':
ensure => directory,
source => $nginx_conf_source, # zde používáme proměnnou místo pevného řetězce
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
}Nicméně uvedený příklad je špatný v tom, že existuje jakési „tajné poznání“ o tom, že někde uvnitř třídy se používá proměnná s nějakým názvem. Daleko správnějším by bylo toto poznání učinit veřejným — třídy mohou mít parametry.
Parametry třídy — to jsou proměnné v jmenovém prostoru třídy, jsou definovány v hlavičce třídy a mohou být použity jako běžné proměnné v těle třídy. Hodnoty parametrů se udávají při použití třídy v manifestu.
Parametru lze přiřadit výchozí hodnotu. Pokud nemá parametr výchozí hodnotu a hodnota není určena při použití, způsobí to chybu kompilace.
Pojďme parametrizovat třídu z výše uvedeného příkladu a přidat dva parametry: první, povinný — cesta k konfiguraci, a druhý, volitelný — název balíčku s nginx (v Debianu například existují 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 jsou proměnné typově definované. Existuje . Datové typy se obvykle používají pro validaci hodnot parametrů předávaných do tříd a definic. Pokud předaný parametr neodpovídá uvedenému typu, dojde k chybě kompilace.
Typ se zapisuje přímo 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 typu třída. Stejně jako v případě jakýchkoliv jiných typů zdrojů, na jedné nodě nemohou existovat dva instance stejné třídy.
Pokud se pokusíte přidat třídu na jednu a tutéž nodu dvakrát pomocí class { 'classname':} (bez ohledu na to, zda mají různé nebo stejné parametry), dojde k chybě kompilace. Nicméně v případě použití třídy ve stylu zdroje lze v manifestu okamžitě jasně definovat všechny její parametry.
Pokud však použijete include, pak lze třídu přidávat neomezeně. Jde o to, že include je idempotentní funkce, která kontroluje, zda byla třída přidána do adresáře. Pokud třída v adresáři není — přidá ji, a pokud už existuje, nic se neděje. Ale v případě použití include nelze nastavit parametry třídy při jejím vyhlášení — všechny povinné parametry musí být zadány ve vnějším zdroji dat — Hiera nebo ENC. O těchto se zmíníme v následujícím článku.
Definice
Jak bylo zmíněno v předchozím bloku, stejná třída nemůže být přítomna na nodě více než jednou. Nicméně v některých případech je potřeba mít možnost aplikovat stejný blok kódu s různými parametry na jedné nodě. Jinými slovy, je potřeba vlastního typu zdroje.
Například pro instalaci modulu PHP v Avitu děláme následující:
- Nainstalujeme balíček s tímto modulem.
- Vytvoříme konfigurační soubor pro tento modul.
- Vytvoříme symlink na konfiguraci pro php-fpm.
- Vytvoříme symlink na konfiguraci pro php cli.
V takových případech se používá konstrukce jako (define, defined type, defined resource type). Definice je podobná třídě, ale existují rozdíly: za prvé, každá definice je typem zdroje, nikoli zdrojem; za druhé, každá definice má implicitní parametr $title, kam se ukládá název zdroje při jeho vyhlášení. Stejně jako u tříd, nejprve je třeba definici popsat, poté ji lze použít.
Zjednodušený příklad modulu 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'], # debianové php balíčky sami vytvářejí symlinky a restartují službu php-fpm - nám to není potřeba, protože symlink i službu spravujeme pomocí 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' }
}V definici je nejsnazší chytit chybu Duplicate declaration. To nastane, pokud v definici existuje zdroj se konstantním názvem a na nějakém uzlu je více než jeden instance této definice.
Ochráníte se před tím snadno: všechny zdroje uvnitř definice by měly mít názvy závislé na $title. Jako alternativu - idempotentní přidávání zdrojů, v nejjednodušším případě je dostatečné vyvodit společné pro všechny instance definice zdroje do samostatné třídy a zahrnout tuto třídu do definice - funkce include je idempotentní.
Existují i jiné způsoby, jak dosáhnout idempotentnosti při přidávání zdrojů, konkrétně použití funkcí defined a ensure_resources, ale o tom budu vyprávět v příští sérii.
Závislosti a notifikace pro třídy a definice
Třídy a definice přidávají následující pravidla pro zpracování závislostí a notifikací:
- závislost na třídě/definici přidává závislosti na všechny zdroje třídy/definice;
- závislost třídy/definice přidává závislosti všem zdrojům třídy/definice;
- notifikace třídy/definice notifikuje všechny zdroje třídy/definice;
- přihlášení na třídu/definici přihlašuje na všechny zdroje třídy/definice.
Podmínkové operátory a selektory
if
Tady je to jednoduché:
if VÝRAZ1 {
...
} elsif VÝRAZ2 {
...
} else {
...
}pokud_nikoli
unless - to je obrácené if: blok kódu se vykoná, pokud je výraz nepravdivý.
unless VÝRAZ {
...
}case
Tady také není nic složitého. Jako hodnoty můžete použít běžné hodnoty (řetězce, čísla atd.), regulární výrazy a také datové typy.
case VÝRAZ {
HODNOTA1: { ... }
HODNOTA2, HODNOTA3: { ... }
default: { ... }
}Selektory
Selector je jazyková konstrukce, která se podobá case, ale místo vykonávání bloku kódu vrací hodnotu.
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }Moduly
Když je konfigurace malá, lze ji snadno udržovat v jednom manifestu. Ale čím více konfigurací popisujeme, tím více tříd a uzlů se v manifestu objevuje, a ten se zvětšuje, což ztěžuje práci s ním.
Kromě toho existuje problém s opětovným použitím kódu — když je veškerý kód v jednom manifestu, je těžké s ním sdílet s ostatními. Pro řešení těchto dvou problémů existuje v Puppetě entita zvaná moduly.
Moduly — to jsou sady tříd, definic a dalších entit Puppet, umístěné do samostatného adresáře. Jinými slovy, modul je nezávislý kus logiky Puppet. Například může být modul pro práci s nginx, který bude obsahovat jen to, co je potřeba pro práci s nginx, a může existovat modul pro práci s PHP, a tak dále.
Moduly jsou verzované a také podporují závislosti mezi moduly. Existuje veřejný repozitář modulů — .
Na serveru Puppet jsou moduly uloženy v podadresáři modules kořenového adresáře. Uvnitř každého modulu je standardní struktura adresářů — manifests, files, templates, lib a tak dále.
Struktura souborů v modulu
V kořenovém adresáři modulu mohou být následující adresáře s výstižnými názvy:
manifests— obsahuje manifestyfiles— obsahuje souborytemplates— obsahuje šablonylib— obsahuje Ruby kód
Toto není úplný seznam adresářů a souborů, ale pro tento článek je zatím dostatečný.
Názvy zdrojů a názvy souborů v modulu
Zdroje (třídy, definice) v modulu nelze pojmenovávat libovolně. Kromě toho existuje přímá vazba mezi názvem zdroje a názvem souboru, ve kterém Puppet hledá popis tohoto zdroje. Pokud se pravidla názvání poruší, Puppet jednoduše nenajde popis zdrojů a dojde k chybě kompilace.
Pravidla jsou jednoduchá:
- Všechny zdroje v modulu musí být ve jmenném prostoru modulu. Pokud se modul nazývá
foo, pak všechny zdroje v něm musí mít názevfoo::, nebo jednodušefoo. - Zdroje s názvem modulu musí být v souboru
init.pp. - Pro ostatní zdroje platí tato pravidla o názvech souborů:
- prefix s názvem modulu se opomíjí
- všechna dvojitá dvojtečka, pokud existují, jsou nahrazena lomítky
- přidá se přípona
.pp
Ukáži na příkladu. Předpokládejme, že píšu modul nginx. Obsahuje následující zdroje:
- třída
nginxpopisováno v manifestuinit.pp; - třída
nginx::servicepopisováno v manifestuservice.pp; - definice
nginx::serverpopisováno v manifestuserver.pp; - definice
nginx::server::locationpopisováno v manifestuserver/location.pp.
Šablony
Určitě víte, co jsou šablony, nebudu to zde podrobně rozebírat. Ale pro jistotu zanechám .
Jak používat šablony: hodnotu šablony lze rozvinout pomocí funkce template, ke které se předává cesta k šabloně. Pro zdroje typu soubor používáme společně s parametrem content. Například takto:
file { '/tmp/example': content => template('modulename/templatename.erb')Cesta ve tvaru / předpokládá soubor /modules//templates/.
Kromě toho existuje funkce inline_template — této funkci se předává text šablony, nikoli název souboru.
Uvnitř šablon je možné používat všechny proměnné Puppet v aktuálním rozsahu.
Puppet podporuje šablony ve formátu ERB a EPP:
Stručně o ERB
Řídící konstrukce:
<%= ВЫРАЖЕНИЕ %>— vložit hodnotu výrazu<% ВЫРАЖЕНИЕ %>— vyhodnotit hodnotu výrazu (aniž by ji vložil). Sem patří běžné podmínkové operátory (if), cykly (each).<%# КОММЕНТАРИЙ %>
Výrazy v ERB jsou psány v Ruby (ve skutečnosti, ERB – to je Embedded Ruby).
Pro přístup k proměnným z manifestu je potřeba napsat @ k názvu proměnné. Abyste odstranili zalomení řádku, které se objeví po řídící konstrukci, je třeba použít uzavírací tag -%>.
Příklad použití šablony
Předpokládejme, že píšu modul pro správu ZooKeeper. Třída, která zajišťuje vytvoření konfiguračního souboru, vypadá přibližně 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 vypadá takto:
0 -%>
server.=::;
dataDir=
=Fakty a vestavěné proměnné
Konkrétní část konfigurace často závisí na tom, co se právě děje na nodu. Například v závislosti na tom, jakou verzi Debianu máte, je třeba nainstalovat určitou verzi balíčku. Můžete to sledovat ručně, přepisovat manifesty v případě změny nodů. Ale to je neefektivní přístup, automatizace je mnohem lepší.
Pro získání informací o nodech v Puppet existuje mechanismus zvaný fakta. Fakta — to jsou informace o nodu dostupné v manifeztech 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 dalšího. Fakta jsou v manifeztech a šablonách dostupná jako běžné proměnné.
Příklad práce s fakty:
notify { "Běží OS ${facts['os']['name']} verze ${facts['os']['release']['full']}": }
# zdroj typu notify prostě vypisuje zprávu do loguFormálně řečeno, fakt má jméno (řetězec) a hodnotu (dostupné jsou různé typy: řetězce, pole, slovníky). Existuje . Můžete také psát vlastní. Sběrače faktů jsou popsány , nebo jako . Také fakta mohou být prezentována ve formě na nodech.
Během běhu Puppet agent nejprve zkopíruje ze serveru Puppet na nod všechny dostupné sběrače faktů, poté je spustí a odešle shromážděná fakta na server; až poté server začíná s kompilací katalogu.
Fakta ve formě spustitelných souborů
Taková fakta jsou umístěna do modulů do adresáře facts.d. Samozřejmě, soubory musí být spustitelné. Při spuštění by měly na standardní výstup vypisovat informace buď ve formátu YAML, nebo ve formátu „klíč=hodnota“.
Nezapomeňte, že fakta se rozšiřují na všechny nody, které jsou pod správou Puppet serveru, na který se nahrává váš modul. Proto v skriptu zajistěte kontrolu, že v systému jsou všechny potřebné programy a soubory pro fungování vašeho faktu.
#!/bin/sh
echo "testfact=success"#!/bin/sh
echo '{"testyamlfact":"success"}'Fakta v Ruby
Taková fakta jsou umístěna do modulů do adresáře 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
endTextová fakta
Taková fakta jsou umístěna na nody do adresáře /etc/facter/facts.d ve starém Puppetu nebo /etc/puppetlabs/facts.d v novém Puppetu.
examplefact=examplevalue---
examplefact2: examplevalue2
anotherfact: anothervaluePřístup k faktům
K faktům se lze dostat dvěma způsoby:
- přes slovník
$facts:$facts['fqdn']; - použitím názvu fakta jako názvu proměnné:
$fqdn.
Nejlépe je používat slovník $facts, a ještě lépe uvádět globální namespaces ($::facts).
Vestavěné proměnné
Kromě faktů existují také , které jsou dostupné ve globálním prostoru názvů.
- trusted facts — proměnné, které jsou vzaty z certifikátu klienta (jelikož certifikát je obvykle vydáván na serveru Puppet, agent nemůže jen tak vzít a změnit svůj certifikát, a proto proměnné jsou "důvěryhodné"): název certifikátu, jméno hostitele a domény, rozšíření z certifikátu.
- server facts — proměnné týkající se informací o serveru — verze, jméno, IP adresa serveru, prostředí.
- agent facts — proměnné, které jsou přidávány přímo agentem Puppet, nikoli Facterem — název certifikátu, verze agenta, verze Puppetu.
- master variables — proměnné Puppetmasteru (sic!). Tam je přibližně to samé jako v server facts, plus jsou dostupné hodnoty konfiguračních parametrů.
- compiler variables — proměnné kompilátoru, které se liší v každém rozsahu platnosti: název aktuálního modulu a název modulu, ve kterém bylo volání aktuálního objektu. Můžete je použít například k ověření, že vaše soukromé třídy nejsou používány přímo z jiných modulů.
Dodatek 1: jak to všechno spustit a debagovat?
V článku bylo mnoho příkladů kódu Puppet, ale vůbec nebylo řečeno, jak tento kód spustit. Omlouvám se.
Pro práci s Puppet stačí agent, ale pro většinu případů bude potřeba i server.
Agent
Minimálně od verze pět obsahují balíčky puppet-agent z všechny závislosti (ruby a odpovídající gemy), takže s instalací nejsou žádné obtíže (mluvím o distribucích založených na Debianu — distribucím založeným na RPM se vyhýbáme).
V nejjednodušším případě pro použití konfigurace Puppet stačí spustit agenta v bezserverovém režimu: za předpokladu, že je kód Puppet zkopírován na uzel, spustí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 secondsJe lepší samozřejmě spustit server a spustit agenty na nodech v režimu démona — pak budou každých třicet minut aplikovat konfiguraci staženou ze serveru.
Můžete napodobit push model práce — přihlásit se na zajímavou node a spustit sudo puppet agent -t. Volba -t (--test) ve skutečnosti zahrnuje několik možností, které lze zapnout i jednotlivě. Mezi těmito možnostmi jsou následující:
- nepracovat v režimu démona (výchozí agent se spouští v režimu démona);
- ukončit činnost po aplikaci katalogu (výchozí agent bude pokračovat a aplikovat konfiguraci každých třicet minut);
- psát podrobný log činnosti;
- ukazovat změny v souborech.
Agent má režim práce bez změn — může být použit v případě, kdy si nejste jisti, že jste napsali správnou konfiguraci, a chcete zkontrolovat, co přesně agent během práce změní. Tento režim se zapíná parametrem --noop v příkazovém řádku: sudo puppet agent -t --noop.
Kromě toho lze zapnout ladící log činnosti — v něm puppet zapisuje všechny akce, které provádí: o zdroji, který zrovna zpracovává, o parametrech tohoto zdroje, o tom, jaké programy spouští. Samozřejmě, že tento parametr --debug.
Server
Plnou konfiguraci puppet serveru a nasazení kódu na něj v tomto článku nebudu rozebírat, řeknu jen, že z krabice se instaluje zcela funkční verze serveru, která nevyžaduje dodatečné nastavení pro práci v podmínkách malé množství nod (řekněme do sta). Větší množství nod už vyžaduje ladění — podle výchozího nastavení puppet server spouští maximálně čtyři pracovníky, pro větší výkon je třeba zvýšit jejich počet a nezapomenout zvýšit limity paměti, jinak většinu času server bude provádět garbage collect.
Nasazení kódu — pokud potřebujete rychle a jednoduše, podívejte se (na r10k)[], pro malé instalace by to mělo stačit.
Dodatek 2: doporučení pro psaní kódu
- Vytahujte veškerou logiku do tříd a definic.
- Udržujte třídy a definice v modulech, nikoli v manifestech popisujících uzly.
- Používejte fakta.
- Nevytvářejte podmínky na základě hostnames.
- Nebojte se přidávat parametry pro třídy a definice — je to lepší než nejasná logika skrytá v těle třídy/definice.
Proč doporučuji takto postupovat — vysvětlím v dalším článku.
Závěr
Tímto končíme s úvodem. V dalším článku se budu věnovat Hiera, ENC a PuppetDB.
Pouze registrovaní uživatelé se mohou účastnit ankety. , prosím.
Ve skutečnosti je materiálu mnohem více — mohu napsat články na následující témata, hlasujte, o čem byste byli rádi, kdybyste se dočetli:
- 59,1%Pokročilé konstrukce Pupetu — některé další úrovně: cykly, mapování a další lambda výrazy, kolektory zdrojů, exportované zdroje a mezihostitelská komunikace přes Puppet, štítky, poskytovatelé, abstraktní datové typy.13
- 31,8%„Jsem admin u mámy“ nebo jak jsme v Avitu spojili několik Puppet serverů různých verzí, no a vlastně část o administraci puppet serveru.7
- 81,8%Jak píšeme Puppet kód: nástrojová knihovna, dokumentace, testování, CI/CD.18
Hlasovalo 22 uživatelů. Dalo přednost 9 uživatelům.
Zdroj: habr.com
