Uvod v Puppet

Puppet je sistem za upravljanje konfiguracijama. Koristi se za dovođenje hostova u željeno stanje i održavanje tog stanja.

Radim sa Puppetom više od pet godina. Ovaj tekst je u suštini prevedena i reorganizovana kompilacija ključnih tačaka iz zvanične dokumentacije, koja će omogućiti početnicima da brzo razumeju suštinu Puppeta.

Uvod v Puppet

Osnovne informacije

Shema rada Puppeta je klijent-server, iako se podržava i varijanta rada bez servera sa ograničenom funkcionalnošću.

Koristi se pull-model rada: po defaultu svakih pola sata klijenti se obraćaju serveru za konfiguraciju i primenjuju je. Ako ste radili sa Ansible, tu se koristi drugačiji model, push-model: administrator inicira proces primene konfiguracije, a klijenti sami ništa ne primenjuju.

Pri mrežnoj interakciji koristi se dvosmerno TLS-enkripcija: server i klijent imaju svoje privatne ključeve i odgovarajuće sertifikate. Obično server izdaje sertifikate za klijente, ali je u principu moguće koristiti i spoljašnje CA.

Upoznavanje s manifestima

U terminologiji Puppeta k puppetskom serveru priključuju se nodovi (nodes). Konfiguracija za nodove piše se u manifestima na specijalnom programskom jeziku - Puppet DSL.

Puppet DSL je deklarativni jezik. Na njemu se opisuje željeno stanje nod-a u obliku deklaracija pojedinačnih resursa, na primer:

  • Datoteka postoji i ima određeni sadržaj.
  • Paket je instaliran.
  • Servis je pokrenut.

Resursi mogu biti međusobno povezani:

  • Postoje zavisnosti koje utiču na redosled primene resursa.
    Na primer, 'prvo instaliraj paket, zatim ispravi konfiguracioni fajl, a nakon toga pokreni servis'.
  • Postoje obaveštenja - ako se resurs promeni, šalje obaveštenja resursima koji su na njega prijavljeni.
    Na primer, ako se promeni konfiguracioni fajl, servis se može automatski ponovo pokrenuti.

Pored toga, u Puppet DSL postoje funkcije i promenljive, kao i uslovni operatori i selektori. Takođe se podržavaju različiti mehanizmi šablonizacije - EPP i ERB.

Puppet je napisan na Ruby-ju, pa su mnoge konstrukcije i termini preuzeti odatle. Ruby omogućava proširivanje Puppeta - dodavanje složene logike, novih tipova resursa, funkcija.

Med delovanjem Puppet se manifesti za vsako določeno vozlišče na strežniku prevajajo v katalog. Katalog je seznam virov in njihovih medsebojnih povezav po izračunu vrednosti funkcij, spremenljivk in razkritju pogojnih operaterjev.

Sintaksa in stil kodiranja

Tukaj so oddelki uradne dokumentacije, ki vam bodo pomagali razumeti sintakso, če navedeni primeri ne bodo dovolj:

Tukaj je primer, kako izgleda manifest:

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

Odstotki in prelomi vrstic niso obvezni del manifesta, vendar obstaja priporočeni stil. Kratka različica:

  • Dvojniki niso uporabljeni, tabulatorji pa se ne uporabljajo.
  • Zavojne oklepaje so ločene s presledkom, dvojni podpičje pa ni ločen s presledkom.
  • Zareze po vsakem parametru, vključno z zadnjim. Vsak parameter naj bo v ločeni vrstici. Izjema se naredi za primere brez parametrov in enega parametra: lahko jih napišete v eni vrstici in brez zareze (t.j. resource { 'title': } in resource { 'title': param => value }).
  • Puščice pri parametrih morajo biti na enem nivoju.
  • Puščice medsebojne povezanosti virov se pišejo pred njimi.

Postavitev datotek na puppeteer strežniku

Za nadaljnja pojasnila uvajam pojem »koreninska mapa«. Koreninska mapa je mapa, v kateri se nahaja Puppet konfiguracija za določeno vozlišče.

Koreninska mapa se razlikuje glede na različico Puppet in uporabo okolij. Okolja so neodvisni nabori konfiguracij, ki se hranijo v ločenih mapah. Običajno se uporabljajo v kombinaciji z gitom, v tem primeru se okolja ustvarjajo iz vejah gita. Tako vsako vozlišče pripada določenemu okolju. To se nastavi na samem vozlišču ali v ENC, o čemer bom govoril v naslednjem članku.

  • V tretji različici (»stari Puppet«) je bila osnovna mapa /etc/puppet. Uporaba okolij je opcijska – na primer, mi jih ne uporabljamo s starim Puppetom. Če se okolja uporabljajo, jih običajno hranijo v /etc/puppet/environments, bo koreninska mapa mapa okolja. Če se okolja ne uporabljajo, bo koreninska mapa osnovna.
  • Od četrte različice (»novi Puppet«) pa je postalo obvezno uporabljati okolja, osnovna mapa pa je bila premaknjena v /etc/puppetlabs/code. Tako se okolja shranijo v /etc/puppetlabs/code/environments, korenska mapa je mapa okolja.

V korenski mapi mora biti podmapa manifests, v kateri so en ali več manifestov z opisom vozlišč. Poleg tega mora biti tam podmapa modules, v kateri so moduli. Kaj so moduli, bom razložil malo kasneje. Poleg tega je v starem Puppet-u lahko tudi podmapa files, v kateri so različne datoteke, ki jih kopiramo na vozlišča. V novem Puppet-u pa so vse datoteke umaknjene v module.

Datoteke manifestov imajo končnico .pp.

Par bojnih primerov

Opis vozlišča in vira na njem

Na vozlišču server1.testdomain mora biti ustvarjena datoteka /etc/issue s vsebino Debian GNU/Linux n l. Datoteka mora pripadati uporabniku in skupini root, pravice dostopa morajo biti 644.

Pišemo manifest:

node 'server1.testdomain' {   # blok konfiguracije, ki se nanaša na vozlišče server1.testdomain
    file { '\/etc\/issue':   # opisujemo datoteko \/etc\/issue
        ensure  => present,   # ta datoteka mora obstajati
        content => 'Debian GNU\/Linux n l',   # imeti mora to vsebino
        owner   => root,   # uporabnik lastnik
        group   => root,   # skupina lastnik
        mode    => '0644',   # pravice za datoteko. Določene so v obliki niza (v oklepajih), ker bo v nasprotnem primeru številka z 0 na začetku obravnavana kot napisana v osemški številčni sistem, in vse ne bo šlo tako, kot je bilo zamišljeno
    }
}

Medsebojni odnosi virov na vozlišču

Na vozlišču server2.testdomain mora biti zagnan nginx, ki deluje z vnaprej pripravljenimi nastavitvami.

Razčlenimo nalogo:

  • Potrebno je, da je nameščen paket nginx.
  • Potrebno je, da so konfiguracijske datoteke kopirane s strežnika.
  • Potrebno je, da je storitev zagnana nginx.
  • V primeru posodobitve konfiguracije je potrebno znova zagnati storitev.

Pišemo manifest:

node 'server2.testdomain' {   # konfiguracijski blok koji se odnosi na nod server2.testdomain
    package { 'nginx':   # opisujemo paket nginx
        ensure => installed,   # treba biti instaliran
    }
  # Direktna strelica (->) ukazuje da resurs ispod mora
  # biti kreiran nakon resursa opisanog iznad.
  # Takve zavisnosti su tranzitivne.
    -> file { '/etc/nginx':   # opisujemo datoteku /etc/nginx
        ensure => directory,   # ovo treba biti direktorij
        source => 'puppet:///modules/example/nginx-conf',   # sadržaj treba uzeti sa puppet servera na naznačenoj adresi
        recurse => true,   # kopirati datoteke rekurzivno
        purge => true,   # treba ukloniti suvišne datoteke (one koje nisu u izvoru)
        force => true,   # ukloniti suvišne direktorije
    }
  # Talasasta strelica (~>) ukazuje da resurs ispod mora
  # pretplatiti se na promene resursa opisanog iznad.
  # Talasasta strelica uključuje direktnu (->).
    ~> service { 'nginx':   # opisujemo servis nginx
        ensure => running,   # treba biti pokrenut
        enable => true,   # treba ga automatski pokrenuti prilikom starta sistema
    }
  # Kada resurs tipa usluga dobije obaveštenje,
  # odgovarajući servis se ponovo pokreće.
}

Da bi to radilo, potrebno je otprilike ovakvo raspoređivanje datoteka na puppet serveru:

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

Tipovi resursa

Potpuni spisak podržanih tipova resursa možete naći u dokumentaciji, ovde ću opisati pet osnovnih tipova, koji su u mojoj praksi dovoljni za rešavanje većine zadataka.

file

Upravlja datotekama, direktorijumima, simboličkim linkovima, njihovim sadržajem, pravima pristupa.

Parametri:

  • ime resursa — put do datoteke (opciono)
  • path — put do datoteke (ako nije zadat u imenu)
  • ensure — tip datoteke:
    • absent — ukloni datoteku
    • present — treba biti datoteka bilo kog tipa (ako datoteka ne postoji — biće kreirana obična datoteka)
    • file — obična datoteka
    • directory — direktorij
    • link — simbolički link
  • content — sadržaj datoteke (prikladno samo za obične datoteke, ne može se koristiti zajedno s source ili target)
  • source — referenca na put s kojeg treba kopirati sadržaj datoteke (ne može se koristiti zajedno s content ili target). Može biti postavljena kao URI sa shemom puppet: (tada će se koristiti datoteke s puppet servera), ili sa shemom http: (nadam se da je jasno šta će biti u tom slučaju), pa čak i sa shemom file: ili kao apsolutna putanja bez sheme (tada će se koristiti datoteka s lokalnog FS na nodu)
  • target — куда должен указывать симлинк (нельзя использовать вместе с content ili source)
  • owner — пользователь, которому должен принадлежать файл
  • group — группа, которой должен принадлежать файл
  • mode — права на файл (в виде строки)
  • recurse — включает рекурсивную обработку директорий
  • purge — включает удаление файлов, которые не описаны в Puppet
  • force — включает удаление директорий, которые не описаны в Puppet

package

Устанавливает и удаляет пакеты. Умеет обрабатывать уведомления — переустанавливает пакет, если задан параметр reinstall_on_refresh.

Parametri:

  • ime resursa — название пакета (опционально)
  • name — название пакета (если не задано в названии)
  • provider — пакетный менеджер, который нужно использовать
  • ensure — желаемое состояние пакета:
    • present, installed — установлена любая версия
    • latest — установлена последняя версия
    • absent — удалён (apt-get remove)
    • purged — удалён вместе с конфигурационными файлами (apt-get purge)
    • held — версия пакета заблокирована (apt-mark hold)
    • любая другая строка — установлена указанная версия
  • reinstall_on_refresh — если true, то при получении уведомления пакет будет переустановлен. Полезно для source-based дистрибутивов, где пересборка пакетов может быть необходима при изменении параметров сборки. По умолчанию false.

service

Управляет сервисами. Умеет обрабатывать уведомления — перезапускает сервис.

Parametri:

  • ime resursa — сервис, которым нужно управлять (опционально)
  • name — сервис, которым нужно управлять (если не задано в названии)
  • ensure — желаемое состояние сервиса:
    • running — запущен
    • stopped — остановлен
  • enable — управляет возможностью запуска сервиса:
    • true — включен автозапуск (systemctl enable)
    • mask — замаскирован (systemctl mask)
    • false — выключен автозапуск (systemctl disable)
  • restart — команда для перезапуска сервиса
  • status — команда для проверки статуса сервиса
  • hasrestart — указать, поддерживает ли инитскрипт сервиса перезапуск. Если false и указан параметр restart — используется значение этого параметра. Если false и параметр restart не указан — сервис останавливается и запускается для перезапуска (но в systemd используется команда systemctl restart).
  • hasstatus — указать, поддерживает ли инитскрипт сервиса команду status. Если false, то используется значение параметра status. По умолчанию true.

exec

Запускает внешние команды. Если не указывать параметры creates, onlyif, unless ili refreshonly, команда будет запускаться при каждом прогоне Паппета. Умеет обрабатывать уведомления — запускает команду.

Parametri:

  • ime resursa — ukaz, ki ga je treba izvesti (neobvezno)
  • ukaz — ukaz, ki ga je treba izvesti (če ni določen v nazivu)
  • path — poti, v katerih iskati izvršljivo datoteko
  • onlyif — če je ukaz v tem parametru končal z ničelno kodo izhoda, bo glavni ukaz izveden
  • unless — če je ukaz v tem parametru končal z nenično kodo izhoda, bo glavni ukaz izveden
  • creates — če navedena datoteka v tem parametru ne obstaja, bo glavni ukaz izveden
  • refreshonly — если true, ukaz bo izveden le, če ta exec prejme obvestilo od drugih virov
  • cwd — imenik, iz katerega bo ukaz izveden
  • uporabnik — uporabnik, pod katerim bo ukaz izveden
  • provider — s pomočjo katerega bo ukaz izveden:
    • posix — preprosto se ustvari podproces, obvezno navesti path
    • shell — ukaz se izvede v shellu /bin/sh, ni treba navesti path, lahko uporabite globbing, pipe in druge funkcije shella. Običajno je samodejno določeno, če so prisotni posebni simboli (|, ;, &&, || in tako naprej).

cron

Upravlja cron nalogami.

Parametri:

  • ime resursa — preprosto identifikator
  • ensure — stanje cron naloge:
    • present — ustvarite, če ne obstaja
    • absent — izbrišite, če obstaja
  • ukaz — katero ukaz izvršiti
  • okolje — v katerem okolju bo ukaz izveden (seznam spremenljivk okolja in njihovih vrednosti preko =)
  • uporabnik — od katerega uporabnika bo ukaz izveden
  • minuta, ura, dan v tednu, mesec, dan v mesecu — kdaj izvesti cron. Če kateri od teh atributov ni naveden, bo njegova vrednost v cron tabeli *.

V Puppet 6.0 cron kot bi izbrisali iz škatle v puppetserverju, zato ni dokumentacije na splošni strani. Ampak on je v škatli v puppet-agentu, zato ga ni treba namestiti posebej. Dokumentacijo o njem lahko vidite v dokumentaciji pete različice Puppeta, ali na GitHubu.

O virih na splošno

Zahteve za edinstvenost virov

Najpogostejša napaka, s katero se srečujemo — Podvojena deklaracija. Ta napaka se zgodi, ko v imenik pridejo dve ali več virov enakega tipa z enakim imenom.

Zato ponavljam: v manifestih za en node ne sme biti virov enakega tipa z enakim imenom (title)!

Včasih je treba namestiti pakete z enakim imenom, vendar z različnimi upravljalci paketov. V takem primeru je treba uporabiti parameter name, da bi se izognili napaki:

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

V drugih vrstah virov so podobni parametri, ki pomagajo preprečiti podvajanje, — name za service, ukaz za exec, in tako naprej.

Metaparametri

Nekateri posebni parametri so prisotni pri vsakem tipu vira, ne glede na njegovo entiteto.

Celoten seznam metaparametrov v dokumentaciji Puppet.

Kratki seznam:

  • require — v tem parametru so navedeni viri, od katerih je odvisen ta vir.
  • before — v tem parametru so navedeni viri, ki so odvisni od tega vira.
  • subscribe — v tem parametru so navedeni viri, od katerih ta vir prejema obvestila.
  • notify — v tem parametru so navedeni viri, ki prejmejo obvestila od tega vira.

Vsi navedeni metaparametri sprejemajo bodisi eno povezavo na vir bodisi seznam povezav v kvadratnih oklepajih.

Povezave na vire

Povezava na vir je preprosto omemba vira. Uporabljajo se predvsem za določanje odvisnosti. Povezava na neobstoječ vir bo povzročila napako pri kompilaciji.

Syntaksa za povezavo je naslednja: tip vira z veliko začetnico (če je v imenu tipa dvojni dvopičje, se vsaka delitev med dvopičji piše z veliko začetnico), nato pa v kvadratnih oklepajih ime vira (velikost črk imena se ne spremeni!). Prostorov ne sme biti, kvadratni oklepaji se pišejo takoj za imenom tipa.

Primer:

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

Odvisnosti in obvestila

Dokumentacija tukaj.

Kot je bilo že omenjeno, so preproste odvisnosti med viri tranzitivne. Mimogrede, bodite pazljivi pri postavljanju odvisnosti — lahko ustvarite ciklične odvisnosti, kar bo povzročilo napako pri kompilaciji.

V nasprotju z odvisnostmi obvestila niso tranzitivna. Za obvestila veljajo naslednja pravila:

  • Če vir prejme obvestilo, se posodobi. Dejanje pri posodabljanju je odvisno od tipa vira — exec izvede ukaz, service ponovno zažene storitev, package ponovno namesti paket. Če za vir ni določeno dejanje pri posodabljanju, se ne zgodi nič.
  • V eni iteraciji Puppet se vir posodobi največ enkrat. To je mogoče, ker obvestila vključujejo odvisnosti, graf odvisnosti pa ne vsebuje ciklov.
  • Če Puppet spremeni stanje vira, vir pošlje obvestila vsem, ki so na njega naročeni.
  • Če se vir posodobi, pošlje obvestila vsem, ki so na njega naročeni.

Obdelava neobveznih parametrov

Na splošno, če nekemu parametru vira ni dodeljena privzeta vrednost in ta parameter ni naveden v manifestu, Puppet ne bo spremenil te lastnosti pri ustreznem viru na nodi. Na primer, če pri viru tipa file ni naveden parameter owner, Puppet ne bo spremenil lastnika ustrezne datoteke.

Spoznavanje razredov, spremenljivk in definicij

Recimo, da imamo več nod, na katerih je enaka del konfiguracije, vendar so prisotne tudi razlike — drugače bi lahko vse to opisali v enem bloku. node {}. Seveda bi lahko preprosto kopirali enake dele konfiguracije, vendar je to v splošnem slabša rešitev — konfiguracija se povečuje, pri spremembi skupnega dela konfiguracije bo treba popraviti isto stvar na več mestih. Pri tem se lahko zlahka zmotiš, in načelo DRY (don’t repeat yourself) ni brez razloga.

Za rešitev takšne težave obstaja konstrukcija, kot je razred.

Razredi

Razred je poimenovan blok Puppet kode. Razredi so potrebni za ponovno uporabo kode.

Najprej je treba razred opisati. Sam opis ne doda nobenih virov nikamor. Razred se opisuje v manifestih:

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

Po tem je mogoče razred uporabiti:

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

Primer iz prejšnje naloge — prenesimo namestitev in konfiguracijo nginx v razred:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recurse => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    include nginx_example
}

Spremenljivke

Razred iz prejšnjega primera ni zelo prilagodljiv, saj vedno prinaša isto konfiguracijo nginx. Naredimo, da bo pot do konfiguracije spremenljiva, tako da bo mogoče ta razred uporabiti za namestitev nginx z različnimi konfiguracijami.

To se lahko stori s pomočjo spremenljivk.

Pozor: spremenljivke v Puppet so nespremenljive!

Poleg tega lahko dostopate do spremenljivke šele po tem, ko je bila razglašena, sicer bo vrednost spremenljivke undef.

Primer dela s spremenljivkami:

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

V Puppet obstajajo imenska področja, spremenljivke pa imajo ustrezno območje vidnosti: spremenljivka z istim imenom je lahko definirana v različnih imenskih področjih. Pri razreševanju vrednosti spremenljivke se spremenljivka išče v trenutnem imenskem področju, nato v obsežnejšem, in tako naprej.Primeri imenskih področij:

globalno — sem spadajo spremenljivke zunaj opisa razreda ali vozlišča;

  • imensko področje vozlišča v opisu vozlišča;
  • imensko področje razreda v opisu razreda.
  • Da bi se izognili nejasnostim pri dostopu do spremenljivke, lahko v imenu spremenljivke navedete imensko področje:

Dogovorimo se, da bo pot do konfiguracije nginx ležala v spremenljivki

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

$nginx_conf_source . Takole bo izgledal razred:class nginx_example { package { 'nginx': ensure => installed, } -> file { '/etc/nginx': ensure => directory, source => $nginx_conf_source, # tukaj uporabljamo spremenljivko namesto fiksne vrstice 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 }

Vendar pa je ta primer slab, ker obstaja neka "skrivna znanja" o tem, da nekje znotraj razreda uporablja spremenljivko s takim imenom. Veliko bolj pravilno je, da to znanje postane skupno — razredi lahko imajo parametre.

Parametri razreda

— so spremenljivke v imenskem področju razreda, določajo se v naslovu razreda in jih je mogoče uporabiti kot navadne spremenljivke v telesu razreda. Vrednosti parametrov se določijo pri uporabi razreda v manifestu. Parametru lahko določite privzeto vrednost. Če parametra nima privzete vrednosti in vrednost ni določena pri uporabi, bo to povzročilo napako pri prevajanju.

Poglejmo, kako parametrize razred iz zgornjega primera in dodajmo dva parametra: prvega, obveznega — pot do konfiguracije, in drugega, neobveznega — ime paketa z nginx (v Debian, na primer, so paketi

nginx-light nginx, nginx-full, V Puppet so spremenljivke tipizirane. Obstaja).

# переменные описываются сразу после имени класса в круглых скобках
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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

veliko tipov podatkov veliko tipov podatkov. Tipi podatkov se običajno uporabljajo za validacijo vrednosti parametrov, ki se prenašajo v razrede in definicije. Če posredovani parameter ne ustreza določenemu tipu, bo prišlo do napake pri prevajanju.

Tip je zapisan neposredno pred imenom parametra:

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

Razredi: include classname vs class{‘classname’:}

Vsak razred je vir tipa class. Tako kot pri vseh drugih tipih virov, na eni nodi ne more biti dveh instanc istega razreda.

Če poskusite dodati razred na isto nodo dvakrat s pomočjo class { 'classname':} (brez razlike, z različnimi ali enakimi parametri), bo prišlo do napake pri prevajanju. Vendar pa v primeru uporabe razreda v slogu vira lahko takoj v manifestu izrecno določite vse njegove parametre.

Vendar pa, če uporabljate include, lahko razred dodate neomejeno število krat. Gre za to, da include je idempotentna funkcija, ki preverja, ali je razred že dodan v katalog. Če razreda v katalogu ni, ga doda, sicer pa ne stori nič. Ampak v primeru uporabe include ni mogoče določiti parametrov razreda med napovedjo razreda - vsi obvezni parametri morajo biti določeni v zunanjem viru podatkov - Hiera ali ENC. O tem bomo govorili v naslednjem članku.

Defini

Kot je bilo omenjeno v prejšnjem bloku, isti razred ne more biti prisoten na nodi več kot enkrat. Vendar pa v nekaterih primerih potrebujemo možnost uporabe istega bloka kode z različnimi parametri na eni nodi. Z drugimi besedami, obstaja potreba po lastnem tipu virov.

Na primer, za namestitev PHP modula v Avitu storimo naslednje:

  1. Namestimo paket s tem modulom.
  2. Ustvarimo konfiguracijsko datoteko za ta modul.
  3. Ustvarimo simbolno povezavo na konfiguracijo za php-fpm.
  4. Ustvarimo simbolno povezavo na konfiguracijo za php cli.

V takih primerih uporabimo konstrukcijo, kot je definiranje (define, defined type, defined resource type). Definiranje je podobno razredu, vendar obstajajo razlike: po eni strani je vsako definicijo tip virov, ne vir; po drugi strani, vsaka definicija ima implicitni parameter $title, kjer pride ime vira ob njegovi napovedi. Tako kot pri razredih, je tudi definicijo najprej treba opisati, šele nato jo lahko uporabimo.

Poenostavljen primer s 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'],  # debian php paketi sami ustvarjajo symlinke in ponovno zaženejo servis php-fpm - to nam ni potrebno, saj z symlinki in servisom upravljamo s Puppet
  }
  -> file { "${php_module_path}/${php_module_name}.ini":
    ensure  => $ensure,
    content => $data,
  }
  file { "etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
  file { "etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
}

node server3.testdomain {
  php74::module { 'sqlite3': }
  php74::module { 'amqp': php_package_name => 'php-amqp' }
  php74::module { 'msgpack': priority => '10' }
}

V definiciji je najlažje ujeti napako Duplicate declaration. Do tega pride, če je v definiciji vir s konstantnim imenom, in na neki nodi sta dva ali več primerkov te definicije.

Obraniti se pred tem je preprosto: vsi viri znotraj definicije morajo imeti ime, ki je odvisno od $title. Kot alternativo - idempotentno dodajanje virov, v najpreprostejšem primeru je dovolj, da skupne vire za vse primere definicije prenesemo v ločeno klaso in vključimo to klaso v definicijo - funkcija include je idempotentna.

Obstajajo tudi drugi načini za dosego idempotentnosti pri dodajanju virov, in sicer z uporabo funkcij defined in ensure_resources, ampak o tem bom govoril v naslednji seriji.

Odvisnosti in obvestila za klase in definicije

Klase in definicije dodajajo naslednja pravila za obdelavo odvisnosti in obvestil:

  • odvisnost od klase/definicije dodaja odvisnosti od vseh virov klase/definicije;
  • odvisnost klase/definicije dodaja odvisnosti vsem virom klase/definicije;
  • obvestilo klase/definicije obvešča vse vire klase/definicije;
  • prijava na klas/definicijo prijavi vse vire klase/definicije.

Pogojni operatorji in selektorji

Dokumentacija tukaj.

if

Tukaj je vse preprosto:

if IZRAZ1 {
  ...
} elsif IZRAZ2 {
  ...
} else {
  ...
}

unless

unless — to je if v obratni smeri: blok kode bo izveden, če je izraz lažen.

unless IZRAZ {
  ...
}

case

Tudi tukaj ni nič zapletenega. Kot vrednosti lahko uporabite običajne vrednosti (niz, števila itd.), regularne izraze ter vrste podatkov.

primer IZRAŽANJE {
  VREDNOST1: { ... }
  VREDNOST2, VREDNOST3: { ... }
  privzeto: { ... }
}

Izbirniki

Izbirnik je jezikovna konstrukcija, podobna case, le da namesto izvajanja bloka kode vrne vrednost.

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

Moduli

Ko je konfiguracija majhna, jo je mogoče enostavno obdržati v enem manifestu. Vendar pa bolj ko opisujemo konfiguracijo, več razredov in vozlišč postane v manifestu, ta se povečuje, z njim pa postane težko delati.

Poleg tega obstaja problem ponovno uporabe kode - ko je vsa koda v enem manifestu, je težko deliti to kodo z drugimi. Za reševanje teh dveh težav v Puppet-u obstaja entiteta, imenovana moduli.

Moduli — to so nizi razredov, definicij in drugih Puppet entitet, prestavljenih v ločeno direktorij. Z drugimi besedami, modul je neodvisen kos Puppet logike. Na primer, lahko je modul za delo z nginx, v njem bo le to, kar je potrebno za delo z nginx, ali pa modul za delo s PHP in tako naprej.

Moduli so različni in podpirajo odvisnosti med moduli. Obstaja odprti repozitorij modulov - Puppet Forge.

Na Puppet strežniku moduli ležijo v poddirektoriju modules v korenski mapi. Znotraj vsakega modula je standardna struktura direktorijev - manifests, files, templates, lib in tako naprej.

Struktura datotek v modulu

V korenu modula so lahko naslednje mape z govorečimi imeni:

  • manifests — v njej so manifesti
  • files — v njej so datoteke
  • templates — v njej so predloge
  • lib — v njej je Ruby koda

To ni celoten seznam direktorijev in datotek, vendar je za ta članek dovolj.

Imena virov in datotek v modulu

Dokumentacija tukaj.

Viri (razredi, definicije) v modulu se ne morejo imenovati poljubno. Poleg tega obstaja neposredna povezava med imenom vira in imenom datoteke, v kateri bo Puppet iskal opis tega vira. Če kršite pravila imenovanja, Puppet preprosto ne bo našel opisa virov, kar bo povzročilo napako pri prevajanju.

Pravila so preprosta:

  • Vsi viri v modulu morajo biti v imenskem prostoru modula. Če se modul imenuje foo, potem se morajo vsi viri v njem imenovati foo::, ali preprosto foo.
  • Vir z imenom modula mora biti v datoteki init.pp.
  • Za ostale vire je shema imenovanja datotek naslednja:
    • predpona z imenom modula se odstrani
    • vse dvojne dvotečke, če obstajajo, se nadomestijo z obratnimi poševnicami
    • se doda razširitev .pp

Prikazal bom na primeru. Predpostavimo, da pišem modul nginx. V njem so naslednji viri:

  • razred nginx opisan v manifestu init.pp;
  • razred nginx::service opisan v manifestu service.pp;
  • definiranje nginx::server opisan v manifestu server.pp;
  • definiranje nginx::server::location opisan v manifestu server/location.pp.

Predloge

Vsekakor že sami veste, kaj so predloge, zato jih ne bom tu podrobno opisoval. Ampak za vsak slučaj bom pustil povezavo do Wikipedije.

Kako uporabiti predloge: vrednost predloge lahko razkrijemo s pomočjo funkcije template, ki ji posredujemo pot do predloge. Za vire tipa file uporabimo skupaj s parametrom content. Na primer, tako:

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

Pot v obliki / podrazumeva datoteko /modules//templates/.

Poleg tega obstaja funkcija inline_template — vanjo se posreduje besedilo predloge, ne ime datoteke.

Znotraj predlog lahko uporabimo vse spremenljivke Puppet v trenutnem območju vidnosti.

Puppet podpira predloge v formatu ERB in EPP:

Na kratko o ERB

Krmilne konstrukcije:

  • <%= ВЫРАЖЕНИЕ %> — vstaviti vrednost izraza
  • <% ВЫРАЖЕНИЕ %> — izračunati vrednost izraza (brez vstavljanja). Sem običajno spadajo pogojni operatorji (if), zanke (each).
  • <%# КОММЕНТАРИЙ %>

Izrazi v ERB so napisani v Ruby (pravzaprav je ERB vgrajen Ruby).

Za dostop do spremenljivk iz manifesta je treba dodati @ k imenu spremenljivke. Da odstranimo novo vrstico, ki se pojavi po krmilni konstrukciji, je treba uporabiti zaključni oznako -%>.

Primer uporabe predloge

Predpostavimo, da pišem modul za upravljanje z ZooKeeper. Razred, odgovoren za ustvarjanje konfiguracije, izgleda približno tako:

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'),
  }
}

In ustrezna predloga zoo.cfg.erb — tako:

0 -%>

server.=::;



dataDir=


=

Dejstva in vgrajene spremenljivke

Pogosto je specifični del konfiguracije odvisen od trenutnih dogodkov na nodi. Na primer, glede na to, katera različica Debiana je nameščena, je potrebno namestiti ustrezno verzijo paketa. Vse to lahko spremljate ročno, tako da v primeru sprememb nod prepisujete manifeste. Vendar to ni resen pristop, avtomatizacija je veliko boljša.

Za pridobitev informacij o nodah v Puppet obstaja mehanizem, imenovan dejstva. Dejstva so informacije o nodi, dostopne v manifestih kot običajne spremenljivke v globalnem prostoru imen. Na primer, ime gostitelja, različica operacijskega sistema, arhitektura procesorja, seznam uporabnikov, seznam omrežnih vmesnikov in njihovih naslovov ter še mnogo več. Dejstva so dostopna v manifestih in predlogah kot običajne spremenljivke.

Primer dela s fakti:

notify { "Delovanje OS ${facts['os']['name']} različica ${facts['os']['release']['full']}": }
# vir tipa notify preprosto izpiše sporočilo v dnevnik

Formalno povedano, dejstvo ima ime (niz) in vrednost (različne vrste so na voljo: nizi, seznami, slovarji). Obstaja nabor vgrajenih dejstev. Prav tako lahko pišete svoja. Zbiralci dejstev so opisani kot funkcije v Rubyju, ali kot izvršljivi datoteke. Dejstva so lahko predstavljena tudi v obliki besedilnih datotek z informacijami na nodah.

Med delovanjem Puppet agenta najprej kopira vse dostopne zbiralce dejstev s Puppet strežnika na nodo, nato jih zažene in pošlje zbrane dejstva na strežnik; šele nato strežnik začne s sestavljanjem direktorija.

Dejstva v obliki izvršljivih datotek

Taka dejstva se nahajajo v modulih v imeniku facts.d. Seveda morajo biti datoteke izvršljive. Ob zagonu morajo na standardni izhod izpisovati informacije bodisi v formatu YAML bodisi v formatu 'ključ=vrednost'.

Ne pozabite, da se dejstva širijo na vse node, ki jih upravlja Puppet strežnik, na katerega se uvaja vaša modul. Zato se v skriptu prepričajte, da obstajajo vsi potrebni programi in datoteke za delovanje vašega dejstva.

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

Dejstva v Rubyju

Taka dejstva se nahajajo v modulih v 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

Besedilna dejstva

Taka dejstva se nahajajo na nodah v imeniku /etc/facter/facts.d v starem Puppet-u ali /etc/puppetlabs/facts.d v novem Puppet-u.

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

Dostop do dejstev

Obratiti se činjenicama može na dva načina:

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

Najbolje je koristiti rečnik $facts, a još bolje je navesti globalni prostor imena ($::facts).

Evo potrebnog dela dokumentacije.

Ugrađene promenljive

Pored činjenica, postoje i neke promenljive, dostupne u globalnom prostoru imena.

  • trusted facts — promenljive koje dolaze iz sertifikata klijenta (pošto se sertifikat obično izdaje na puppet-serveru, agenta ne može jednostavno da promeni svoj sertifikat, stoga promenljive i "poverenik": naziv sertifikata, ime hosta i domena, ekstenzije iz sertifikata.
  • server facts — promenljive koje se odnose na informacije o serveru — verzija, ime, IP adresa servera, okruženje.
  • agent facts — promenljive koje dodaje direktno puppet-agent, a ne facter — naziv sertifikata, verzija agenta, verzija puppeta.
  • master variables — promenljive puppetmastera (sic!). Tu je otprilike isto kao i server facts, plus dostupne vrednosti konfiguracionih parametara.
  • compiler variables — promenljive kompajlera koje se razlikuju u svakoj oblasti vidljivosti: ime trenutnog modula i ime modula iz kojeg je učinjen poziv na trenutni objekat. Mogu se koristiti, na primer, kako bi se proverilo da vaši privatni klasi ne koriste direktno iz drugih modula.

Dodatak 1: kako sve ovo pokrenuti i debagovati?

U članku je bilo mnogo primera puppet-koda, ali nije rečeno kako se taj kod pokreće. Pa, ispravka je ovde.

Za rad Puppet-a dovoljno je imati agenta, ali za većinu slučajeva biće potreban i server.

Agent

Najmanje od pete verzije paketi puppet-agent iz službenog repozitorijuma Puppetlabs sadrže sve zavisnosti (ruby i odgovarajući gem-ovi), tako da nema problema sa instalacijom (govorim o Debian-based distribucijama — RPM-based distribucije ne koristimo).

U najjednostavnijem slučaju, da biste primenili puppet-konfiguraciju, dovoljno je pokrenuti agenta u beservernom režimu: pod uslovom da je puppet-kod kopiran na nodu, pokrećete 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

Najbolje je podići server i pokrenuti agente na nodovima u demonu - tada će svake pola sata primenjivati konfiguraciju preuzetu sa servera.

Možete imitirati model push rada - ući na zanimljivu nodu i pokrenuti sudo puppet agent -t. Ključ -t (--test) zapravo uključuje nekoliko opcija koje se mogu uključivati i pojedinačno. Među ovim opcijama su sledeće:

  • ne raditi u režimu demona (po defaultu agent se pokreće u režimu demona);
  • završiti rad nakon primene kataloga (po defaultu agent nastavlja rad i primenjuje konfiguraciju svake pola sata);
  • pisati detaljan log rada;
  • prikazivati promene u datotekama.

Agent ima režim rada bez promena - može se koristiti u slučaju kada niste sigurni da ste napisali ispravnu konfiguraciju i želite proveriti šta će agent promeniti tokom rada. Ovaj režim se aktivira parametrima --noop u komandnoj liniji: sudo puppet agent -t --noop.

Osim toga, možete uključiti debag log rada - u njemu puppet beleži sve radnje koje obavlja: o resursu koji trenutno obrađuje, o parametrima ovog resursa, o programima koje pokreće. Naravno, ovo je parametar --debug.

Server

Potpunu konfiguraciju puppets servera i deploy na njega koda u ovom članku neću razmatrati, reći ću samo da iz kutije dolazi sasvim funkcionalna verzija servera, koja ne zahteva dodatno podešavanje za rad u uslovima malog broja nodova (recimo, do sto). Veći broj nodova zahtevaće tuning - po defaultu puppetserver pokreće ne više od četiri radnika, za veću produktivnost treba povećati njihov broj i ne zaboraviti povećati limite memorije, inače će veći deo vremena server biti u fazi sakupljanja smeća.

Deploy koda - ako vam je potrebno brzo i jednostavno, pogledajte (na r10k)[https://github.com/puppetlabs/r10k], za male instalacije to bi trebalo da bude više nego dovoljno.

Dodatak 2: preporuke za pisanje koda

  1. Iznesite svu logiku u klase i definicije.
  2. Držite klase i definicije unutar modula, a ne u manifestima sa opisom nodova.
  3. Koristite činjenice.
  4. Ne radite if-ove po hostname-ima.
  5. Ne ustručavajte se dodavati parametre za klase i definicije — to je bolje od skrivene logike unutar tela klase/definicije.

A zašto preporučujem ovakav pristup — objasniću u sledećem članku.

Zaključek

Ovim završavamo sa uvodom. U sledećem članku ću govoriti o Hieri, ENC-u i PuppetDB-u.

Samo registrovani korisnici mogu učestvovati u anketi. Prijavite se, molim vas.

U stvari, materijala ima mnogo više — mogu napisati članke na sledeće teme, glasajte o čemu biste želeli da čitate:

  • 59,1%Napredne Puppet konstrukcije — neki sledeći nivo: petlje, mapiranje i drugi lambda izrazi, kolektori resursa, eksportovani resursi i međuhost interakcija kroz Puppet, tagovi, provajderi, apstraktni tipovi podataka.
  • 31,8%«Ja sam admin kod mame» ili kako smo u Avitu spojili nekoliko Puppet servera različitih verzija, a u principu deo o administraciji Puppet servera.
  • 81,8%Kako pišemo Puppet kod: alatna obloga, dokumentacija, testiranje, CI/CD.

Glasalo je 22 korisnika. 9 korisnika se uzdržalo.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster