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.
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í
- 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
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 souborpresent
— musí existovat soubor jakéhokoli typu (pokud soubor neexistuje, vytvoří se běžný soubor)file
- běžný soubordirectory
- 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ématemhttp:
(Doufám, že je jasné, co se stane v tomto případě), a dokonce i s diagramemfile:
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 verzelatest
- nainstalovaná nejnovější verzeabsent
- 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ěnastopped
- 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. Lifalse
a parametr znovu není zadáno - služba se zastaví a spustí se restart (ale systemd používá příkazsystemctl restart
). - hasstatus — uveďte, zda initscript služby podporuje příkaz
status
. Pokudfalse
, 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 neexistujeabsent
- 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
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 name
aby 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ů
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í
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í
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
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
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í:
- Nainstalujte balíček s tímto modulem.
- Vytvořme konfigurační soubor pro tento modul.
- Vytvoříme symbolický odkaz na konfiguraci pro php-fpm.
- Vytvoříme symbolický odkaz na konfiguraci pro php cli.
V takových případech je vhodné provedení jako např $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
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ů -
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 manifestyfiles
- obsahuje souborytemplates
- obsahuje šablonylib
— 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
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ányfoo::<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 manifestuinit.pp
; - třída
nginx::service
popsané v manifestuservice.pp
; - definovat
nginx::server
popsané v manifestuserver.pp
; - definovat
nginx::server::location
popsané v manifestuserver/location.pp
.
šablony
Jistě sami víte, co jsou šablony, nebudu je zde podrobně popisovat. Ale pro každý případ to nechám
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
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
).
Vestavěné proměnné
Kromě faktů existuje také
- 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
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)[
Dodatek 2: Pokyny pro kódování
- Umístěte veškerou logiku do tříd a definic.
- Udržujte třídy a definice v modulech, nikoli v manifestech popisujících uzly.
- Použijte fakta.
- Nevytvářejte if na základě názvů hostitelů.
- 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é.
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