Uvod u lutku

Puppet je sistem za upravljanje konfiguracijom. Koristi se za dovođenje domaćina u željeno stanje i održavanje ovog stanja.

Sa Puppet-om radim više od pet godina. Ovaj tekst je u suštini prevedena i preuređena kompilacija ključnih tačaka iz službene dokumentacije, koja će početnicima omogućiti da brzo shvate suštinu Puppet-a.

Uvod u lutku

Osnovne informacije

Operativni sistem Puppet-a je klijent-server, iako takođe podržava rad bez servera sa ograničenom funkcionalnošću.

Koristi se model rada povlačenja: po defaultu, jednom svakih pola sata, klijenti kontaktiraju server radi konfiguracije i primjenjuju je. Ako ste radili sa Ansibleom, onda oni koriste drugačiji push model: administrator pokreće proces primjene konfiguracije, sami klijenti neće primijeniti ništa.

Tokom mrežne komunikacije koristi se dvosmjerna TLS enkripcija: server i klijent imaju svoje privatne ključeve i odgovarajuće certifikate. Obično server izdaje certifikate za klijente, ali u principu je moguće koristiti eksterni CA.

Uvod u manifeste

U lutkarskoj terminologiji na server marioneta povežite se čvorovi (čvorovi). Napisana je konfiguracija za čvorove u manifestima u posebnom programskom jeziku - Puppet DSL.

Puppet DSL je deklarativni jezik. Opisuje željeno stanje čvora u obliku deklaracija pojedinačnih resursa, na primjer:

  • Datoteka postoji i ima određeni sadržaj.
  • Paket je instaliran.
  • Usluga je počela.

Resursi se mogu međusobno povezati:

  • Postoje zavisnosti, one utiču na redosled kojim se resursi koriste.
    Na primjer, "prvo instalirajte paket, zatim uredite konfiguracijsku datoteku, a zatim pokrenite uslugu."
  • Postoje obavještenja - ako se resurs promijenio, on šalje obavještenja resursima koji su pretplaćeni na njega.
    Na primjer, ako se konfiguracijski fajl promijeni, možete automatski ponovo pokrenuti uslugu.

Dodatno, Puppet DSL ima funkcije i varijable, kao i uslovne izjave i selektore. Podržani su i različiti šablonski mehanizmi - EPP i ERB.

Puppet je napisan u Ruby-u, tako da su mnogi konstrukti i termini preuzeti odatle. Ruby vam omogućava da proširite Puppet - dodate složenu logiku, nove vrste resursa, funkcije.

Dok je Puppet pokrenut, manifesti za svaki određeni čvor na serveru se kompajliraju u direktorij. Directory je lista resursa i njihovih odnosa nakon izračunavanja vrijednosti funkcija, varijabli i proširenja uvjetnih iskaza.

Sintaksa i stil koda

Evo odjeljaka službene dokumentacije koji će vam pomoći da razumijete sintaksu ako navedeni primjeri nisu dovoljni:

Evo primjera kako manifest izgleda:

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

Uvlačenje i prijelomi reda nisu obavezni dio manifesta, ali postoji preporuka stil vodič. Sažetak:

  • Uvlačenja sa dva razmaka, tabulatori se ne koriste.
  • Vitičaste zagrade su odvojene razmakom; dvotočke nisu odvojene razmakom.
  • Zarezi iza svakog parametra, uključujući i posljednji. Svaki parametar je u posebnom redu. Izuzetak je napravljen za slučaj bez parametara i jednog parametra: možete pisati u jednom redu i bez zareza (tj. resource { 'title': } и resource { 'title': param => value }).
  • Strelice na parametrima treba da budu na istom nivou.
  • Ispred njih su ispisane strelice odnosa resursa.

Lokacija datoteka na pappetserveru

Za dalje objašnjenje, uvest ću koncept „korijenskog direktorija“. Korijenski direktorij je direktorij koji sadrži konfiguraciju lutke za određeni čvor.

Korijenski direktorij varira ovisno o verziji Puppet-a i korištenim okruženjima. Okruženja su nezavisni skupovi konfiguracija koji su pohranjeni u odvojenim direktorijima. Obično se koristi u kombinaciji sa git-om, u kom slučaju se okruženja kreiraju iz git grana. Prema tome, svaki čvor se nalazi u jednom ili drugom okruženju. Ovo se može konfigurirati na samom čvoru ili u ENC-u, o čemu ću govoriti u sljedećem članku.

  • U trećoj verziji ("stara lutka") osnovni direktorij je bio /etc/puppet. Upotreba okruženja je opciona - na primjer, ne koristimo ih sa starim Puppetom. Ako se koriste okruženja, obično se pohranjuju u /etc/puppet/environments, korijenski direktorij će biti direktorij okruženja. Ako se okruženja ne koriste, korijenski direktorij će biti osnovni direktorij.
  • Počevši od četvrte verzije (“nova lutka”), upotreba okruženja postala je obavezna, a osnovni direktorij je premješten u /etc/puppetlabs/code. Shodno tome, okruženja se pohranjuju u /etc/puppetlabs/code/environments, korijenski direktorij je direktorij okruženja.

Mora postojati poddirektorij u korijenskom direktoriju manifests, koji sadrži jedan ili više manifesta koji opisuju čvorove. Osim toga, trebao bi postojati poddirektorij modules, koji sadrži module. Reći ću vam koji su moduli malo kasnije. Osim toga, stari Puppet može imati i poddirektorij files, koji sadrži razne datoteke koje kopiramo u čvorove. U novom Puppet-u, svi fajlovi su smešteni u module.

Datoteke manifesta imaju ekstenziju .pp.

Par borbenih primjera

Opis čvora i resursa na njemu

Na čvoru server1.testdomain fajl mora biti kreiran /etc/issue sa sadržajem Debian GNU/Linux n l. Datoteka mora biti vlasništvo korisnika i grupe root, prava pristupa moraju biti 644.

Pišemo 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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Odnosi između resursa na čvoru

Na čvoru server2.testdomain nginx mora biti pokrenut, raditi sa prethodno pripremljenom konfiguracijom.

Hajde da dekomponujemo problem:

  • Paket je potrebno instalirati nginx.
  • Neophodno je da se konfiguracioni fajlovi kopiraju sa servera.
  • Usluga mora biti pokrenuta nginx.
  • Ako se konfiguracija ažurira, servis se mora ponovo pokrenuti.

Pišemo 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 получает уведомление,
  # соответствующий сервис перезапускается.
}

Da bi ovo funkcioniralo, potrebna vam je otprilike sljedeća lokacija datoteke na serveru lutke:

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

Vrste resursa

Kompletnu listu podržanih tipova resursa možete pronaći ovdje u dokumentaciji, ovdje ću opisati pet osnovnih tipova, koji su u mojoj praksi dovoljni za rješavanje većine problema.

fajl

Upravlja fajlovima, direktorijumima, simboličkim vezama, njihovim sadržajem i pravima pristupa.

Parametri:

  • naziv resursa — putanja do datoteke (opcionalno)
  • put — putanja do datoteke (ako nije navedena u nazivu)
  • obezbijediti - tip fajla:
    • absent - izbrisati fajl
    • present — mora postojati fajl bilo koje vrste (ako ne postoji, kreiraće se običan fajl)
    • file - običan fajl
    • directory - imenik
    • link - simbolična veza
  • sadržaj — sadržaj datoteke (prikladan samo za obične datoteke, ne može se koristiti zajedno sa izvor ili cilj)
  • izvor — vezu do putanje sa koje želite da kopirate sadržaj datoteke (ne može se koristiti zajedno sa sadržaj ili cilj). Može se specificirati kao URI sa šemom puppet: (tada će se koristiti fajlovi sa servera lutke) i sa šemom http: (Nadam se da je jasno šta će se dogoditi u ovom slučaju), pa čak i sa dijagramom file: ili kao apsolutna staza bez šeme (tada će se koristiti datoteka iz lokalnog FS-a na čvoru)
  • cilj — gdje simbolska veza treba da pokazuje (ne može se koristiti zajedno sa sadržaj ili izvor)
  • vlasnik — korisnik koji bi trebao posjedovati fajl
  • grupa — grupa kojoj datoteka treba da pripada
  • način — dozvole datoteke (kao niz)
  • recidiv - omogućava rekurzivnu obradu direktorija
  • cistiti - omogućava brisanje datoteka koje nisu opisane u Puppet-u
  • sila - omogućava brisanje direktorija koji nisu opisani u Puppet-u

paket

Instalira i uklanja pakete. Može da obrađuje obavijesti - ponovo instalira paket ako je parametar specificiran reinstall_on_refresh.

Parametri:

  • naziv resursa — naziv paketa (opciono)
  • ime — naziv paketa (ako nije navedeno u nazivu)
  • usluga — menadžer paketa za korištenje
  • obezbijediti — željeno stanje pakovanja:
    • present, installed - bilo koja instalirana verzija
    • latest - instalirana najnovija verzija
    • absent - obrisan (apt-get remove)
    • purged — obrisan zajedno sa konfiguracionim fajlovima (apt-get purge)
    • held - verzija paketa je zaključana (apt-mark hold)
    • любая другая строка — navedena verzija je instalirana
  • reinstall_on_refresh - ako true, tada će po prijemu obavijesti paket biti ponovo instaliran. Korisno za distribucije zasnovane na izvoru, gdje može biti potrebno obnavljanje paketa prilikom promjene parametara izgradnje. Default false.

usluga

Upravlja uslugama. Mogućnost obrade obavijesti - ponovo pokreće uslugu.

Parametri:

  • naziv resursa — usluga kojom se upravlja (opciono)
  • ime — usluga kojom se treba upravljati (ako nije navedeno u nazivu)
  • obezbijediti — željeno stanje usluge:
    • running - pokrenut
    • stopped - stao
  • omogućiti — kontrolira mogućnost pokretanja usluge:
    • true — automatsko pokretanje je omogućeno (systemctl enable)
    • mask - prerušen (systemctl mask)
    • false — automatsko pokretanje je onemogućeno (systemctl disable)
  • Restart - naredba za ponovno pokretanje usluge
  • status — komanda za provjeru statusa servisa
  • hasrestart — naznačite da li initscript servisa podržava ponovno pokretanje. Ako false i parametar je specificiran Restart — koristi se vrijednost ovog parametra. Ako false i parametar Restart nije navedeno - usluga je zaustavljena i pokrenuta za ponovno pokretanje (ali systemd koristi naredbu systemctl restart).
  • hasstatus — naznačite da li initscript servisa podržava naredbu status. Ako false, tada se koristi vrijednost parametra status. Default true.

exec

Pokreće eksterne komande. Ako ne navedete parametre stvara, samo ako, osim ako nije ili refreshonly, naredba će se pokrenuti svaki put kada se pokrene Puppet. Može obraditi obavijesti - pokreće naredbu.

Parametri:

  • naziv resursa — naredba koju treba izvršiti (opciono)
  • naredba — naredba koju treba izvršiti (ako nije navedena u nazivu)
  • put — putanje u kojima se traži izvršna datoteka
  • samo ako — ako je naredba navedena u ovom parametru dovršena s nultim povratnim kodom, glavna komanda će se izvršiti
  • osim ako nije — ako je naredba navedena u ovom parametru dovršena povratnim kodom koji nije nula, glavna naredba će se izvršiti
  • stvara — ako datoteka navedena u ovom parametru ne postoji, glavna komanda će se izvršiti
  • refreshonly - ako true, tada će se naredba pokrenuti samo kada ovaj exec primi obavijest od drugih resursa
  • cwd — direktorij iz kojeg se izvodi naredba
  • korisnik — korisnik od koga se izvodi naredba
  • usluga - kako pokrenuti naredbu:
    • positix — podređeni proces se jednostavno kreira, obavezno navedite put
    • školjka - komanda se pokreće u ljusci /bin/sh, možda nije navedeno put, možete koristiti globing, cijevi i druge karakteristike ljuske. Obično se automatski detektuje ako postoje posebni znakovi (|, ;, &&, || itd).

Cron

Kontroliše cronjobs.

Parametri:

  • naziv resursa - samo neka vrsta identifikatora
  • obezbijediti — status krunskog posla:
    • present - kreirati ako ne postoji
    • absent - obrisati ako postoji
  • naredba - koju naredbu pokrenuti
  • ambijent — u kojem okruženju pokrenuti naredbu (lista varijabli okruženja i njihovih vrijednosti preko =)
  • korisnik — od kojeg korisnika pokrenuti naredbu
  • minuta, sat, radni dan, mjesec, mjesec dana — kada pokrenuti cron. Ako bilo koji od ovih atributa nije specificiran, njegova vrijednost u crontab-u će biti *.

U Puppet 6.0 Cron kao da izvađen iz kutije u puppetserver, tako da nema dokumentacije na generalnom sajtu. Ali on je u kutiji u puppet-agent-u, tako da nema potrebe da ga zasebno instalirate. Možete pogledati dokumentaciju za to u dokumentaciji za petu verziju Puppet-a, ili na GitHubu.

O resursima općenito

Zahtjevi za jedinstvenost resursa

Najčešća greška sa kojom se susrećemo je Duplicirana deklaracija. Ova greška se javlja kada se dva ili više resursa istog tipa sa istim imenom pojavljuju u direktoriju.

Stoga ću ponovo napisati: manifesti za isti čvor ne bi trebali sadržavati resurse istog tipa sa istim naslovom!

Ponekad postoji potreba da se instaliraju paketi sa istim imenom, ali sa različitim menadžerima paketa. U ovom slučaju morate koristiti parametar nameda biste izbjegli grešku:

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

Druge vrste resursa imaju slične opcije koje pomažu u izbjegavanju dupliciranja − name у usluga, command у exec, i tako dalje.

Metaparametri

Svaki tip resursa ima neke posebne parametre, bez obzira na njegovu prirodu.

Potpuna lista meta parametara u dokumentaciji lutke.

Kratka lista:

  • zahtijevati — ovaj parametar pokazuje o kojim resursima ovisi ovaj resurs.
  • prije - Ovaj parametar određuje koji resursi zavise od ovog resursa.
  • pretplatiti — ovaj parametar određuje s kojih izvora ovaj resurs prima obavijesti.
  • obavjestiti — Ovaj parametar specificira koji resursi primaju obavještenja od ovog resursa.

Svi navedeni metaparametri prihvataju ili jednu vezu do resursa ili niz veza u uglastim zagradama.

Linkovi na resurse

Veza resursa je jednostavno spominjanje resursa. Uglavnom se koriste za označavanje zavisnosti. Upućivanje na nepostojeći resurs će uzrokovati grešku kompilacije.

Sintaksa veze je sljedeća: tip resursa sa velikim slovom (ako ime tipa sadrži dvostruke dvotočke, onda je svaki dio imena između dvotočka napisan velikim), zatim naziv resursa u uglastim zagradama (velika i mala slova imena ne menja!). Ne bi trebalo biti razmaka, uglaste zagrade se pišu odmah iza naziva tipa.

Primjer:

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

Zavisnosti i obavještenja

Dokumentacija ovdje.

Kao što je ranije rečeno, jednostavne zavisnosti između resursa su tranzitivne. Usput, budite oprezni kada dodajete ovisnosti - možete kreirati cikličke ovisnosti, što će uzrokovati grešku u kompilaciji.

Za razliku od zavisnosti, obavještenja nisu tranzitivna. Za obavještenja važe sljedeća pravila:

  • Ako resurs primi obavijest, ažurira se. Akcije ažuriranja zavise od tipa resursa − exec izvodi naredbu, usluga ponovo pokreće uslugu, paket ponovo instalira paket. Ako resurs nema definiranu akciju ažuriranja, onda se ništa ne događa.
  • Tokom jednog pokretanja Puppet-a, resurs se ne ažurira više od jednom. Ovo je moguće jer obavijesti uključuju ovisnosti, a graf ovisnosti ne sadrži cikluse.
  • Ako Puppet promijeni stanje resursa, resurs šalje obavještenja svim resursima koji su pretplaćeni na njega.
  • Ako se resurs ažurira, on šalje obavještenja svim resursima koji su pretplaćeni na njega.

Rukovanje nespecificiranim parametrima

Po pravilu, ako neki parametar resursa nema zadanu vrijednost i ovaj parametar nije naveden u manifestu, tada Puppet neće promijeniti ovo svojstvo za odgovarajući resurs na čvoru. Na primjer, ako je resurs tipa fajl parametar nije specificiran owner, tada Puppet neće promijeniti vlasnika odgovarajuće datoteke.

Uvod u klase, varijable i definicije

Pretpostavimo da imamo nekoliko čvorova koji imaju isti dio konfiguracije, ali postoje i razlike - inače bismo to sve mogli opisati u jednom bloku node {}. Naravno, možete jednostavno kopirati identične dijelove konfiguracije, ali općenito je ovo loše rješenje - konfiguracija raste, a ako promijenite opći dio konfiguracije, morat ćete uređivati ​​istu stvar na mnogo mjesta. U isto vrijeme, lako je pogriješiti, a generalno, princip DRY (ne ponavljaj se) izmišljen je s razlogom.

Za rješavanje ovog problema postoji takav dizajn kao razred.

Klase

Класс je imenovani blok popet koda. Klase su potrebne za ponovnu upotrebu koda.

Prvo treba opisati klasu. Sam opis nigdje ne dodaje nikakve resurse. Klasa je opisana u manifestima:

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

Nakon ovoga klasa se može koristiti:

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

Primjer iz prethodnog zadatka - premjestimo instalaciju i konfiguraciju nginx-a u klasu:

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
}

Varijable

Klasa iz prethodnog primjera uopće nije fleksibilna jer uvijek donosi istu nginx konfiguraciju. Napravimo putanju do konfiguracijske varijable, onda se ova klasa može koristiti za instaliranje nginxa sa bilo kojom konfiguracijom.

To se može učiniti koristeći varijable.

Pažnja: varijable u Puppet-u su nepromjenjive!

Osim toga, varijabli se može pristupiti tek nakon što je deklarirana, inače će vrijednost varijable biti undef.

Primjer rada sa varijablama:

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

Lutka ima imenski prostori, a varijable, shodno tome, imaju područje vidljivosti: Varijabla s istim imenom može se definirati u različitim imenskim prostorima. Prilikom rješavanja vrijednosti varijable, varijabla se pretražuje u trenutnom imenskom prostoru, zatim u zatvorenom imenskom prostoru i tako dalje.

Primjeri imenskog prostora:

  • globalno - varijable izvan opisa klase ili čvora idu tamo;
  • imenski prostor čvora u opisu čvora;
  • imenski prostor klase u opisu klase.

Da biste izbjegli dvosmislenost prilikom pristupa varijabli, možete specificirati imenski prostor u imenu varijable:

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

Složimo se da put do nginx konfiguracije leži u varijabli $nginx_conf_source. Tada će klasa izgledati ovako:

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
}

Međutim, navedeni primjer je loš jer postoji neka „tajna saznanja“ da se negdje unutar klase koristi varijabla s takvim i takvim imenom. Mnogo je ispravnije ovo znanje učiniti opštim – klase mogu imati parametre.

Parametri klase su varijable u imenskom prostoru klase, navedene su u zaglavlju klase i mogu se koristiti kao obične varijable u tijelu klase. Vrijednosti parametara su specificirane kada se klasa koristi u manifestu.

Parametar se može postaviti na zadanu vrijednost. Ako parametar nema zadanu vrijednost i vrijednost nije postavljena kada se koristi, to će uzrokovati grešku kompilacije.

Parametrizujmo klasu iz gornjeg primjera i dodajmo dva parametra: prvi, obavezan, je put do konfiguracije, a drugi, opcijski, je naziv paketa s nginxom (u Debianu, na primjer, postoje paketi 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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

U Puppet-u se upisuju varijable. Jedi mnogo tipova podataka. Tipovi podataka se obično koriste za validaciju vrijednosti parametara proslijeđenih klasama i definicijama. Ako proslijeđeni parametar ne odgovara navedenom tipu, pojavit će se greška kompilacije.

Tip se piše neposredno ispred imena parametra:

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

Klase: uključuju naziv klase vs class{'classname':}

Svaka klasa je resurs tipa razred. Kao i kod bilo koje druge vrste resursa, ne mogu postojati dvije instance iste klase na istom čvoru.

Ako pokušate dvaput dodati klasu istom čvoru koristeći class { 'classname':} (bez razlike, sa različitim ili identičnim parametrima), doći će do greške u kompilaciji. Ali ako koristite klasu u stilu resursa, možete odmah eksplicitno postaviti sve njene parametre u manifestu.

Međutim, ako koristite include, tada se klasa može dodati koliko god puta želite. Činjenica je da include je idempotentna funkcija koja provjerava da li je klasa dodana u direktorij. Ako klasa nije u direktoriju, dodaje je, a ako već postoji, ne radi ništa. Ali u slučaju upotrebe include Ne možete postaviti parametre klase tokom deklaracije klase - svi potrebni parametri moraju biti postavljeni u eksternom izvoru podataka - Hiera ili ENC. O njima ćemo govoriti u sljedećem članku.

Definiše

Kao što je rečeno u prethodnom bloku, ista klasa ne može biti prisutna na čvoru više od jednom. Međutim, u nekim slučajevima morate biti u mogućnosti da koristite isti blok koda s različitim parametrima na istom čvoru. Drugim riječima, postoji potreba za vlastitim tipom resursa.

Na primjer, da bismo instalirali PHP modul, u Avito-u radimo sljedeće:

  1. Instalirajte paket sa ovim modulom.
  2. Kreirajmo konfiguracijski fajl za ovaj modul.
  3. Kreiramo simboličku vezu do konfiguracije za php-fpm.
  4. Kreiramo simboličku vezu do konfiguracije za php cli.

U takvim slučajevima, dizajn kao npr definisati (definiraj, definiran tip, definiran tip resursa). Define je sličan klasi, ali postoje razlike: prvo, svaki Define je tip resursa, a ne resurs; drugo, svaka definicija ima implicitni parametar $title, gdje ide ime resursa kada je deklarirano. Kao iu slučaju klasa, definicija se prvo mora opisati, nakon čega se može koristiti.

Pojednostavljeni primjer sa modulom za 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' }
}

Najlakši način da uhvatite grešku Duplicate declaration je u Define. Ovo se dešava ako definicija ima resurs sa konstantnim imenom, a postoje dve ili više instanci ove definicije na nekom čvoru.

Lako se zaštititi od ovoga: svi resursi unutar definicije moraju imati naziv u zavisnosti od toga $title. Alternativa je idempotentno dodavanje resursa; u najjednostavnijem slučaju, dovoljno je premjestiti resurse zajedničke za sve instance definicije u posebnu klasu i uključiti ovu klasu u definiciju - funkciju include idempotentan.

Postoje i drugi načini za postizanje idempotencije prilikom dodavanja resursa, odnosno korištenje funkcija defined и ensure_resources, ali o tome ću vam pričati u sljedećoj epizodi.

Zavisnosti i obavijesti za klase i definicije

Klase i definicije dodaju sljedeća pravila rukovanju ovisnostima i obavijestima:

  • zavisnost od klase/define dodaje zavisnosti od svih resursa klase/define;
  • zavisnost klase/definisanja dodaje zavisnosti svim resursima klase/definisanja;
  • class/define notifikacija obavještava sve resurse klase/define;
  • class/define pretplata se pretplaćuje na sve resurse klase/define.

Uslovne izjave i selektori

Dokumentacija ovdje.

if

Ovdje je jednostavno:

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

osim ako nije

osim ako je ako je obrnuto: blok koda će se izvršiti ako je izraz lažan.

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

slučaj

Ni tu nema ništa komplikovano. Možete koristiti regularne vrijednosti (nizove, brojeve, itd.), regularne izraze i tipove podataka kao vrijednosti.

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

Selektori

Selektor je jezička konstrukcija slična case, ali umjesto da izvrši blok koda, vraća vrijednost.

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

Moduli

Kada je konfiguracija mala, lako se može držati u jednom manifestu. Ali što više konfiguracija opisujemo, više klasa i čvorova ima u manifestu, on raste i postaje nezgodan za rad.

Osim toga, postoji problem ponovne upotrebe koda - kada je sav kod u jednom manifestu, teško je podijeliti ovaj kod s drugima. Da bi riješio ova dva problema, Puppet ima entitet koji se zove moduli.

Moduli - ovo su skupovi klasa, definicija i drugih lutkarskih entiteta smještenih u poseban direktorij. Drugim riječima, modul je nezavisan dio lutkarske logike. Na primjer, može postojati modul za rad sa nginx-om, koji će sadržavati ono i samo ono što je potrebno za rad sa nginx-om, ili može postojati modul za rad sa PHP-om i tako dalje.

Moduli su verzionisani, a također su podržane ovisnosti modula jedan o drugom. Postoji otvoreno spremište modula - Puppet Forge.

Na lutkarskom serveru, moduli se nalaze u poddirektorijumu moduli osnovnog direktorijuma. Unutar svakog modula postoji standardna šema direktorija - manifesti, datoteke, predlošci, lib, itd.

Struktura datoteke u modulu

Korijen modula može sadržavati sljedeće direktorije s opisnim imenima:

  • manifests - sadrži manifeste
  • files - sadrži fajlove
  • templates - sadrži šablone
  • lib — sadrži Ruby kod

Ovo nije potpuna lista direktorija i datoteka, ali je za sada dovoljna za ovaj članak.

Imena resursa i imena datoteka u modulu

Dokumentacija ovdje.

Resursi (klase, definicije) u modulu se ne mogu imenovati kako god želite. Osim toga, postoji direktna korespondencija između imena resursa i imena datoteke u kojoj će Puppet tražiti opis tog resursa. Ako prekršite pravila imenovanja, tada Puppet jednostavno neće pronaći opis resursa i dobit ćete grešku u kompilaciji.

Pravila su jednostavna:

  • Svi resursi u modulu moraju biti u imenskom prostoru modula. Ako je modul pozvan foo, tada bi svi resursi u njemu trebali biti imenovani foo::<anything>, ili samo foo.
  • Resurs sa imenom modula mora biti u datoteci init.pp.
  • Za ostale resurse, shema imenovanja datoteka je sljedeća:
    • prefiks sa imenom modula se odbacuje
    • sve dvostruke dvotočke, ako ih ima, zamjenjuju se kosim crtama
    • proširenje je dodano .pp

Pokazat ću na primjeru. Recimo da pišem modul nginx. Sadrži sljedeće resurse:

  • razred nginx opisano u manifestu init.pp;
  • razred nginx::service opisano u manifestu service.pp;
  • definisati nginx::server opisano u manifestu server.pp;
  • definisati nginx::server::location opisano u manifestu server/location.pp.

Obrasci

Sigurno i sami znate šta su šabloni; neću ih ovdje detaljno opisivati. Ali ostaviću to za svaki slučaj link na Wikipediju.

Kako koristiti šablone: ​​Značenje šablona se može proširiti pomoću funkcije template, kojem se prosljeđuje putanja do predloška. Za resurse tipa fajl koristi se zajedno sa parametrom content. Na primjer, ovako:

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

View path <modulename>/<filename> implicira fajl <rootdir>/modules/<modulename>/templates/<filename>.

Osim toga, postoji i funkcija inline_template — prima tekst šablona kao unos, a ne naziv datoteke.

Unutar predložaka možete koristiti sve varijable Puppet u trenutnom opsegu.

Puppet podržava šablone u ERB i EPP formatu:

Ukratko o ERB-u

Kontrolne strukture:

  • <%= ВЫРАЖЕНИЕ %> — ubacite vrijednost izraza
  • <% ВЫРАЖЕНИЕ %> — izračunajte vrijednost izraza (bez umetanja). Uvjetne izjave (if) i petlje (svaka) obično idu ovdje.
  • <%# КОММЕНТАРИЙ %>

Izrazi u ERB-u su napisani u Ruby-u (ERB je zapravo Embedded Ruby).

Da biste pristupili varijablama iz manifesta, morate dodati @ na ime varijable. Da biste uklonili prijelom reda koji se pojavljuje nakon kontrolne konstrukcije, trebate koristiti završnu oznaku -%>.

Primjer korištenja šablona

Recimo da pišem modul za kontrolu ZooKeeper-a. Klasa odgovorna za kreiranje konfiguracije izgleda otprilike ovako:

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 odgovarajući šablon zoo.cfg.erb - Dakle:

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

Činjenice i ugrađene varijable

Često određeni dio konfiguracije ovisi o tome što se trenutno događa na čvoru. Na primjer, ovisno o tome koje je Debian izdanje, trebate instalirati jednu ili drugu verziju paketa. Sve ovo možete pratiti ručno, prepisivanjem manifesta ako se čvorovi promijene. Ali ovo nije ozbiljan pristup, automatizacija je mnogo bolja.

Za dobijanje informacija o čvorovima, Puppet ima mehanizam koji se zove činjenice. Činjenice - ovo je informacija o čvoru, dostupna u manifestima u obliku običnih varijabli u globalnom prostoru imena. Na primjer, ime hosta, verzija operativnog sistema, arhitektura procesora, lista korisnika, lista mrežnih sučelja i njihovih adresa i još mnogo, mnogo više. Činjenice su dostupne u manifestima i predlošcima kao regularne varijable.

Primjer rada sa činjenicama:

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

Formalno gledano, činjenica ima ime (string) i vrijednost (dostupni su različiti tipovi: nizovi, nizovi, rječnici). Jedi skup ugrađenih činjenica. Možete napisati i svoje. Opisani su sakupljači činjenica kao funkcije u Rubyjubilo kao izvršne datoteke. Činjenice se također mogu predstaviti u obliku tekstualne datoteke sa podacima na čvorovima.

Tokom rada, puppet agent prvo kopira sve dostupne sakupljače činjenica sa servera papeta u čvor, nakon čega ih pokreće i šalje prikupljene činjenice na server; Nakon toga, server počinje sa sastavljanjem kataloga.

Činjenice u obliku izvršnih datoteka

Takve činjenice su smještene u modulima u imeniku facts.d. Naravno, fajlovi moraju biti izvršni. Kada se pokreću, oni moraju poslati informacije u standardni izlaz u YAML ili formatu ključ=vrijednost.

Ne zaboravite da se činjenice odnose na sve čvorove koji su pod kontrolom popet servera na koji je vaš modul raspoređen. Stoga, u skripti, vodite računa da provjerite da li sistem ima sve programe i datoteke neophodne da vaša činjenica radi.

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

Ruby facts

Takve činjenice su smještene u modulima u imeniku 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

Tekstualne činjenice

Takve činjenice se postavljaju na čvorove u direktoriju /etc/facter/facts.d u starom Puppet ili /etc/puppetlabs/facts.d u novom Lutku.

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

Dolazimo do činjenica

Postoje dva načina da se pristupi činjenicama:

  • kroz rečnik $facts: $facts['fqdn'];
  • koristeći ime činjenice kao ime varijable: $fqdn.

Najbolje je koristiti rječnik $facts, ili još bolje, naznačite globalni prostor imena ($::facts).

Evo relevantnog dijela dokumentacije.

Ugrađene varijable

Osim činjenica, postoji i neke varijable, dostupno u globalnom imenskom prostoru.

  • pouzdane činjenice — varijable koje su preuzete iz klijentovog certifikata (pošto se certifikat obično izdaje na popet serveru, agent ne može samo uzeti i promijeniti svoj certifikat, tako da su varijable “pouzdane”): naziv certifikata, naziv certifikata host i domena, ekstenzije iz certifikata.
  • serverske činjenice —promenljive koje se odnose na informacije o serveru—verzija, ime, IP adresa servera, okruženje.
  • agentske činjenice — varijable koje dodaje direktno puppet-agent, a ne faktor — naziv certifikata, verzija agenta, verzija lutke.
  • master varijable - Pappetmaster varijable (sic!). To je otprilike isto kao u serverske činjenice, plus vrijednosti konfiguracijskih parametara su dostupne.
  • varijable kompajlera — varijable kompajlera koje se razlikuju u svakom opsegu: naziv trenutnog modula i naziv modula u kojem se pristupilo trenutnom objektu. Mogu se koristiti, na primjer, za provjeru da se vaše privatne klase ne koriste direktno iz drugih modula.

Dodatak 1: kako pokrenuti i otkloniti sve ovo?

Članak je sadržavao mnogo primjera lutkarskog koda, ali nam uopće nije rekao kako pokrenuti ovaj kod. Pa, ispravljam se.

Za pokretanje Puppet-a dovoljan je agent, ali u većini slučajeva će vam trebati i server.

Agent

Barem od verzije XNUMX, paketi puppet-agent od službeni repozitorij Puppetlabs sadrže sve ovisnosti (ruby i odgovarajuće dragulje), tako da nema poteškoća s instalacijom (govorim o distribucijama baziranim na Debianu - ne koristimo distribucije zasnovane na RPM-u).

U najjednostavnijem slučaju, da biste koristili konfiguraciju lutke, dovoljno je pokrenuti agenta u načinu rada bez servera: pod uvjetom da je kod lutke kopiran u čvor, pokrenite 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

Bolje je, naravno, postaviti server i pokrenuti agente na čvorovima u demonskom modu - tada će svakih pola sata primijeniti konfiguraciju preuzetu sa servera.

Možete imitirati push model rada - idite na čvor koji vas zanima i počnite sudo puppet agent -t... Ključ -t (--test) zapravo uključuje nekoliko opcija koje se mogu pojedinačno omogućiti. Ove opcije uključuju sljedeće:

  • nemojte pokretati u demonskom modu (po defaultu agent počinje u demon modu);
  • isključite se nakon primjene kataloga (po defaultu, agent će nastaviti s radom i primjenjivati ​​konfiguraciju svakih pola sata);
  • napisati detaljan dnevnik rada;
  • prikaži promjene u fajlovima.

Agent ima način rada bez promjena - možete ga koristiti kada niste sigurni da ste napisali ispravnu konfiguraciju i želite provjeriti šta će tačno agent promijeniti tokom rada. Ovaj način rada je omogućen parametrom --noop na komandnoj liniji: sudo puppet agent -t --noop.

Osim toga, možete omogućiti dnevnik otklanjanja grešaka rada - u njemu lutka piše o svim radnjama koje izvodi: o resursu koji trenutno obrađuje, o parametrima ovog resursa, o tome koje programe pokreće. Naravno, ovo je parametar --debug.

Server

U ovom članku neću razmatrati potpunu postavku pappetservera i implementaciju koda na njega; samo ću reći da iz kutije postoji potpuno funkcionalna verzija servera koja ne zahtijeva dodatnu konfiguraciju za rad s malim brojem čvorova (recimo, do stotinu). Veći broj čvorova će zahtijevati podešavanje - po defaultu, puppetserver pokreće ne više od četiri radnika, za veće performanse morate povećati njihov broj i ne zaboravite povećati ograničenja memorije, inače će server većinu vremena sakupljati smeće.

Postavljanje koda - ako vam je potrebno brzo i jednostavno, pogledajte (na r10k)[https://github.com/puppetlabs/r10k], za male instalacije bi trebalo biti sasvim dovoljno.

Dodatak 2: Smjernice za kodiranje

  1. Postavite svu logiku u klase i definicije.
  2. Držite klase i definicije u modulima, a ne u manifestima koji opisuju čvorove.
  3. Koristite činjenice.
  4. Nemojte praviti if-ove na osnovu imena hostova.
  5. Slobodno dodajte parametre za klase i definicije - ovo je bolje od implicitne logike skrivene u tijelu klase/define.

Objasnit ću zašto to preporučujem u sljedećem članku.

zaključak

Završimo sa uvodom. U sljedećem članku ću vam reći o Hieri, ENC-u i PuppetDB-u.

Samo registrovani korisnici mogu učestvovati u anketi. Prijavite semolim.

Zapravo, materijala ima mnogo više - mogu pisati članke o sljedećim temama, glasati o onome o čemu bi vas zanimalo:

  • 59,1%Napredne lutkarske konstrukcije - neka sranja na sljedećem nivou: petlje, mapiranje i drugi lambda izrazi, sakupljači resursa, izvezeni resursi i komunikacija među hostovima putem Puppet-a, oznaka, dobavljača, apstraktnih tipova podataka.13
  • 31,8%“Ja sam mamin admin” ili kako smo se u Avitu sprijateljili sa nekoliko poppet servera različitih verzija i, u principu, dio o administriranju poppet servera.7
  • 81,8%Kako pišemo lutkarski kod: instrumentacija, dokumentacija, testiranje, CI/CD.18

Glasalo je 22 korisnika. Uzdržano je bilo 9 korisnika.

izvor: www.habr.com