Uvod v lutko

Puppet je sistem za upravljanje konfiguracije. Uporablja se za spravljanje gostiteljev v želeno stanje in vzdrževanje tega stanja.

Z Lutko sodelujem že več kot pet let. To besedilo je v bistvu prevedena in preurejena zbirka ključnih točk iz uradne dokumentacije, ki bo začetnikom omogočila hitro razumevanje bistva Lutke.

Uvod v lutko

Osnovni podatki

Operacijski sistem Puppet je odjemalec-strežnik, čeprav podpira tudi delovanje brez strežnika z omejeno funkcionalnostjo.

Uporablja se vlečni model delovanja: odjemalci privzeto enkrat na pol ure kontaktirajo strežnik za konfiguracijo in jo uporabijo. Če ste delali z Ansible, potem uporabljajo drugačen potisni model: skrbnik sproži postopek uporabe konfiguracije, stranke same ne bodo uporabile ničesar.

Med omrežno komunikacijo se uporablja dvosmerno šifriranje TLS: strežnik in odjemalec imata svoje zasebne ključe in ustrezne certifikate. Običajno strežnik izda potrdila za odjemalce, vendar je načeloma možno uporabiti zunanjo CA.

Uvod v manifeste

V lutkovni terminologiji na lutkovni strežnik povezati vozlišča (vozlišča). Konfiguracija za vozlišča je zapisana v manifestih v posebnem programskem jeziku – Puppet DSL.

Puppet DSL je deklarativni jezik. Opisuje želeno stanje vozlišča v obliki deklaracij posameznih virov, na primer:

  • Datoteka obstaja in ima specifično vsebino.
  • Paket je nameščen.
  • Storitev se je začela.

Sredstva so lahko med seboj povezana:

  • Obstajajo odvisnosti, ki vplivajo na vrstni red uporabe virov.
    Na primer, "najprej namestite paket, nato uredite konfiguracijsko datoteko in nato zaženite storitev."
  • Obstajajo obvestila - če se je vir spremenil, pošlje obvestila virom, ki so nanj naročeni.
    Na primer, če se konfiguracijska datoteka spremeni, lahko samodejno znova zaženete storitev.

Poleg tega ima Puppet DSL funkcije in spremenljivke ter pogojne stavke in izbirnike. Podprti so tudi različni mehanizmi predlog - EPP in ERB.

Puppet je napisan v Rubyju, zato je veliko konstruktov in izrazov vzetih od tam. Ruby vam omogoča razširitev Puppet - dodajte kompleksno logiko, nove vrste virov, funkcije.

Medtem ko se Puppet izvaja, se manifesti za vsako specifično vozlišče na strežniku prevedejo v imenik. Imenik je seznam virov in njihovih odnosov po izračunu vrednosti funkcij, spremenljivk in razširitve pogojnih stavkov.

Sintaksa in slog kode

Tukaj so deli uradne dokumentacije, ki vam bodo pomagali razumeti sintakso, če podani primeri ne zadostujejo:

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

Zamiki in prelomi vrstic niso obvezni del manifesta, vendar je priporočljiv vodnik. Povzetek:

  • Zamiki z dvema presledkoma, tabulatorji se ne uporabljajo.
  • Zavit oklepaj je ločen s presledkom, dvopičje pa ni ločeno s presledkom.
  • Vejice za vsakim parametrom, vključno z zadnjim. Vsak parameter je v ločeni vrstici. Izjema je primer brez parametrov in en parameter: lahko pišete v eni vrstici in brez vejice (tj. resource { 'title': } и resource { 'title': param => value }).
  • Puščice na parametrih morajo biti na isti ravni.
  • Pred njimi so napisane puščice razmerij med viri.

Lokacija datotek na pappetserverju

Za nadaljnjo razlago bom predstavil koncept "korenskega imenika". Korenski imenik je imenik, ki vsebuje konfiguracijo lutke za določeno vozlišče.

Korenski imenik se razlikuje glede na različico programa Puppet in uporabljena okolja. Okolja so neodvisni nizi konfiguracije, ki so shranjeni v ločenih imenikih. Običajno se uporablja v kombinaciji z git, v tem primeru so okolja ustvarjena iz vej git. V skladu s tem se vsako vozlišče nahaja v enem ali drugem okolju. To je mogoče konfigurirati na samem vozlišču ali v ENC, o čemer bom govoril v naslednjem članku.

  • V tretji različici (»old Puppet«) je bil osnovni imenik /etc/puppet. Uporaba okolij ni obvezna - na primer, ne uporabljamo jih pri stari Puppet. Če se uporabljajo okolja, so običajno shranjena v njih /etc/puppet/environments, bo korenski imenik imenik okolja. Če okolja niso uporabljena, bo korenski imenik osnovni imenik.
  • Od četrte različice (»nova lutka«) je uporaba okolij postala obvezna, osnovni imenik pa je bil premaknjen v /etc/puppetlabs/code. V skladu s tem so okolja shranjena v /etc/puppetlabs/code/environments, korenski imenik je imenik okolja.

V korenskem imeniku mora obstajati podimenik manifests, ki vsebuje enega ali več manifestov, ki opisujejo vozlišča. Poleg tega bi moral obstajati podimenik modules, ki vsebuje module. Malo kasneje vam bom povedal, kaj so moduli. Poleg tega ima lahko stara lutka tudi podimenik files, ki vsebuje različne datoteke, ki jih kopiramo v vozlišča. V novi Puppet so vse datoteke postavljene v module.

Datoteke manifesta imajo pripono .pp.

Par bojnih primerov

Opis vozlišča in vira na njem

Na vozlišču server1.testdomain treba je ustvariti datoteko /etc/issue z vsebino Debian GNU/Linux n l. Datoteka mora biti v lasti uporabnika in skupine root, morajo biti pravice dostopa 644.

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

Razmerja med viri na vozlišču

Na vozlišču server2.testdomain nginx mora delovati s predhodno pripravljeno konfiguracijo.

Razčlenimo problem:

  • Paket je treba namestiti nginx.
  • Potrebno je, da se konfiguracijske datoteke prekopirajo s strežnika.
  • Storitev mora delovati nginx.
  • Če je konfiguracija posodobljena, je treba storitev znova zagnati.

Napiš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 bo to delovalo, potrebujete približno naslednjo lokacijo datoteke na lutkovnem strežniku:

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

Vrste virov

Celoten seznam podprtih vrst virov najdete tukaj v dokumentaciji, tukaj bom opisal pet osnovnih tipov, ki v moji praksi zadostujejo za rešitev večine težav.

datoteka

Upravlja datoteke, imenike, simbolne povezave, njihovo vsebino in pravice dostopa.

Parametri:

  • ime vira — pot do datoteke (neobvezno)
  • pot — pot do datoteke (če ni navedena v imenu)
  • zagotovitev - vrsta datoteke:
    • absent - izbrisati datoteko
    • present — obstajati mora datoteka katere koli vrste (če datoteke ni, bo ustvarjena običajna datoteka)
    • file - običajna datoteka
    • directory - imenik
    • link - simbolna povezava
  • vsebina — vsebina datoteke (primerna samo za običajne datoteke, ni je mogoče uporabljati skupaj z vir ali ciljna)
  • vir — povezava do poti, s katere želite kopirati vsebino datoteke (ni mogoče uporabiti skupaj z vsebina ali ciljna). Lahko se določi kot URI s shemo puppet: (takrat bodo uporabljene datoteke iz lutkovnega strežnika) in s shemo http: (Upam, da je jasno, kaj se bo zgodilo v tem primeru) in celo z diagramom file: ali kot absolutna pot brez sheme (takrat bo uporabljena datoteka iz lokalnega FS na vozlišču)
  • ciljna — kam naj kaže simbolna povezava (ni mogoče uporabiti skupaj z vsebina ali vir)
  • Lastnik — uporabnik, ki bi moral biti lastnik datoteke
  • skupina — skupina, ki naj ji pripada datoteka
  • Način — dovoljenja za datoteke (kot niz)
  • ponoviti - omogoča rekurzivno obdelavo imenikov
  • purge - omogoča brisanje datotek, ki niso opisane v Puppet
  • moč - omogoča brisanje imenikov, ki niso opisani v Puppet

paket

Namesti in odstrani pakete. Sposoben obravnavati obvestila - znova namesti paket, če je parameter naveden reinstall_on_refresh.

Parametri:

  • ime vira — ime paketa (neobvezno)
  • Ime — ime paketa (če ni navedeno v imenu)
  • Ponudnik — upravitelj paketov za uporabo
  • zagotovitev — želeno stanje paketa:
    • present, installed - katera koli nameščena različica
    • latest - nameščena zadnja različica
    • absent - izbrisano (apt-get remove)
    • purged — izbrisano skupaj s konfiguracijskimi datotekami (apt-get purge)
    • held - verzija paketa je zaklenjena (apt-mark hold)
    • любая другая строка — navedena različica je nameščena
  • reinstall_on_refresh - če true, nato pa bo po prejemu obvestila paket znova nameščen. Uporabno za distribucije, ki temeljijo na viru, kjer je ob spreminjanju parametrov gradnje morda potrebna vnovična izgradnja paketov. Privzeto false.

Storitev

Upravlja storitve. Sposoben obdelave obvestil - znova zažene storitev.

Parametri:

  • ime vira — storitev, ki jo je treba upravljati (neobvezno)
  • Ime — storitev, ki jo je treba upravljati (če ni navedeno v imenu)
  • zagotovitev — želeno stanje storitve:
    • running - lansiran
    • stopped - ustavil
  • omogočajo — nadzoruje možnost zagona storitve:
    • true — samodejni zagon je omogočen (systemctl enable)
    • mask - prikrito (systemctl mask)
    • false — samodejni zagon je onemogočen (systemctl disable)
  • restart - ukaz za ponovni zagon storitve
  • Status — ukaz za preverjanje stanja storitve
  • hasrestart — navedite, ali začetni skript storitve podpira ponovni zagon. če false in parameter je določen restart — uporabljena je vrednost tega parametra. če false in parameter restart ni določeno - storitev se zaustavi in ​​začne znova zagnati (vendar systemd uporablja ukaz systemctl restart).
  • hasstatus — navedite, ali začetni skript storitve podpira ukaz status. Če false, potem se uporabi vrednost parametra Status. Privzeto true.

exec

Izvaja zunanje ukaze. Če ne določite parametrov ustvari, samo če, če ali refreshonly, bo ukaz zagnan vsakič, ko se zažene Puppet. Sposoben obdelave obvestil - zažene ukaz.

Parametri:

  • ime vira — ukaz za izvedbo (neobvezno)
  • ukaz — ukaz, ki naj se izvrši (če ni naveden v imenu)
  • pot — poti za iskanje izvršljive datoteke
  • samo če — če se ukaz, določen v tem parametru, zaključi z ničelno povratno kodo, se izvede glavni ukaz
  • če — če se ukaz, naveden v tem parametru, zaključi z neničelno povratno kodo, bo izveden glavni ukaz
  • ustvari — če datoteka, podana v tem parametru, ne obstaja, bo izveden glavni ukaz
  • refreshonly - če true, potem bo ukaz zagnan le, ko ta izvajalec prejme obvestilo iz drugih virov
  • cwd — imenik, iz katerega zaženete ukaz
  • uporabnik — uporabnik, od katerega želite zagnati ukaz
  • Ponudnik - kako zagnati ukaz:
    • posix — podrejeni proces je preprosto ustvarjen, obvezno navedite pot
    • shell - ukaz se zažene v lupini /bin/sh, morda ni naveden pot, lahko uporabite globbing, cevi in ​​druge funkcije lupine. Običajno samodejno zazna, če obstajajo kakršni koli posebni znaki (|, ;, &&, || itd.).

cron

Nadzoruje cronjobs.

Parametri:

  • ime vira - samo neke vrste identifikator
  • zagotovitev — stanje crownjob:
    • present - ustvari, če ne obstaja
    • absent - izbrišite, če obstaja
  • ukaz - kateri ukaz zagnati
  • okolje — v katerem okolju zagnati ukaz (seznam spremenljivk okolja in njihovih vrednosti prek =)
  • uporabnik — od katerega uporabnika zagnati ukaz
  • min, uro, dan v tednu, mesec, mesečni dan — kdaj zagnati cron. Če kateri od teh atributov ni določen, bo njegova vrednost v crontabu *.

V Puppet 6.0 cron kot da odstraniti iz škatle v lutkovnem strežniku, zato na splošnem spletnem mestu ni dokumentacije. Ampak on je v škatli v lutkovnem agentu, zato ga ni treba posebej namestiti. Ogledate si lahko dokumentacijo zanj v dokumentaciji za peto različico PuppetAli na GitHubu.

O virih na splošno

Zahteve za edinstvenost vira

Najpogostejša napaka, s katero se srečujemo, je Podvojena izjava. Do te napake pride, ko se v imeniku pojavita dva ali več virov iste vrste z istim imenom.

Zato bom še enkrat napisal: manifesti za isto vozlišče ne smejo vsebovati virov iste vrste z istim naslovom!

Včasih je treba namestiti pakete z istim imenom, vendar z različnimi upravitelji paketov. V tem primeru morate uporabiti parameter nameda se izognete napaki:

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

Druge vrste virov imajo podobne možnosti za preprečevanje podvajanja − name у Storitev, command у exec, in tako naprej.

Metaparametri

Vsaka vrsta vira ima nekaj posebnih parametrov, ne glede na svojo naravo.

Celoten seznam meta parametrov v dokumentaciji Puppet.

Ožji seznam:

  • zahteva — ta parameter označuje, od katerih virov je ta vir odvisen.
  • pred - Ta parameter določa, kateri viri so odvisni od tega vira.
  • naročiti — ta parameter določa, iz katerih virov ta vir prejema obvestila.
  • obvestiti — Ta parameter določa, kateri viri prejemajo obvestila od tega vira.

Vsi navedeni metaparametri sprejmejo eno samo povezavo do vira ali niz povezav v oglatih oklepajih.

Povezave do virov

Povezava do vira je preprosto omemba vira. Uporabljajo se predvsem za označevanje odvisnosti. Sklicevanje na neobstoječi vir bo povzročilo napako pri prevajanju.

Sintaksa povezave je naslednja: tip vira z veliko začetnico (če ime tipa vsebuje dvojno dvopičje, je vsak del imena med dvopičji napisan z veliko začetnico), nato ime vira v oglatih oklepajih (primer imena se ne spremeni!). Presledkov ne sme biti, oglati oklepaji so zapisani 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 smo že omenili, so preproste odvisnosti med viri prehodne. Mimogrede, bodite previdni pri dodajanju odvisnosti - ustvarite lahko ciklične odvisnosti, kar bo povzročilo napako pri prevajanju.

Za razliko od odvisnosti obvestila niso prehodna. Za obvestila veljajo naslednja pravila:

  • Če vir prejme obvestilo, se posodobi. Dejanja posodobitve so odvisna od vrste vira − exec izvaja ukaz, Storitev znova zažene storitev, paket znova namesti paket. Če vir nima definiranega dejanja posodobitve, se ne zgodi nič.
  • Med enim zagonom Puppet se vir posodobi največ enkrat. To je mogoče, ker obvestila vključujejo odvisnosti in graf odvisnosti ne vsebuje ciklov.
  • Če Puppet spremeni stanje vira, vir pošlje obvestila vsem virom, ki so nanj naročeni.
  • Če je vir posodobljen, pošlje obvestila vsem virom, ki so nanj naročeni.

Ravnanje z nedoločenimi parametri

Praviloma velja, da če neki parameter vira nima privzete vrednosti in ta parameter ni podan v manifestu, Puppet ne bo spremenil te lastnosti za ustrezen vir na vozlišču. Na primer, če je vir vrste datoteka parameter ni določen owner, potem Puppet ne bo spremenil lastnika ustrezne datoteke.

Uvod v razrede, spremenljivke in definicije

Recimo, da imamo več vozlišč, ki imajo enak del konfiguracije, vendar obstajajo tudi razlike - sicer bi lahko vse opisali v enem bloku node {}. Seveda lahko preprosto kopirate enake dele konfiguracije, vendar je to na splošno slaba rešitev - konfiguracija raste in če spremenite splošni del konfiguracije, boste morali urejati isto stvar na več mestih. Hkrati je enostavno narediti napako in na splošno je bilo načelo DRY (ne ponavljaj se) izumljeno z razlogom.

Za rešitev tega problema obstaja takšen dizajn kot Razred.

Классы

Razred je poimenovani blok kode kolebnice. Za ponovno uporabo kode so potrebni razredi.

Najprej je treba razred opisati. Sam opis nikjer ne dodaja virov. Razred je opisan v manifestih:

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

Po tem lahko razred uporabite:

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

Primer iz prejšnje naloge - premaknimo namestitev in konfiguracijo nginxa v razred:

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
}

Spremenljivke

Razred iz prejšnjega primera sploh ni prilagodljiv, ker vedno prinaša isto konfiguracijo nginx. Naredimo pot do konfiguracijske spremenljivke, nato pa lahko ta razred uporabimo za namestitev nginx s katero koli konfiguracijo.

To je mogoče storiti z uporabo spremenljivk.

Pozor: spremenljivke v Puppet so nespremenljive!

Poleg tega je do spremenljivke mogoče dostopati šele, ko je bila deklarirana, 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}"

Lutka ima imenski prostori, spremenljivke pa imajo v skladu s tem območje vidnosti: Spremenljivko z istim imenom je mogoče definirati v različnih imenskih prostorih. Pri razrešitvi vrednosti spremenljivke se spremenljivka išče v trenutnem imenskem prostoru, nato v obdajajočem imenskem prostoru in tako naprej.

Primeri imenskega prostora:

  • globalno - tja gredo spremenljivke zunaj opisa razreda ali vozlišča;
  • imenski prostor vozlišča v opisu vozlišča;
  • imenski prostor razreda v opisu razreda.

Da bi se izognili dvoumnosti pri dostopu do spremenljivke, lahko podate imenski prostor v imenu spremenljivke:

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

Strinjajmo se, da pot do konfiguracije nginx leži v spremenljivki $nginx_conf_source. Potem bo razred izgledal takole:

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
}

Vendar je podani primer slab, ker obstaja neko »skrivno znanje«, da se nekje znotraj razreda uporablja spremenljivka s takim in tem imenom. Veliko bolj pravilno je, da je to znanje splošno - razredi imajo lahko parametre.

Parametri razreda so spremenljivke v imenskem prostoru razreda, podane so v glavi razreda in se lahko uporabljajo kot navadne spremenljivke v telesu razreda. Vrednosti parametrov so določene pri uporabi razreda v manifestu.

Parameter lahko nastavite na privzeto vrednost. Če parameter nima privzete vrednosti in vrednost ni nastavljena, ko je uporabljena, bo to povzročilo napako pri prevajanju.

Parametrirajmo razred iz zgornjega primera in dodamo dva parametra: prvi, obvezen, je pot do konfiguracije, drugi, neobvezen, pa je ime paketa z nginxom (v Debianu so na primer 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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

V Puppet so spremenljivke tipkane. Jejte veliko tipov podatkov. Tipi podatkov se običajno uporabljajo za preverjanje vrednosti parametrov, posredovanih razredom in definicijam. Če se posredovani parameter ne ujema s podanim tipom, 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: vključi ime razreda proti razredu {'classname':}

Vsak razred je vir vrste razred. Kot pri kateri koli drugi vrsti vira, na istem vozlišču ne moreta biti dva primerka istega razreda.

Če poskusite dvakrat dodati razred v isto vozlišče z uporabo class { 'classname':} (brez razlike, z različnimi ali enakimi parametri), bo prišlo do napake pri prevajanju. Če pa uporabite razred v slogu vira, lahko takoj izrecno nastavite vse njegove parametre v manifestu.

Vendar, če uporabljate include, potem lahko razred dodate tolikokrat, kot želite. Dejstvo je, da include je idempotentna funkcija, ki preveri, ali je bil razred dodan v imenik. Če razreda ni v imeniku, ga doda, če pa že obstaja, ne naredi ničesar. Toda v primeru uporabe include Parametrov razreda ne morete nastaviti med deklaracijo razreda - vsi zahtevani parametri morajo biti nastavljeni v zunanjem viru podatkov - Hiera ali ENC. O njih bomo govorili v naslednjem članku.

Določa

Kot je bilo rečeno v prejšnjem bloku, isti razred ne more biti prisoten na vozlišču več kot enkrat. Vendar pa morate v nekaterih primerih imeti možnost uporabe istega bloka kode z različnimi parametri na istem vozlišču. Z drugimi besedami, obstaja potreba po lastni vrsti vira.

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

  1. Namestite paket s tem modulom.
  2. Ustvarimo konfiguracijsko datoteko za ta modul.
  3. Ustvarimo simbolno povezavo do konfiguracije za php-fpm.
  4. Ustvarimo simbolno povezavo do konfiguracije za php cli.

V takšnih primerih je zasnova, kot je npr opredeliti (definiraj, definiran tip, definiran tip vira). Define je podoben razredu, vendar obstajajo razlike: prvič, vsaka Define je vrsta vira, ne vir; drugič, vsaka definicija ima implicitni parameter $title, kamor gre ime vira, ko je deklarirano. Tako kot pri razredih je treba najprej opisati definicijo, nato jo lahko uporabimo.

Poenostavljen primer z 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' }
}

Napako Duplicate declaration najlažje odkrijete v Define. To se zgodi, če ima definicija vir s konstantnim imenom in sta na nekem vozlišču dva ali več primerkov te definicije.

Pred tem se je enostavno zaščititi: vsi viri znotraj definicije morajo imeti ime, odvisno od $title. Alternativa je idempotentno dodajanje virov, v najpreprostejšem primeru je dovolj, da vire, ki so skupni vsem primerkom definicije, premaknete v ločen razred in ta razred vključite v definicijo - funkcijo. include idempotenten.

Obstajajo tudi drugi načini za doseganje idempotence pri dodajanju virov, in sicer z uporabo funkcij defined и ensure_resources, a o tem vam bom povedal v naslednji epizodi.

Odvisnosti in obvestila za razrede in definicije

Razredi in definicije dodajajo naslednja pravila za obravnavanje odvisnosti in obvestil:

  • odvisnost od razreda/define doda odvisnosti od vseh virov razreda/define;
  • odvisnost razreda/definiranja doda odvisnosti vsem virom razreda/definiranja;
  • obvestilo razreda/define obvesti vse vire razreda/define;
  • class/define subscription se naroči na vse vire class/define.

Pogojni stavki in izbirniki

Dokumentacija tukaj.

if

Tukaj je preprosto:

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

če

razen če je če obratno: blok kode bo izveden, če je izraz napačen.

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

primeru

Tudi tukaj ni nič zapletenega. Kot vrednosti lahko uporabite običajne vrednosti (nize, številke itd.), regularne izraze in vrste podatkov.

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

Selektorji

Izbirnik je jezikovni konstrukt, podoben case, vendar namesto izvedbe bloka kode vrne vrednost.

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

Moduli

Ko je konfiguracija majhna, jo je mogoče preprosto hraniti v enem manifestu. Toda več kot opisujemo konfiguracij, več razredov in vozlišč je v manifestu, raste in postane neprijetno za delo.

Poleg tega obstaja problem ponovne uporabe kode – ko je vsa koda v enem manifestu, je to kodo težko deliti z drugimi. Za rešitev teh dveh težav ima Puppet entiteto, imenovano moduli.

Moduli - to so nizi razredov, definicij in drugih lutkovnih entitet, ki so nameščene v ločenem imeniku. Z drugimi besedami, modul je neodvisen del lutkovne logike. Na primer, lahko obstaja modul za delo z nginxom, ki bo vseboval tisto in samo tisto, kar je potrebno za delo z nginxom, ali pa obstaja modul za delo s PHP itd.

Moduli so verzionirani, podprte pa so tudi medsebojne odvisnosti modulov. Obstaja odprto skladišče modulov - Kovačnica lutk.

Na lutkovnem strežniku se moduli nahajajo v podimeniku modulov korenskega imenika. Znotraj vsakega modula je standardna shema imenikov - manifesti, datoteke, predloge, lib itd.

Struktura datoteke v modulu

Koren modula lahko vsebuje naslednje imenike z opisnimi imeni:

  • manifests - vsebuje manifeste
  • files - vsebuje datoteke
  • templates - vsebuje predloge
  • lib — vsebuje kodo Ruby

To ni popoln seznam imenikov in datotek, vendar je zaenkrat dovolj za ta članek.

Imena virov in imena datotek v modulu

Dokumentacija tukaj.

Virov (razredov, definicij) v modulu ni mogoče poimenovati, kakor želite. Poleg tega obstaja neposredna ujemanje med imenom vira in imenom datoteke, v kateri bo Puppet iskal opis tega vira. Če prekršite pravila poimenovanja, potem Puppet preprosto ne bo našel opisa vira in dobili boste napako pri prevajanju.

Pravila so preprosta:

  • Vsi viri v modulu morajo biti v imenskem prostoru modula. Če je modul poklican foo, potem morajo biti vsi viri v njem poimenovani foo::<anything>, ali samo foo.
  • V datoteki mora biti vir z imenom modula init.pp.
  • Za druge vire je shema poimenovanja datotek naslednja:
    • predpona z imenom modula je zavržena
    • vsa dvojna dvopičja, če obstajajo, se nadomestijo s poševnicami
    • je dodana razširitev .pp

Pokazal bom s primerom. Recimo, da pišem modul nginx. Vsebuje naslednje vire:

  • Razred nginx opisano v manifestu init.pp;
  • Razred nginx::service opisano v manifestu service.pp;
  • opredeliti nginx::server opisano v manifestu server.pp;
  • opredeliti nginx::server::location opisano v manifestu server/location.pp.

Predloge

Zagotovo sami veste, kaj so predloge, tukaj jih ne bom podrobno opisoval. Ampak za vsak slučaj ga bom pustil povezava do Wikipedije.

Kako uporabljati predloge: Pomen predloge je mogoče razširiti s funkcijo template, ki mu je posredovana pot do predloge. Za vire vrste datoteka uporablja skupaj s parametrom content. Na primer takole:

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

Pogled na pot <modulename>/<filename> pomeni datoteko <rootdir>/modules/<modulename>/templates/<filename>.

Poleg tega obstaja funkcija inline_template — kot vhod prejme besedilo predloge, ne imena datoteke.

Znotraj predlog lahko uporabite vse spremenljivke Puppet v trenutnem obsegu.

Puppet podpira predloge v formatu ERB in EPP:

Na kratko o ERB

Nadzorne strukture:

  • <%= ВЫРАЖЕНИЕ %> — vstavite vrednost izraza
  • <% ВЫРАЖЕНИЕ %> — izračuna vrednost izraza (brez vstavljanja). Pogojni stavki (if) in zanke (each) so običajno sem.
  • <%# КОММЕНТАРИЙ %>

Izrazi v ERB so napisani v Rubyju (ERB je pravzaprav vdelani Ruby).

Če želite dostopati do spremenljivk iz manifesta, morate dodati @ na ime spremenljivke. Če želite odstraniti prelom vrstice, ki se pojavi za kontrolnim konstruktom, morate uporabiti zaključno oznako -%>.

Primer uporabe predloge

Recimo, da pišem modul za nadzor ZooKeeperja. Razred, odgovoren za ustvarjanje konfiguracije, izgleda nekako takole:

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 pripadajoča predloga zoo.cfg.erb - Torej:

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

Dejstva in vgrajene spremenljivke

Pogosto je določen del konfiguracije odvisen od tega, kaj se trenutno dogaja na vozlišču. Na primer, odvisno od izdaje Debiana morate namestiti eno ali drugo različico paketa. Vse to lahko spremljate ročno in prepisujete manifeste, če se vozlišča spremenijo. Vendar to ni resen pristop, avtomatizacija je veliko boljša.

Za pridobivanje informacij o vozliščih ima Puppet mehanizem, imenovan dejstva. dejstva - to so informacije o vozlišču, ki so na voljo v manifestih v obliki navadnih spremenljivk v globalnem imenskem prostoru. Na primer ime gostitelja, različica operacijskega sistema, arhitektura procesorja, seznam uporabnikov, seznam omrežnih vmesnikov in njihovih naslovov ter še veliko, veliko več. Dejstva so na voljo v manifestih in predlogah kot redne spremenljivke.

Primer dela z dejstvi:

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

Formalno gledano ima dejstvo ime (niz) in vrednost (na voljo so različni tipi: nizi, nizi, slovarji). Jejte niz vgrajenih dejstev. Lahko tudi napišete svoje. Opisani so zbiralci dejstev kot funkcije v Rubyjubodisi kot izvršljive datoteke. Dejstva lahko predstavimo tudi v obrazcu besedilne datoteke s podatki na vozliščih.

Med delovanjem lutkovni agent najprej prekopira vse razpoložljive zbiralnike dejstev iz pappetserverja v vozlišče, nato jih zažene in pošlje zbrana dejstva na strežnik; Po tem začne strežnik sestavljati katalog.

Dejstva v obliki izvršljivih datotek

Takšna dejstva so umeščena v module v imeniku facts.d. Seveda morajo biti datoteke izvršljive. Ko se zaženejo, morajo izpisati informacije v standardnem izhodu v formatu YAML ali ključ=vrednost.

Ne pozabite, da se dejstva nanašajo na vsa vozlišča, ki jih nadzoruje stolpčni strežnik, v katerega je nameščen vaš modul. Zato v skriptu preverite, ali ima sistem vse programe in datoteke, ki jih potrebujete za delovanje.

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

Ruby dejstva

Takšna dejstva so umeščena v module 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

Takšna dejstva so postavljena na vozlišča v imeniku /etc/facter/facts.d v starem Puppet oz /etc/puppetlabs/facts.d v novi Lutki.

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

Priti do dejstev

Do dejstev lahko pristopimo na dva načina:

  • skozi slovar $facts: $facts['fqdn'];
  • z uporabo imena dejstva kot imena spremenljivke: $fqdn.

Najbolje je uporabiti slovar $facts, ali še bolje, označite globalni imenski prostor ($::facts).

Tukaj je ustrezen del dokumentacije.

Vgrajene spremenljivke

Poleg dejstev obstaja tudi nekatere spremenljivke, ki je na voljo v globalnem imenskem prostoru.

  • zaupanja vredna dejstva — spremenljivke, ki so vzete iz odjemalčevega potrdila (ker je potrdilo običajno izdano na poppet strežniku, agent ne more kar vzeti in spremeniti njegovega potrdila, zato so spremenljivke »zaupane«): ime potrdila, ime gostitelj in domena, končnice iz certifikata.
  • dejstva o strežniku — spremenljivke, povezane z informacijami o strežniku — različica, ime, naslov IP strežnika, okolje.
  • agent dejstva — spremenljivke, ki jih je neposredno dodal lutkovni agent in ne faktor — ime potrdila, različica agenta, različica lutke.
  • glavne spremenljivke - Spremenljivke Pappetmaster (sic!). Je približno enako kot v dejstva o strežniku, na voljo pa so tudi vrednosti konfiguracijskih parametrov.
  • spremenljivke prevajalnika — spremenljivke prevajalnika, ki se razlikujejo v vsakem obsegu: ime trenutnega modula in ime modula, v katerem je bil dostopan trenutni objekt. Uporabljajo se lahko na primer za preverjanje, ali se vaši zasebni razredi ne uporabljajo neposredno iz drugih modulov.

Dodatek 1: kako vse to zagnati in odpraviti napake?

Članek je vseboval veliko primerov lutkovne kode, vendar nam sploh ni povedal, kako zagnati to kodo. No, se popravljam.

Za zagon Puppet je dovolj agent, vendar boste v večini primerov potrebovali tudi strežnik.

Agent

Vsaj od različice XNUMX, paketi lutkovnega agenta iz uradno skladišče Puppetlabs vsebujejo vse odvisnosti (ruby in ustrezne dragulje), tako da ni težav z namestitvijo (govorim o distribucijah, ki temeljijo na Debianu - ne uporabljamo distribucij, ki temeljijo na RPM).

V najpreprostejšem primeru je za uporabo lutkovne konfiguracije dovolj, da zaženete agenta v brezstrežniškem načinu: pod pogojem, da je lutkovna koda kopirana v vozlišče, zaženite 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

Seveda je bolje, da nastavite strežnik in zaženete agente na vozliščih v demonskem načinu - nato bodo enkrat na pol ure uporabili konfiguracijo, preneseno s strežnika.

Lahko posnemate potisni model dela - pojdite na vozlišče, ki vas zanima, in začnite sudo puppet agent -t. Ključ -t (--test) dejansko vključuje več možnosti, ki jih je mogoče omogočiti posamezno. Te možnosti vključujejo naslednje:

  • ne zaženite v demonskem načinu (privzeto se agent zažene v demonskem načinu);
  • izklop po uporabi kataloga (privzeto bo agent nadaljeval z delom in uporabil konfiguracijo enkrat na pol ure);
  • napisati podroben dnevnik dela;
  • prikaz sprememb v datotekah.

Agent ima način delovanja brez sprememb – uporabite ga lahko, ko niste prepričani, da ste napisali pravilno konfiguracijo in želite preveriti, kaj točno bo agent spremenil med delovanjem. Ta način je omogočen s parametrom --noop v ukazni vrstici: sudo puppet agent -t --noop.

Poleg tega lahko omogočite dnevnik odpravljanja napak dela - v njem lutka piše o vseh dejanjih, ki jih izvaja: o viru, ki ga trenutno obdeluje, o parametrih tega vira, o tem, katere programe zažene. Seveda je to parameter --debug.

Strežnik

V tem članku ne bom obravnaval popolne nastavitve strežnika pappetserver in uvajanja kode nanj; rekel bom le, da je takoj na voljo popolnoma funkcionalna različica strežnika, ki ne potrebuje dodatne konfiguracije za delo z majhnim številom strežnikov. vozlišča (recimo do sto). Večje število vozlišč bo zahtevalo nastavitev - lutkovni strežnik privzeto zažene največ štiri delavce, za večjo zmogljivost morate povečati njihovo število in ne pozabite povečati omejitev pomnilnika, sicer bo strežnik večino časa zbiral smeti.

Namestitev kode - če jo potrebujete hitro in enostavno, poglejte (na r10k) [https://github.com/puppetlabs/r10k], za majhne inštalacije bi moralo biti čisto dovolj.

Dodatek 2: Smernice za kodiranje

  1. Vso logiko postavite v razrede in definicije.
  2. Razrede in definicije hranite v modulih, ne v manifestih, ki opisujejo vozlišča.
  3. Uporabite dejstva.
  4. Ne ustvarjajte if-jev na podlagi imen gostiteljev.
  5. Lahko dodajate parametre za razrede in definicije - to je bolje kot implicitna logika, skrita v telesu razreda/definicije.

V naslednjem članku bom pojasnil, zakaj priporočam to.

Zaključek

Končajmo z uvodom. V naslednjem članku vam bom povedal o Hieri, ENC in PuppetDB.

V anketi lahko sodelujejo samo registrirani uporabniki. Prijaviti se, prosim.

Pravzaprav je materiala veliko več - lahko pišem članke o naslednjih temah, glasujem o tem, o čemer bi vas zanimalo branje:

  • 59,1%Napredni lutkovni konstrukti – nekaj sranja naslednje ravni: zanke, preslikave in drugi lambda izrazi, zbiralniki virov, izvoženi viri in komunikacija med gostitelji prek lutke, oznake, ponudniki, abstraktni tipi podatkov.13
  • 31,8%“Jaz sem mamin admin” ali kako smo se v Avitu spoprijateljili z več poppet strežniki različnih različic in načeloma tisti del o administraciji poppet strežnika.7
  • 81,8%Kako pišemo lutkovno kodo: instrumentacija, dokumentacija, testiranje, CI/CD.18

Glasovalo je 22 uporabnikov. 9 uporabnikov se je vzdržalo.

Vir: www.habr.com