Puppet este un sistem de management al configurației. Este folosit pentru a aduce gazdele la starea dorită și pentru a menține această stare.
Lucrez cu Puppet de peste cinci ani. Acest text este în esență o compilație tradusă și reordonată a punctelor cheie din documentația oficială, care le va permite începătorilor să înțeleagă rapid esența lui Puppet.

Informații de bază
Sistemul de operare al lui Puppet este client-server, deși acceptă și operarea fără server cu funcționalitate limitată.
Se folosește un model de operare pull: implicit, o dată la jumătate de oră, clienții contactează serverul pentru o configurare și o aplică. Dacă ați lucrat cu Ansible, atunci ei folosesc un alt model push: administratorul inițiază procesul de aplicare a configurației, clienții înșiși nu vor aplica nimic.
În timpul comunicării în rețea, se utilizează criptarea TLS bidirecțională: serverul și clientul au propriile lor chei private și certificatele corespunzătoare. De obicei, serverul emite certificate pentru clienți, dar în principiu este posibil să se utilizeze un CA extern.
Introducere în manifeste
În terminologia păpușilor la serverul de păpuși conectați noduri (noduri). Se scrie configurația pentru noduri în manifeste într-un limbaj de programare special - Puppet DSL.
Puppet DSL este un limbaj declarativ. Descrie starea dorită a nodului sub formă de declarații de resurse individuale, de exemplu:
- Fișierul există și are conținut specific.
- Pachetul este instalat.
- Serviciul a început.
Resursele pot fi interconectate:
- Există dependențe, ele afectează ordinea în care sunt utilizate resursele.
De exemplu, „mai întâi instalați pachetul, apoi editați fișierul de configurare, apoi porniți serviciul”. - Există notificări - dacă o resursă s-a schimbat, aceasta trimite notificări resurselor abonate la ea.
De exemplu, dacă fișierul de configurare se modifică, puteți reporni automat serviciul.
În plus, Puppet DSL are funcții și variabile, precum și instrucțiuni condiționate și selectoare. Sunt acceptate și diverse mecanisme de șabloane - EPP și ERB.
Puppet este scris în Ruby, așa că multe dintre constructe și termeni sunt preluați de acolo. Ruby vă permite să extindeți Puppet - adăugați o logică complexă, noi tipuri de resurse, funcții.
În timp ce Puppet rulează, manifestele pentru fiecare nod specific de pe server sunt compilate într-un director. Directory este o listă de resurse și relațiile lor după calcularea valorii funcțiilor, variabilelor și extinderea instrucțiunilor condiționate.
Sintaxă și stil de cod
Iată secțiuni din documentația oficială care vă vor ajuta să înțelegeți sintaxa dacă exemplele furnizate nu sunt suficiente:
Iată un exemplu despre cum arată manifestul:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}Indentarea și rupturile de linie nu sunt o parte obligatorie a manifestului, dar există o parte recomandată . Rezumat:
- Indentări cu două spații, file nu sunt utilizate.
- Acoladele sunt separate printr-un spațiu;
- Virgule după fiecare parametru, inclusiv ultimul. Fiecare parametru se află pe o linie separată. Se face o excepție pentru cazul fără parametri și un parametru: puteți scrie pe o singură linie și fără virgulă (adică
resource { 'title': }иresource { 'title': param => value }). - Săgețile de pe parametri ar trebui să fie la același nivel.
- În fața lor sunt scrise săgeți de relație de resurse.
Locația fișierelor pe pappetserver
Pentru explicații suplimentare, voi introduce conceptul de „director rădăcină”. Directorul rădăcină este directorul care conține configurația Puppet pentru un anumit nod.
Directorul rădăcină variază în funcție de versiunea Puppet și de mediile utilizate. Mediile sunt seturi independente de configurații care sunt stocate în directoare separate. Folosit de obicei în combinație cu git, caz în care mediile sunt create din ramuri git. În consecință, fiecare nod este situat într-un mediu sau altul. Acesta poate fi configurat pe nodul în sine, sau în ENC, despre care voi vorbi în articolul următor.
- În cea de-a treia versiune („vechiul Puppet”) directorul de bază era
/etc/puppet. Utilizarea mediilor este opțională – de exemplu, nu le folosim cu vechea Marionetă. Dacă sunt folosite medii, acestea sunt de obicei stocate în/etc/puppet/environments, directorul rădăcină va fi directorul de mediu. Dacă mediile nu sunt folosite, directorul rădăcină va fi directorul de bază. - Începând cu cea de-a patra versiune („new Puppet”), utilizarea mediilor a devenit obligatorie, iar directorul de bază a fost mutat în
/etc/puppetlabs/code. În consecință, mediile sunt stocate/etc/puppetlabs/code/environments, directorul rădăcină este directorul de mediu.
Trebuie să existe un subdirector în directorul rădăcină manifests, care conține unul sau mai multe manifeste care descriu nodurile. În plus, ar trebui să existe un subdirector modules, care conține modulele. Vă voi spune ce module sunt puțin mai târziu. În plus, vechiul Puppet poate avea și un subdirector files, care conține diverse fișiere pe care le copiem în noduri. În noul Puppet, toate fișierele sunt plasate în module.
Fișierele manifest au extensia .pp.
Câteva exemple de luptă
Descrierea nodului și a resursei de pe acesta
Pe nod server1.testdomain trebuie creat un fișier /etc/issue cu continut Debian GNU/Linux n l. Fișierul trebuie să fie deținut de un utilizator și de un grup root, drepturile de acces trebuie să fie 644.
Scriem un 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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
}
}Relațiile dintre resursele de pe un nod
Pe nod server2.testdomain nginx trebuie să ruleze, lucrând cu o configurație pregătită anterior.
Să descompunăm problema:
- Pachetul trebuie instalat
nginx. - Este necesar ca fișierele de configurare să fie copiate de pe server.
- Serviciul trebuie să funcționeze
nginx. - Dacă configurația este actualizată, serviciul trebuie repornit.
Scriem un 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 получает уведомление,
# соответствующий сервис перезапускается.
}Pentru ca acest lucru să funcționeze, aveți nevoie de aproximativ următoarea locație a fișierului pe serverul marionete:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.confTipuri de resurse
O listă completă a tipurilor de resurse acceptate poate fi găsită aici , aici voi descrie cinci tipuri de bază, care în practica mea sunt suficiente pentru a rezolva majoritatea problemelor.
fişier
Gestionează fișierele, directoarele, linkurile simbolice, conținutul acestora și drepturile de acces.
Opțiuni:
- numele resursei — calea către fișier (opțional)
- cale — calea către fișier (dacă nu este specificată în nume)
- asigura - tip fișier:
absent- ștergeți un fișierpresent— trebuie să existe un fișier de orice tip (dacă nu există un fișier, va fi creat un fișier obișnuit)file- dosar normaldirectory- directorlink- legătură simbolică
- conţinut — conținutul fișierului (potrivit numai pentru fișierele obișnuite, nu poate fi utilizat împreună cu sursă sau ţintă)
- sursă — un link către calea de pe care doriți să copiați conținutul fișierului (nu poate fi folosit împreună cu conţinut sau ţintă). Poate fi specificat fie ca URI cu o schemă
puppet:(atunci vor fi folosite fișiere de pe serverul marionete), și cu schemahttp:(Sper că este clar ce se va întâmpla în acest caz), și chiar și cu diagramafile:sau ca o cale absolută fără o schemă (atunci va fi folosit fișierul din FS local de pe nod) - ţintă — unde ar trebui să indice legătura simbolică (nu poate fi utilizat împreună cu conţinut sau sursă)
- proprietar — utilizatorul care ar trebui să dețină fișierul
- grup — grupul căruia ar trebui să aparțină fișierul
- mod — permisiuni pentru fișiere (sub formă de șir)
- recurge - permite procesarea recursivă a directoarelor
- epurare - permite ștergerea fișierelor care nu sunt descrise în Puppet
- putere - permite ștergerea directoarelor care nu sunt descrise în Puppet
pachet
Instalează și elimină pachete. Capabil să gestioneze notificările - reinstalează pachetul dacă este specificat parametrul reinstall_on_refresh.
Opțiuni:
- numele resursei — numele pachetului (opțional)
- nume — numele pachetului (dacă nu este specificat în nume)
- furnizorul — manager de pachete de utilizat
- asigura — starea dorită a pachetului:
present,installed- orice versiune instalatălatest- cea mai recentă versiune instalatăabsent- șters (apt-get remove)purged— șters împreună cu fișierele de configurare (apt-get purge)held- versiunea pachetului este blocată (apt-mark hold)любая другая строка— este instalată versiunea specificată
- reinstall_on_refresh - dacă
true, apoi la primirea notificării pachetul va fi reinstalat. Util pentru distribuțiile bazate pe sursă, în care reconstruirea pachetelor poate fi necesară la modificarea parametrilor de compilare. Mod implicitfalse.
serviciu
Gestionează serviciile. Capabil să proceseze notificările - repornește serviciul.
Opțiuni:
- numele resursei — serviciu de gestionat (opțional)
- nume — serviciul care trebuie gestionat (dacă nu este specificat în nume)
- asigura — starea dorită a serviciului:
running- lansatstopped- oprit
- permite — controlează capacitatea de a porni serviciul:
true— rularea automată este activată (systemctl enable)mask- deghizat (systemctl mask)false— rularea automată este dezactivată (systemctl disable)
- reîncepe - comanda de repornire a serviciului
- Starea — comandă pentru a verifica starea serviciului
- hasrestart — indicați dacă initscript-ul serviciului acceptă repornirea. Dacă
falseiar parametrul este specificat reîncepe — se utilizează valoarea acestui parametru. Dacăfalseși parametru reîncepe nu este specificat - serviciul este oprit și începe să repornească (dar systemd folosește comandasystemctl restart). - hasstatus — indicați dacă initscript-ul serviciului acceptă comanda
status. dacăfalse, atunci se utilizează valoarea parametrului Starea. Mod implicittrue.
Exec
Rulează comenzi externe. Dacă nu specificați parametri creează, doar dacă, dacă nu sau revigorat, comanda va fi rulată de fiecare dată când Puppet este rulat. Capabil să proceseze notificări - rulează o comandă.
Opțiuni:
- numele resursei — comandă de executat (opțional)
- comandă — comanda de executat (dacă nu este specificată în nume)
- cale — căi în care să căutați fișierul executabil
- doar dacă — dacă comanda specificată în acest parametru este completată cu un cod returnat zero, comanda principală va fi executată
- dacă nu — dacă comanda specificată în acest parametru s-a completat cu un cod de returnare diferit de zero, comanda principală va fi executată
- creează — dacă fișierul specificat în acest parametru nu există, comanda principală va fi executată
- revigorat - dacă
true, atunci comanda va fi rulată numai atunci când acest exec primește notificare de la alte resurse - cwd — directorul din care să rulați comanda
- utilizator — utilizatorul de la care să ruleze comanda
- furnizorul - cum se rulează comanda:
- POSIX — un proces copil este pur și simplu creat, asigurați-vă că specificați cale
- coajă - comanda este lansată în shell
/bin/sh, este posibil să nu fie specificat cale, puteți utiliza globbing, țevi și alte caracteristici de înveliș. De obicei detectat automat dacă există caractere speciale (|,;,&&,||etc).
cron
Controlează cronjob-urile.
Opțiuni:
- numele resursei - doar un fel de identificator
- asigura — stare de lucru în coroană:
present- creați dacă nu existăabsent- ștergeți dacă există
- comandă - ce comandă să ruleze
- mediu inconjurator — în ce mediu să rulați comanda (lista de variabile de mediu și valorile acestora prin
=) - utilizator — de la ce utilizator să ruleze comanda
- minut, oră, zi de lucru, lună, lună zi — când să rulați cron. Dacă oricare dintre aceste atribute nu este specificat, valoarea sa în crontab va fi
*.
În Puppet 6.0 cron de parcă în puppetserver, deci nu există documentație pe site-ul general. Dar el în agent de păpuși, deci nu este nevoie să-l instalați separat. Puteți vedea documentația pentru acesta Sau .
Despre resurse în general
Cerințe pentru unicitatea resurselor
Cea mai frecventă greșeală pe care o întâlnim este Declarație duplicat. Această eroare apare atunci când în director apar două sau mai multe resurse de același tip, cu același nume.
Prin urmare, voi scrie din nou: manifestele pentru același nod nu trebuie să conțină resurse de același tip cu același titlu!
Uneori este nevoie să instalați pachete cu același nume, dar cu manageri de pachete diferiți. În acest caz, trebuie să utilizați parametrul namepentru a evita eroarea:
package { 'ruby-mysql':
ensure => installed,
name => 'mysql',
provider => 'gem',
}
package { 'python-mysql':
ensure => installed,
name => 'mysql',
provider => 'pip',
}Alte tipuri de resurse au opțiuni similare pentru a evita dublarea − name у serviciu, command у Exec, și așa mai departe.
Metaparametri
Fiecare tip de resursă are niște parametri speciali, indiferent de natura sa.
Lista completă de meta-parametri .
Lista finaliștilor:
- necesita — acest parametru indică de ce resurse depinde această resursă.
- înainte - Acest parametru specifică ce resurse depind de această resursă.
- subscrie — acest parametru specifică de la ce resurse această resursă primește notificări.
- notifica — Acest parametru specifică ce resurse primesc notificări de la această resursă.
Toți metaparametrii listați acceptă fie o singură legătură de resursă, fie o serie de legături între paranteze drepte.
Link-uri către resurse
Un link de resursă este pur și simplu o mențiune a resursei. Ele sunt utilizate în principal pentru a indica dependențe. Referirea la o resursă inexistentă va provoca o eroare de compilare.
Sintaxa legăturii este următoarea: tipul resursei cu literă mare (dacă numele tipului conține două puncte duble, atunci fiecare parte a numelui dintre două puncte este scrisă cu majuscule), apoi numele resursei între paranteze drepte (cazul numelui nu se schimba!). Nu ar trebui să existe spații; parantezele pătrate sunt scrise imediat după numele tipului.
Exemplu:
file { '/file1': ensure => present }
file { '/file2':
ensure => directory,
before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']Dependențe și notificări
După cum sa menționat mai devreme, dependențele simple dintre resurse sunt tranzitive. Apropo, aveți grijă când adăugați dependențe - puteți crea dependențe ciclice, ceea ce va provoca o eroare de compilare.
Spre deosebire de dependențe, notificările nu sunt tranzitive. Pentru notificări se aplică următoarele reguli:
- Dacă resursa primește o notificare, aceasta este actualizată. Acțiunile de actualizare depind de tipul de resursă − Exec rulează comanda, serviciu repornește serviciul, pachet reinstalează pachetul. Dacă resursa nu are o acțiune de actualizare definită, atunci nu se întâmplă nimic.
- În timpul unei rulări a lui Puppet, resursa este actualizată nu mai mult de o dată. Acest lucru este posibil deoarece notificările includ dependențe, iar graficul dependențelor nu conține cicluri.
- Dacă Puppet modifică starea unei resurse, resursa trimite notificări tuturor resurselor abonate la ea.
- Dacă o resursă este actualizată, aceasta trimite notificări tuturor resurselor abonate la ea.
Manipularea parametrilor nespecificați
De regulă, dacă un parametru de resursă nu are o valoare implicită și acest parametru nu este specificat în manifest, atunci Puppet nu va schimba această proprietate pentru resursa corespunzătoare de pe nod. De exemplu, dacă o resursă de tip fişier parametru nespecificat owner, atunci Puppet nu va schimba proprietarul fișierului corespunzător.
Introducere în clase, variabile și definiții
Să presupunem că avem mai multe noduri care au aceeași parte a configurației, dar există și diferențe - altfel am putea descrie totul într-un singur bloc node {}. Desigur, puteți copia pur și simplu părți identice ale configurației, dar în general aceasta este o soluție proastă - configurația crește, iar dacă modificați partea generală a configurației, va trebui să editați același lucru în multe locuri. În același timp, este ușor să faci o greșeală și, în general, principiul DRY (nu te repeta) a fost inventat cu un motiv.
Pentru a rezolva această problemă există un astfel de design ca clasă.
Clase
este un bloc numit de cod poppet. Sunt necesare clase pentru a reutiliza codul.
Mai întâi trebuie descrisă clasa. Descrierea în sine nu adaugă resurse nicăieri. Clasa este descrisă în manifeste:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}După aceasta, clasa poate fi folosită:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальшеUn exemplu din sarcina anterioară - să mutăm instalarea și configurarea nginx într-o clasă:
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
}variabile
Clasa din exemplul anterior nu este deloc flexibilă, deoarece aduce întotdeauna aceeași configurație nginx. Să facem calea către variabila de configurare, apoi această clasă poate fi folosită pentru a instala nginx cu orice configurație.
Poate fi realizat .
Atenție: variabilele din Puppet sunt imuabile!
În plus, o variabilă poate fi accesată numai după ce a fost declarată, altfel valoarea variabilei va fi undef.
Exemplu de lucru cu variabile:
# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"Marioneta are spații de nume, iar variabilele, în consecință, au zona de vizibilitate: O variabilă cu același nume poate fi definită în spații de nume diferite. Când se rezolvă valoarea unei variabile, variabila este căutată în spațiul de nume curent, apoi în spațiul de nume care îl încadrează și așa mai departe.
Exemple de spații de nume:
- global - variabilele din afara descrierii clasei sau nodului merg acolo;
- spațiu de nume nod în descrierea nodului;
- spațiu de nume de clasă în descrierea clasei.
Pentru a evita ambiguitatea atunci când accesați o variabilă, puteți specifica spațiul de nume în numele variabilei:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::varSă fim de acord că calea către configurația nginx se află în variabilă $nginx_conf_source. Apoi clasa va arăta astfel:
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
}Cu toate acestea, exemplul dat este rău, deoarece există unele „cunoștințe secrete” că undeva în interiorul clasei este folosită o variabilă cu așa sau cutare nume. Este mult mai corect să generalizăm aceste cunoștințe - clasele pot avea parametri.
Parametrii clasei sunt variabile în spațiul de nume ale clasei, sunt specificate în antetul clasei și pot fi folosite ca variabile obișnuite în corpul clasei. Valorile parametrilor sunt specificate atunci când se utilizează clasa în manifest.
Parametrul poate fi setat la o valoare implicită. Dacă un parametru nu are o valoare implicită și valoarea nu este setată atunci când este utilizată, va cauza o eroare de compilare.
Să parametrizăm clasa din exemplul de mai sus și să adăugăm doi parametri: primul, obligatoriu, este calea către configurare, iar al doilea, opțional, este numele pachetului cu nginx (în Debian, de exemplu, există pachete 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', # задаём параметры класса точно так же, как параметры для других ресурсов
}
}În Puppet, variabilele sunt tastate. Mânca . Tipurile de date sunt de obicei folosite pentru a valida valorile parametrilor transmise claselor și definițiilor. Dacă parametrul transmis nu se potrivește cu tipul specificat, va apărea o eroare de compilare.
Tipul este scris imediat înaintea numelui parametrului:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}Clase: include classname vs class{'classname':}
Fiecare clasă este o resursă de tip clasă. Ca și în cazul oricărui alt tip de resursă, nu pot exista două instanțe ale aceleiași clase pe același nod.
Dacă încercați să adăugați o clasă la același nod de două ori folosind class { 'classname':} (fără diferență, cu parametri diferiți sau identici), va apărea o eroare de compilare. Dar dacă utilizați o clasă în stilul resursei, puteți seta imediat în mod explicit toți parametrii acesteia în manifest.
Cu toate acestea, dacă utilizați include, atunci clasa poate fi adăugată de câte ori se dorește. Adevărul este că include este o funcție idempotent care verifică dacă o clasă a fost adăugată în director. Dacă clasa nu este în director, o adaugă, iar dacă există deja, nu face nimic. Dar în cazul utilizării include Nu puteți seta parametrii de clasă în timpul declarației de clasă - toți parametrii necesari trebuie setați într-o sursă de date externă - Hiera sau ENC. Despre ele vom vorbi în următorul articol.
Definește
După cum sa spus în blocul anterior, aceeași clasă nu poate fi prezentă pe un nod de mai multe ori. Cu toate acestea, în unele cazuri, trebuie să puteți utiliza același bloc de cod cu parametri diferiți pe același nod. Cu alte cuvinte, este nevoie de un tip de resursă propriu.
De exemplu, pentru a instala modulul PHP, facem următoarele în Avito:
- Instalați pachetul cu acest modul.
- Să creăm un fișier de configurare pentru acest modul.
- Creăm un link simbolic către configurația pentru php-fpm.
- Creăm un link simbolic către configurația pentru php cli.
În astfel de cazuri, un design precum (definit, tip definit, tip de resursă definit). Un Define este similar cu o clasă, dar există diferențe: în primul rând, fiecare Define este un tip de resursă, nu o resursă; în al doilea rând, fiecare definiție are un parametru implicit $title, unde se numește resursa atunci când este declarată. La fel ca și în cazul claselor, trebuie mai întâi descrisă o definiție, după care poate fi folosită.
Un exemplu simplificat cu un modul pentru 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' }
}Cea mai ușoară modalitate de a detecta eroarea de declarare duplicat este în Define. Acest lucru se întâmplă dacă o definiție are o resursă cu un nume constant și există două sau mai multe instanțe ale acestei definiții pe un nod.
Este ușor să te protejezi de asta: toate resursele din definiție trebuie să aibă un nume în funcție de $title. O alternativă este adăugarea idempotentă a resurselor în cel mai simplu caz, este suficient să mutați resursele comune tuturor instanțelor definiției într-o clasă separată și să includeți această clasă în definiție - funcție; include idempotent.
Există și alte modalități de a obține idempotenta atunci când adăugați resurse, și anume utilizarea funcțiilor defined и ensure_resources, dar vă voi povesti despre asta în episodul următor.
Dependențe și notificări pentru clase și definiții
Clasele și definițiile adaugă următoarele reguli pentru gestionarea dependențelor și notificărilor:
- dependența de o clasă/define adaugă dependențe de toate resursele clasei/define;
- o dependență de clasă/definire adaugă dependențe la toate resursele de clasă/definire;
- notificarea class/define notifică toate resursele clasei/define;
- abonamentul class/define se abonează la toate resursele clasei/define.
Declarații condiționale și selectoare
if
Totul este simplu aici:
if ВЫРАЖЕНИЕ1 {
...
} elsif ВЫРАЖЕНИЕ2 {
...
} else {
...
}dacă nu
dacă nu este un dacă în sens invers: blocul de cod va fi executat dacă expresia este falsă.
unless ВЫРАЖЕНИЕ {
...
}caz
Nici aici nu este nimic complicat. Puteți utiliza valori obișnuite (șiruri, numere etc.), expresii regulate și tipuri de date ca valori.
case ВЫРАЖЕНИЕ {
ЗНАЧЕНИЕ1: { ... }
ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
default: { ... }
}Selectoare
Un selector este un construct de limbaj similar cu case, dar în loc să execute un bloc de cod, returnează o valoare.
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }module
Când configurația este mică, poate fi păstrată cu ușurință într-un singur manifest. Dar cu cât descriem mai multe configurații, cu atât există mai multe clase și noduri în manifest, acesta crește și devine incomod să lucrezi.
În plus, există problema reutilizării codului - când tot codul este într-un singur manifest, este dificil să partajați acest cod cu alții. Pentru a rezolva aceste două probleme, Puppet are o entitate numită module.
module - acestea sunt seturi de clase, definiții și alte entități Puppet plasate într-un director separat. Cu alte cuvinte, un modul este o piesă independentă a logicii Puppet. De exemplu, poate exista un modul pentru lucrul cu nginx și va conține ceea ce și numai ceea ce este necesar pentru a lucra cu nginx, sau poate exista un modul pentru lucrul cu PHP și așa mai departe.
Modulele sunt versionate și sunt acceptate și dependențele modulelor unul față de celălalt. Există un depozit deschis de module - .
Pe serverul puppet, modulele sunt localizate în subdirectorul modules al directorului rădăcină. În interiorul fiecărui modul există o schemă standard de directoare - manifeste, fișiere, șabloane, lib și așa mai departe.
Structura fișierului într-un modul
Rădăcina modulului poate conține următoarele directoare cu nume descriptive:
manifests- contine manifestefiles- contine fisieretemplates- conține șabloanelib— conține cod Ruby
Aceasta nu este o listă completă de directoare și fișiere, dar este suficientă pentru acest articol pentru moment.
Numele resurselor și numele fișierelor din modul
Resursele (clase, definiții) dintr-un modul nu pot fi numite cum doriți. În plus, există o corespondență directă între numele unei resurse și numele fișierului în care Puppet va căuta o descriere a acelei resurse. Dacă încălcați regulile de denumire, Puppet pur și simplu nu va găsi descrierea resursei și veți primi o eroare de compilare.
Regulile sunt simple:
- Toate resursele dintr-un modul trebuie să fie în spațiul de nume al modulului. Dacă modulul este apelat
foo, atunci toate resursele din acesta ar trebui să fie numitefoo::<anything>, sau doarfoo. - Resursa cu numele modulului trebuie să fie în fișier
init.pp. - Pentru alte resurse, schema de denumire a fișierelor este următoarea:
- prefixul cu numele modulului este eliminat
- toate punctele duble, dacă există, sunt înlocuite cu bare oblice
- este adăugată extensia
.pp
Voi demonstra cu un exemplu. Să presupunem că scriu un modul nginx. Acesta conține următoarele resurse:
- clasă
nginxdescrise în manifestinit.pp; - clasă
nginx::servicedescrise în manifestservice.pp; - defini
nginx::serverdescrise în manifestserver.pp; - defini
nginx::server::locationdescrise în manifestserver/location.pp.
Template-uri
Cu siguranță știți ce sunt șabloanele, nu le voi descrie în detaliu aici. Dar o las pentru orice eventualitate .
Cum se utilizează șabloanele: Semnificația unui șablon poate fi extins folosind o funcție template, căruia i se transmite calea către șablon. Pentru resurse de tip fişier utilizat împreună cu parametrul content. De exemplu, așa:
file { '/tmp/example': content => template('modulename/templatename.erb')Vizualizați calea <modulename>/<filename> implică fișier <rootdir>/modules/<modulename>/templates/<filename>.
În plus, există o funcție inline_template — primește textul șablonului ca intrare, nu numele fișierului.
În cadrul șabloanelor, puteți utiliza toate variabilele Puppet din domeniul curent.
Puppet acceptă șabloane în format ERB și EPP:
Pe scurt despre ERB
Structuri de control:
<%= ВЫРАЖЕНИЕ %>— introduceți valoarea expresiei<% ВЫРАЖЕНИЕ %>— calculați valoarea unei expresii (fără a o introduce). Instrucțiunile condiționate (dacă) și buclele (fiecare) merg de obicei aici.<%# КОММЕНТАРИЙ %>
Expresiile în ERB sunt scrise în Ruby (ERB este de fapt Embedded Ruby).
Pentru a accesa variabile din manifest, trebuie să adăugați @ la numele variabilei. Pentru a elimina o întrerupere de linie care apare după un construct de control, trebuie să utilizați o etichetă de închidere -%>.
Exemplu de utilizare a șablonului
Să presupunem că scriu un modul pentru a controla ZooKeeper. Clasa responsabilă pentru crearea configurației arată cam așa:
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'),
}
}Și șablonul corespunzător zoo.cfg.erb - Asa de:
<% 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 -%>Fapte și variabile încorporate
Adesea, o anumită parte a configurației depinde de ceea ce se întâmplă în prezent pe nod. De exemplu, în funcție de versiune Debian Merită, trebuie să instalați o anumită versiune a unui pachet. Puteți gestiona toate acestea manual, rescriind manifestele ori de câte ori nodurile se modifică. Dar aceasta nu este o abordare serioasă; automatizarea este mult mai bună.
Pentru a obține informații despre noduri, Puppet are un mecanism numit fapte. fapte - acestea sunt informații despre nod, disponibile în manifeste sub formă de variabile obișnuite în spațiul de nume global. De exemplu, numele gazdei, versiunea sistemului de operare, arhitectura procesorului, lista de utilizatori, lista interfețelor de rețea și adresele acestora și multe, multe altele. Faptele sunt disponibile în manifeste și șabloane ca variabile obișnuite.
Un exemplu de lucru cu fapte:
notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в логDin punct de vedere formal, un fapt are un nume (șir) și o valoare (sunt disponibile diferite tipuri: șiruri, matrice, dicționare). Mânca . Poți să-l scrii și pe al tău. Sunt descriși colectorii de fapte , sau cum . Faptele pot fi prezentate și sub formă pe noduri.
În timpul funcționării, agentul marionetă copiază mai întâi toți colectorii de fapte disponibili de pe serverul pappet în nod, după care îi lansează și trimite datele colectate către server; După aceasta, serverul începe compilarea catalogului.
Fapte sub formă de fișiere executabile
Astfel de fapte sunt plasate în module din director facts.d. Desigur, fișierele trebuie să fie executabile. Când sunt executate, acestea trebuie să scoată informații în ieșirea standard fie în format YAML, fie în format cheie=valoare.
Nu uitați că faptele se aplică tuturor nodurilor care sunt controlate de serverul poppet pe care este implementat modulul dumneavoastră. Prin urmare, în script, aveți grijă să verificați dacă sistemul are toate programele și fișierele necesare pentru ca fapta dvs. să funcționeze.
#!/bin/sh
echo "testfact=success"#!/bin/sh
echo '{"testyamlfact":"success"}'fapte Ruby
Astfel de fapte sunt plasate în module din director 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
endFapte text
Astfel de fapte sunt plasate pe nodurile din director /etc/facter/facts.d în marionetă veche sau /etc/puppetlabs/facts.d în noua Marionetă.
examplefact=examplevalue---
examplefact2: examplevalue2
anotherfact: anothervalueAjungerea la fapte
Există două moduri de a aborda faptele:
- prin dicționar
$facts:$facts['fqdn']; - folosind numele faptului ca nume variabilă:
$fqdn.
Cel mai bine este să folosiți un dicționar $facts, sau chiar mai bine, indicați spațiul de nume global ($::facts).
Variabile încorporate
Pe lângă fapte, există și , disponibil în spațiul de nume global.
- fapte de încredere — variabile care sunt preluate din certificatul clientului (deoarece certificatul este de obicei emis pe un server poppet, agentul nu poate pur și simplu să-și ia și să-și schimbe certificatul, deci variabilele sunt „de încredere”): numele certificatului, numele certificatului gazdă și domeniu, extensii de la certificat.
- faptele serverului —variabile legate de informații despre server—versiune, nume, adresa IP a serverului, mediu.
- faptele agentului — variabile adăugate direct de agentul marionetă, și nu de către factor — numele certificatului, versiunea agentului, versiunea marionetă.
- variabile principale - Variabile Pappetmaster (sic!). Este cam la fel ca în faptele serverului, plus valorile parametrilor de configurare sunt disponibile.
- variabilele compilatorului — variabile de compilator care diferă în fiecare domeniu: numele modulului curent și numele modulului în care a fost accesat obiectul curent. Ele pot fi folosite, de exemplu, pentru a verifica dacă cursurile dumneavoastră private nu sunt folosite direct din alte module.
Anexa 1: cum să rulați și să depanați toate acestea?
Articolul conținea multe exemple de cod de marionetă, dar nu ne spunea deloc cum să rulăm acest cod. Ei bine, mă corectez.
Un agent este suficient pentru a rula Puppet, dar în majoritatea cazurilor veți avea nevoie și de un server.
agent
Cel puțin de la versiunea 5, pachetele puppet-agent din conține toate dependențele (ruby și gemele corespunzătoare), deci nu există dificultăți la instalare (vorbesc despre Debiandistribuții bazate pe - (nu folosim distribuții bazate pe RPM).
În cel mai simplu caz, pentru a utiliza configurația marionetă, este suficient să lansați agentul în modul serverless: cu condiția ca codul păpușii să fie copiat în nod, lansați 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 secondsDesigur, este mai bine să configurați serverul și să rulați agenți pe noduri în modul daemon - apoi o dată la jumătate de oră vor aplica configurația descărcată de pe server.
Puteți imita modelul push de lucru - mergeți la nodul care vă interesează și începeți sudo puppet agent -t. Cheie -t (--test) include de fapt mai multe opțiuni care pot fi activate individual. Aceste opțiuni includ următoarele:
- nu rulați în modul daemon (în mod implicit, agentul pornește în modul daemon);
- se închide după aplicarea catalogului (în mod implicit, agentul va continua să lucreze și să aplice configurația o dată la jumătate de oră);
- scrieți un jurnal de lucru detaliat;
- arată modificările în fișiere.
Agentul are un mod de operare fără modificări - îl puteți folosi atunci când nu sunteți sigur că ați scris configurația corectă și doriți să verificați ce anume va schimba agentul în timpul funcționării. Acest mod este activat de parametru --noop pe linia de comandă: sudo puppet agent -t --noop.
În plus, puteți activa jurnalul de depanare al lucrării - în el, puppet scrie despre toate acțiunile pe care le efectuează: despre resursa pe care o procesează în prezent, despre parametrii acestei resurse, despre ce programe lansează. Desigur, acesta este un parametru --debug.
Server
Nu voi lua în considerare configurarea completă a pappetserver și implementarea codului acestuia în acest articol, voi spune doar că din cutie există o versiune complet funcțională a serverului care nu necesită o configurare suplimentară pentru a funcționa cu un număr mic de; noduri (să zicem, până la o sută). Un număr mai mare de noduri va necesita reglare - în mod implicit, puppetserver lansează nu mai mult de patru lucrători, pentru o performanță mai mare trebuie să creșteți numărul lor și nu uitați să creșteți limitele de memorie, altfel serverul va colecta gunoi de cele mai multe ori.
Implementarea codului - dacă aveți nevoie de el rapid și ușor, atunci uitați-vă (la r10k)[], pentru instalații mici ar trebui să fie suficient.
Anexa 2: Ghid de codificare
- Plasează toată logica în clase și definiții.
- Păstrați clasele și definițiile în module, nu în manifeste care descriu nodurile.
- Folosiți faptele.
- Nu faceți if-uri pe baza numelor de gazdă.
- Simțiți-vă liber să adăugați parametri pentru clase și definiții - aceasta este mai bună decât logica implicită ascunsă în corpul clasei/definiției.
Voi explica de ce vă recomand să faceți acest lucru în articolul următor.
Concluzie
Să încheiem cu introducerea. În articolul următor vă voi povesti despre Hiera, ENC și PuppetDB.
Numai utilizatorii înregistrați pot participa la sondaj. , Vă rog.
De fapt, există mult mai mult material - pot scrie articole pe următoarele subiecte, pot vota despre ce ați fi interesat să citiți:
- 59,1%Construcții avansate de marionete - câteva lucruri de nivel următor: bucle, cartografiere și alte expresii lambda, colectori de resurse, resurse exportate și comunicare între gazde prin Puppet, etichete, furnizori, tipuri de date abstracte.13
- 31,8%„Sunt administratorul mamei mele” sau modul în care noi, în Avito, ne-am împrietenit cu mai multe servere poppet de diferite versiuni și, în principiu, cu partea despre administrarea serverului poppet.7
- 81,8%Cum scriem codul păpușilor: instrumentare, documentare, testare, CI/CD.18
Au votat 22 de utilizatori. 9 utilizatori s-au abținut.
Sursa: www.habr.com
