Introduksjon til Puppet

Puppet er et konfigurasjonsstyringssystem. Den brukes til å bringe verter til ønsket tilstand og opprettholde denne tilstanden.

Jeg har jobbet med Puppet i over fem år nå. Denne teksten er i hovedsak en oversatt og omorganisert samling av nøkkelpunkter fra den offisielle dokumentasjonen, som lar nybegynnere raskt forstå essensen av Puppet.

Introduksjon til Puppet

Grunnleggende informasjon

Puppets operativsystem er klient-server, selv om det også støtter serverløs drift med begrenset funksjonalitet.

En pull-operasjonsmodell brukes: som standard, en gang hver halvtime, kontakter klienter serveren for en konfigurasjon og bruker den. Hvis du har jobbet med Ansible, bruker de en annen push-modell: administratoren starter prosessen med å bruke konfigurasjonen, klientene selv vil ikke bruke noe.

Under nettverkskommunikasjon brukes toveis TLS-kryptering: serveren og klienten har sine egne private nøkler og tilhørende sertifikater. Vanligvis utsteder serveren sertifikater for klienter, men i prinsippet er det mulig å bruke en ekstern CA.

Introduksjon til manifester

I dukketerminologi til dukkeserveren koble noder (noder). Konfigurasjonen for nodene er skrevet i manifester i et spesielt programmeringsspråk - Puppet DSL.

Puppet DSL er et deklarativt språk. Den beskriver ønsket tilstand til noden i form av erklæringer om individuelle ressurser, for eksempel:

  • Filen eksisterer og den har spesifikt innhold.
  • Pakken er installert.
  • Tjenesten har startet.

Ressurser kan kobles sammen:

  • Det er avhengigheter, de påvirker rekkefølgen ressursene brukes i.
    For eksempel, "installer først pakken, rediger deretter konfigurasjonsfilen, og start deretter tjenesten."
  • Det er varsler - hvis en ressurs har endret seg, sender den varsler til ressursene som abonnerer på den.
    For eksempel, hvis konfigurasjonsfilen endres, kan du automatisk starte tjenesten på nytt.

I tillegg har Puppet DSL funksjoner og variabler, samt betingede utsagn og velgere. Ulike malmekanismer støttes også - EPP og ERB.

Puppet er skrevet i Ruby, så mange av konstruksjonene og begrepene er hentet derfra. Ruby lar deg utvide Puppet - legg til kompleks logikk, nye typer ressurser, funksjoner.

Mens Puppet kjører, blir manifester for hver spesifikke node på serveren kompilert i en katalog. Каталог er en liste over ressurser og deres relasjoner etter beregning av verdien av funksjoner, variabler og utvidelse av betingede utsagn.

Syntaks og kodestil

Her er deler av den offisielle dokumentasjonen som vil hjelpe deg å forstå syntaksen hvis eksemplene som er gitt ikke er nok:

Her er et eksempel på hvordan manifestet ser ut:

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

Innrykk og linjeskift er ikke en obligatorisk del av manifestet, men det er en anbefalt stilguide. Sammendrag:

  • Innrykk med to mellomrom, tabulatorer brukes ikke.
  • Krøllete klammeparenteser er atskilt med et mellomrom; kolon er ikke atskilt med et mellomrom.
  • Kommaer etter hver parameter, inkludert den siste. Hver parameter er på en egen linje. Et unntak er gjort for tilfellet uten parametere og én parameter: du kan skrive på én linje og uten komma (dvs. resource { 'title': } и resource { 'title': param => value }).
  • Pilene på parametrene skal være på samme nivå.
  • Ressursforholdspiler er skrevet foran dem.

Plassering av filer på pappetserver

For ytterligere forklaring vil jeg introdusere konseptet "rotkatalog". Rotkatalogen er katalogen som inneholder Puppet-konfigurasjonen for en bestemt node.

Rotkatalogen varierer avhengig av versjonen av Puppet og miljøene som brukes. Miljøer er uavhengige sett med konfigurasjon som er lagret i separate kataloger. Brukes vanligvis i kombinasjon med git, i så fall skapes miljøer fra git-grener. Følgelig er hver node plassert i et eller annet miljø. Dette kan konfigureres på selve noden, eller i ENC, som jeg vil snakke om i neste artikkel.

  • I den tredje versjonen ("gamle dukke") var basiskatalogen /etc/puppet. Bruk av miljøer er valgfritt - for eksempel bruker vi dem ikke med den gamle dukken. Hvis miljøer brukes, lagres de vanligvis i /etc/puppet/environments, vil rotkatalogen være miljøkatalogen. Hvis miljøer ikke brukes, vil rotkatalogen være basiskatalogen.
  • Fra den fjerde versjonen ("ny marionett") ble bruk av miljøer obligatorisk, og basiskatalogen ble flyttet til /etc/puppetlabs/code. Følgelig lagres miljøer i /etc/puppetlabs/code/environments, er rotkatalogen miljøkatalogen.

Det må være en underkatalog i rotkatalogen manifests, som inneholder ett eller flere manifester som beskriver nodene. I tillegg bør det være en underkatalog modules, som inneholder modulene. Jeg skal fortelle deg hvilke moduler som er litt senere. I tillegg kan den gamle dukken også ha en underkatalog files, som inneholder ulike filer som vi kopierer til nodene. I den nye Puppet er alle filer plassert i moduler.

Manifestfiler har utvidelsen .pp.

Et par kampeksempler

Beskrivelse av noden og ressursen på den

På noden server1.testdomain en fil må opprettes /etc/issue med innhold Debian GNU/Linux n l. Filen må eies av en bruker og gruppe root, må tilgangsrettigheter være 644.

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

Forhold mellom ressurser på en node

På noden server2.testdomain nginx må kjøre og jobbe med en tidligere forberedt konfigurasjon.

La oss dekomponere problemet:

  • Pakken må installeres nginx.
  • Det er nødvendig at konfigurasjonsfilene kopieres fra serveren.
  • Tjenesten må kjøres nginx.
  • Hvis konfigurasjonen oppdateres, må tjenesten startes på nytt.

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

For at dette skal fungere, trenger du omtrent følgende filplassering på dukkeserveren:

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

Ressurstyper

En fullstendig liste over støttede ressurstyper finner du her i dokumentasjonen, her vil jeg beskrive fem grunnleggende typer, som i min praksis er nok til å løse de fleste problemer.

fil

Administrerer filer, kataloger, symbolkoblinger, deres innhold og tilgangsrettigheter.

alternativer:

  • ressursnavn - bane til filen (valgfritt)
  • banen - bane til filen (hvis den ikke er spesifisert i navnet)
  • sikre - filtype:
    • absent - slette en fil
    • present - det må være en fil av hvilken som helst type (hvis det ikke er noen fil, vil det bli opprettet en vanlig fil)
    • file - vanlig fil
    • directory - katalog
    • link - symbolkobling
  • innhold — filinnhold (kun egnet for vanlige filer, kan ikke brukes sammen med kilde eller mål)
  • kilde — en lenke til banen du vil kopiere innholdet i filen fra (kan ikke brukes sammen med innhold eller mål). Kan spesifiseres som enten en URI med et skjema puppet: (da vil filer fra dukkeserveren bli brukt), og med opplegget http: (Jeg håper det er klart hva som vil skje i dette tilfellet), og til og med med diagrammet file: eller som en absolutt bane uten et skjema (da vil filen fra den lokale FS på noden bli brukt)
  • mål — hvor symbolkoblingen skal peke (kan ikke brukes sammen med innhold eller kilde)
  • eieren — brukeren som skal eie filen
  • gruppe — gruppen som filen skal tilhøre
  • modus - filtillatelser (som en streng)
  • gjengi - muliggjør rekursiv katalogbehandling
  • purge - gjør det mulig å slette filer som ikke er beskrevet i Puppet
  • styrke - gjør det mulig å slette kataloger som ikke er beskrevet i Puppet

pakke

Installerer og fjerner pakker. Kan håndtere varsler - installerer pakken på nytt hvis parameteren er spesifisert reinstall_on_refresh.

alternativer:

  • ressursnavn — pakkenavn (valgfritt)
  • navn — pakkenavn (hvis ikke angitt i navnet)
  • leverandør — pakkebehandler å bruke
  • sikre — ønsket tilstand for pakken:
    • present, installed - hvilken som helst versjon installert
    • latest - siste versjon installert
    • absent - slettet (apt-get remove)
    • purged - slettet sammen med konfigurasjonsfiler (apt-get purge)
    • held - pakkeversjonen er låst (apt-mark hold)
    • любая другая строка — den angitte versjonen er installert
  • reinstall_on_refresh - hvis en true, så vil pakken installeres på nytt etter mottak av varselet. Nyttig for kildebaserte distribusjoner, der gjenoppbygging av pakker kan være nødvendig når du endrer byggeparametere. Misligholde false.

tjeneste

Administrerer tjenester. Kan behandle varsler - starter tjenesten på nytt.

alternativer:

  • ressursnavn — tjeneste som skal administreres (valgfritt)
  • navn — tjenesten som må administreres (hvis ikke angitt i navnet)
  • sikre — ønsket tilstand for tjenesten:
    • running - lanserte
    • stopped - stoppet
  • muliggjøre — kontrollerer muligheten til å starte tjenesten:
    • true - autorun er aktivert (systemctl enable)
    • mask - forkledd (systemctl mask)
    • false - autorun er deaktivert (systemctl disable)
  • restart - kommando for å starte tjenesten på nytt
  • status — kommando for å sjekke tjenestestatus
  • har startet på nytt — angi om tjenestens initscript støtter omstart. Hvis false og parameteren er spesifisert restart — verdien av denne parameteren brukes. Hvis false og parameter restart ikke spesifisert - tjenesten stoppes og starter på nytt (men systemd bruker kommandoen systemctl restart).
  • hasstatus — angi om tjenestens initscript støtter kommandoen status. om false, så brukes parameterverdien status. Misligholde true.

exec

Kjører eksterne kommandoer. Hvis du ikke spesifiserer parametere skaper, bare hvis, med mindre eller forfriskende, vil kommandoen kjøres hver gang Puppet kjøres. Kan behandle varsler - kjører en kommando.

alternativer:

  • ressursnavn — kommando som skal utføres (valgfritt)
  • kommando — kommandoen som skal utføres (hvis den ikke er spesifisert i navnet)
  • banen — baner for å se etter den kjørbare filen
  • bare hvis — hvis kommandoen spesifisert i denne parameteren fullføres med en null returkode, vil hovedkommandoen bli utført
  • med mindre — hvis kommandoen spesifisert i denne parameteren fullføres med en returkode som ikke er null, vil hovedkommandoen bli utført
  • skaper — hvis filen spesifisert i denne parameteren ikke eksisterer, vil hovedkommandoen bli utført
  • forfriskende - hvis en true, så vil kommandoen bare kjøres når denne exec mottar varsling fra andre ressurser
  • cwd — katalogen for å kjøre kommandoen
  • bruker — brukeren som kommandoen skal kjøres fra
  • leverandør - hvordan kjøre kommandoen:
    • POSIX — en underordnet prosess er ganske enkelt opprettet, sørg for å spesifisere banen
    • shell - Kommandoen startes i skallet /bin/sh, kan ikke spesifiseres banen, kan du bruke globbing, piper og andre skallfunksjoner. Oppdages vanligvis automatisk hvis det er noen spesialtegn (|, ;, &&, || etc).

cron

Kontrollerer cronjobs.

alternativer:

  • ressursnavn - bare en slags identifikator
  • sikre — kronejobbtilstand:
    • present - opprette hvis ikke eksisterer
    • absent - slett hvis det finnes
  • kommando - hvilken kommando som skal kjøres
  • miljø - i hvilket miljø kommandoen skal kjøres (liste over miljøvariabler og deres verdier via =)
  • bruker - fra hvilken bruker kommandoen skal kjøres
  • minutt, time, ukedag, måned, måned dag — når skal man kjøre cron. Hvis noen av disse attributtene ikke er spesifisert, vil verdien i crontab være *.

I Puppet 6.0 cron som om fjernet fra esken i puppetserver, så det er ingen dokumentasjon på den generelle siden. Men han er i esken i puppet-agent, så det er ikke nødvendig å installere det separat. Du kan se dokumentasjonen for det i dokumentasjonen for den femte versjonen av PuppetEller på GitHub.

Om ressurser generelt

Krav til ressursunikk

Den vanligste feilen vi møter er Duplikaterklæring. Denne feilen oppstår når to eller flere ressurser av samme type med samme navn vises i katalogen.

Derfor vil jeg skrive igjen: manifester for samme node skal ikke inneholde ressurser av samme type med samme tittel!

Noen ganger er det behov for å installere pakker med samme navn, men med forskjellige pakkebehandlere. I dette tilfellet må du bruke parameteren namefor å unngå feilen:

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

Andre ressurstyper har lignende alternativer for å unngå duplisering − name у tjeneste, command у exec, og så videre.

Metaparametere

Hver ressurstype har noen spesielle parametere, uavhengig av dens natur.

Full liste over metaparametere i Puppet-dokumentasjonen.

Kort liste:

  • krever — denne parameteren indikerer hvilke ressurser denne ressursen er avhengig av.
  • før du - Denne parameteren spesifiserer hvilke ressurser som er avhengige av denne ressursen.
  • abonnere — denne parameteren spesifiserer hvilke ressurser denne ressursen mottar varsler fra.
  • gi beskjed — Denne parameteren spesifiserer hvilke ressurser som mottar varsler fra denne ressursen.

Alle de oppførte metaparametrene godtar enten en enkelt ressurskobling eller en rekke koblinger i hakeparenteser.

Lenker til ressurser

En ressurskobling er ganske enkelt en omtale av ressursen. De brukes hovedsakelig for å indikere avhengigheter. Å referere til en ikke-eksisterende ressurs vil forårsake en kompileringsfeil.

Syntaksen til lenken er som følger: ressurstype med stor bokstav (hvis typenavnet inneholder doble kolon, er hver del av navnet mellom kolonene ført med stor bokstav), deretter ressursnavnet i hakeparenteser (navnet endres ikke!). Det skal ikke være mellomrom; hakeparenteser skrives umiddelbart etter typenavnet.

Eksempel:

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

Avhengigheter og varsler

Dokumentasjon her.

Som nevnt tidligere, er enkle avhengigheter mellom ressurser transitive. Vær forresten forsiktig når du legger til avhengigheter - du kan lage sykliske avhengigheter, noe som vil forårsake en kompileringsfeil.

I motsetning til avhengigheter er ikke varslinger transitive. Følgende regler gjelder for varsler:

  • Hvis ressursen mottar et varsel, oppdateres den. Oppdateringshandlingene avhenger av ressurstypen − exec kjører kommandoen, tjeneste starter tjenesten på nytt, pakke installerer pakken på nytt. Hvis ressursen ikke har en oppdateringshandling definert, skjer ingenting.
  • Under en kjøring av Puppet oppdateres ressursen ikke mer enn én gang. Dette er mulig fordi varsler inkluderer avhengigheter og avhengighetsgrafen ikke inneholder sykluser.
  • Hvis Puppet endrer tilstanden til en ressurs, sender ressursen varsler til alle ressursene som abonnerer på den.
  • Hvis en ressurs er oppdatert, sender den varsler til alle ressurser som abonnerer på den.

Håndtering av uspesifiserte parametere

Som regel, hvis en ressursparameter ikke har en standardverdi og denne parameteren ikke er spesifisert i manifestet, vil ikke Puppet endre denne egenskapen for den tilsvarende ressursen på noden. For eksempel hvis en ressurs av typen fil parameter ikke spesifisert owner, så vil ikke Puppet endre eieren av den tilsvarende filen.

Introduksjon til klasser, variabler og definisjoner

Anta at vi har flere noder som har samme del av konfigurasjonen, men det er også forskjeller - ellers kan vi beskrive det hele i en blokk node {}. Selvfølgelig kan du ganske enkelt kopiere identiske deler av konfigurasjonen, men generelt er dette en dårlig løsning - konfigurasjonen vokser, og hvis du endrer den generelle delen av konfigurasjonen, må du redigere det samme mange steder. Samtidig er det lett å gjøre en feil, og generelt ble DRY (ikke gjenta deg selv)-prinsippet oppfunnet av en grunn.

For å løse dette problemet er det et slikt design som klasse.

Klasser

Klasse er en navngitt blokk med tallerkenkode. Klasser er nødvendig for å gjenbruke kode.

Først må klassen beskrives. Selve beskrivelsen legger ikke til noen ressurser noe sted. Klassen er beskrevet i manifester:

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

Etter dette kan klassen brukes:

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

Et eksempel fra forrige oppgave - la oss flytte installasjonen og konfigurasjonen av nginx til en klasse:

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
}

Variabler

Klassen fra forrige eksempel er ikke fleksibel i det hele tatt fordi den alltid gir den samme nginx-konfigurasjonen. La oss lage banen til konfigurasjonsvariabelen, så kan denne klassen brukes til å installere nginx med hvilken som helst konfigurasjon.

Det kan gjøres ved hjelp av variabler.

OBS: variabler i Puppet er uforanderlige!

I tillegg kan man bare få tilgang til en variabel etter at den er deklarert, ellers vil verdien av variabelen være undef.

Eksempel på arbeid med variabler:

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

Puppet har navneområder, og variablene har følgelig synlighetsområde: En variabel med samme navn kan defineres i forskjellige navneområder. Når du løser verdien til en variabel, søkes variabelen i det gjeldende navneområdet, deretter i det omsluttende navnerommet, og så videre.

Eksempler på navneområder:

  • global - variabler utenfor klasse- eller nodebeskrivelsen går dit;
  • nodenavneområde i nodebeskrivelsen;
  • klassenavneområdet i klassebeskrivelsen.

For å unngå tvetydighet når du får tilgang til en variabel, kan du spesifisere navneområdet i variabelnavnet:

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

La oss bli enige om at banen til nginx-konfigurasjonen ligger i variabelen $nginx_conf_source. Da vil klassen se slik ut:

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
}

Imidlertid er det gitte eksemplet dårlig fordi det er en viss "hemmelig kunnskap" om at et sted inne i klassen brukes en variabel med et slikt og et slikt navn. Det er mye mer riktig å gjøre denne kunnskapen generell – klasser kan ha parametere.

Klasseparametere er variabler i klassenavnerommet, de er spesifisert i klasseoverskriften og kan brukes som vanlige variabler i klassekroppen. Parameterverdier spesifiseres når du bruker klassen i manifestet.

Parameteren kan settes til en standardverdi. Hvis en parameter ikke har en standardverdi og verdien ikke er satt når den brukes, vil det forårsake en kompileringsfeil.

La oss parameterisere klassen fra eksemplet ovenfor og legge til to parametere: den første, som kreves, er banen til konfigurasjonen, og den andre, valgfri, er navnet på pakken med nginx (i Debian, for eksempel, er det pakker 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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

I Puppet skrives variabler. Spise mange datatyper. Datatyper brukes vanligvis til å validere parameterverdier som sendes til klasser og definisjoner. Hvis den beståtte parameteren ikke samsvarer med den angitte typen, vil det oppstå en kompileringsfeil.

Typen skrives rett før parameternavnet:

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

Klasser: inkluderer klassenavn vs klasse{'klassenavn':}

Hver klasse er en ressurs av typen klasse. Som med alle andre typer ressurs, kan det ikke være to forekomster av samme klasse på samme node.

Hvis du prøver å legge til en klasse til samme node to ganger ved å bruke class { 'classname':} (ingen forskjell, med forskjellige eller identiske parametere), vil det være en kompileringsfeil. Men hvis du bruker en klasse i ressursstilen, kan du umiddelbart eksplisitt angi alle parameterne i manifestet.

Men hvis du bruker include, så kan klassen legges til så mange ganger som ønskelig. Faktum er det include er en idempotent funksjon som sjekker om en klasse er lagt til i katalogen. Hvis klassen ikke er i katalogen, legger den den til, og hvis den allerede eksisterer, gjør den ingenting. Men i tilfelle bruk include Du kan ikke sette klasseparametere under klassedeklarasjon - alle nødvendige parametere må settes i en ekstern datakilde - Hiera eller ENC. Vi vil snakke om dem i neste artikkel.

Definerer

Som det ble sagt i forrige blokk, kan ikke samme klasse være til stede på en node mer enn én gang. I noen tilfeller må du imidlertid kunne bruke samme kodeblokk med forskjellige parametere på samme node. Det er med andre ord behov for en egen ressurstype.

For å installere PHP-modulen gjør vi for eksempel følgende i Avito:

  1. Installer pakken med denne modulen.
  2. La oss lage en konfigurasjonsfil for denne modulen.
  3. Vi lager en symbolkobling til konfigurasjonen for php-fpm.
  4. Vi lager en symbolkobling til konfigurasjonen for php cli.

I slike tilfeller kan et design som f.eks definere (definer, definert type, definert ressurstype). En Define ligner på en klasse, men det er forskjeller: for det første er hver Define en ressurstype, ikke en ressurs; for det andre har hver definisjon en implisitt parameter $title, hvor ressursnavnet går når det er deklarert. Akkurat som når det gjelder klasser, må en definisjon først beskrives, deretter kan den brukes.

Et forenklet eksempel med en modul for 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' }
}

Den enkleste måten å fange opp duplikatdeklarasjonsfeilen er i Define. Dette skjer hvis en definisjon har en ressurs med et konstant navn, og det er to eller flere forekomster av denne definisjonen på en node.

Det er lett å beskytte deg mot dette: alle ressurser i definisjonen må ha et navn avhengig av $title. Et alternativ er idempotent tillegg av ressurser; i det enkleste tilfellet er det nok å flytte ressursene som er felles for alle forekomster av definisjonen til en egen klasse og inkludere denne klassen i definisjonen - funksjon include idempotent.

Det er andre måter å oppnå idempotens på når du legger til ressurser, nemlig å bruke funksjoner defined и ensure_resources, men jeg skal fortelle deg om det i neste episode.

Avhengigheter og varsler for klasser og definisjoner

Klasser og definisjoner legger til følgende regler for håndtering av avhengigheter og varsler:

  • avhengighet av en klasse/define legger til avhengigheter på alle ressursene i klassen/define;
  • en klasse/definer avhengighet legger til avhengigheter til alle klasse/definer ressurser;
  • class/define notification varsler alle ressursene i klassen/define;
  • class/define-abonnement abonnerer på alle ressursene i klassen/define.

Betingede uttalelser og velgere

Dokumentasjon her.

if

Det er enkelt her:

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

med mindre

med mindre er en hvis i revers: kodeblokken vil bli utført hvis uttrykket er usant.

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

saken

Det er ikke noe komplisert her heller. Du kan bruke vanlige verdier (strenger, tall, etc.), regulære uttrykk og datatyper som verdier.

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

Velgere

En velger er en språkkonstruksjon som ligner på case, men i stedet for å kjøre en kodeblokk, returnerer den en verdi.

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

moduler

Når konfigurasjonen er liten, kan den lett holdes i ett manifest. Men jo flere konfigurasjoner vi beskriver, jo flere klasser og noder er det i manifestet, det vokser, og det blir upraktisk å jobbe med.

I tillegg kommer problemet med gjenbruk av kode - når all koden er i ett manifest, er det vanskelig å dele denne koden med andre. For å løse disse to problemene har Puppet en enhet kalt moduler.

moduler - Dette er sett med klasser, definisjoner og andre Puppet-enheter plassert i en egen katalog. Med andre ord er en modul et uavhengig stykke Puppet-logikk. For eksempel kan det være en modul for å jobbe med nginx, og den vil inneholde det og bare det som trengs for å jobbe med nginx, eller det kan være en modul for å jobbe med PHP, og så videre.

Moduler er versjonert, og modulers avhengighet av hverandre støttes også. Det er et åpent arkiv med moduler - Puppet Forge.

På dukkeserveren er moduler plassert i underkatalogen moduler til rotkatalogen. Inne i hver modul er det et standard katalogoppsett - manifester, filer, maler, lib, og så videre.

Filstruktur i en modul

Roten til modulen kan inneholde følgende kataloger med beskrivende navn:

  • manifests - den inneholder manifester
  • files - den inneholder filer
  • templates - den inneholder maler
  • lib - den inneholder Ruby-kode

Dette er ikke en fullstendig liste over kataloger og filer, men det er nok for denne artikkelen for nå.

Navn på ressurser og navn på filer i modulen

Dokumentasjon her.

Ressurser (klasser, definisjoner) i en modul kan ikke navngis hva du vil. I tillegg er det en direkte samsvar mellom navnet på en ressurs og navnet på filen der Puppet vil se etter en beskrivelse av den ressursen. Hvis du bryter navnereglene, vil Puppet ganske enkelt ikke finne ressursbeskrivelsen, og du vil få en kompileringsfeil.

Reglene er enkle:

  • Alle ressurser i en modul må være i modulnavneområdet. Hvis modulen kalles foo, så skal alle ressursene i den navngis foo::<anything>, eller bare foo.
  • Ressursen med navnet på modulen må være i filen init.pp.
  • For andre ressurser er filnavnskjemaet som følger:
    • prefikset med modulnavnet forkastes
    • alle doble kolon, hvis noen, erstattes med skråstreker
    • utvidelse legges til .pp

Jeg vil demonstrere med et eksempel. La oss si at jeg skriver en modul nginx. Den inneholder følgende ressurser:

  • klasse nginx beskrevet i manifestet init.pp;
  • klasse nginx::service beskrevet i manifestet service.pp;
  • definere nginx::server beskrevet i manifestet server.pp;
  • definere nginx::server::location beskrevet i manifestet server/location.pp.

maler

Du vet sikkert selv hva maler er; jeg vil ikke beskrive dem i detalj her. Men jeg lar det være i tilfelle lenke til Wikipedia.

Slik bruker du maler: Betydningen av en mal kan utvides ved hjelp av en funksjon template, som sendes veien til malen. For ressurser av typen fil brukes sammen med parameteren content. For eksempel slik:

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

Vis sti <modulename>/<filename> innebærer fil <rootdir>/modules/<modulename>/templates/<filename>.

I tillegg er det en funksjon inline_template — den mottar malteksten som input, ikke filnavnet.

Innenfor maler kan du bruke alle Puppet-variabler i gjeldende omfang.

Puppet støtter maler i ERB- og EPP-format:

Kort om ERB

Kontrollstrukturer:

  • <%= ВЫРАЖЕНИЕ %> – sett inn verdien av uttrykket
  • <% ВЫРАЖЕНИЕ %> — beregne verdien av et uttrykk (uten å sette det inn). Betingede utsagn (hvis) og løkker (hver) går vanligvis her.
  • <%# КОММЕНТАРИЙ %>

Uttrykk i ERB er skrevet i Ruby (ERB er faktisk Embedded Ruby).

For å få tilgang til variabler fra manifestet, må du legge til @ til variabelnavnet. For å fjerne et linjeskift som vises etter en kontrollkonstruksjon, må du bruke en avsluttende kode -%>.

Eksempel på bruk av malen

La oss si at jeg skriver en modul for å kontrollere ZooKeeper. Klassen som er ansvarlig for å lage konfigurasjonen ser omtrent slik ut:

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

Og den tilsvarende malen zoo.cfg.erb - Så:

<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>

dataDir=<%= @datadir %>

<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>

Fakta og innebygde variabler

Ofte avhenger den spesifikke delen av konfigurasjonen av hva som for øyeblikket skjer på noden. For eksempel, avhengig av hva Debian-utgivelsen er, må du installere en eller annen versjon av pakken. Du kan overvåke alt dette manuelt, og omskrive manifester hvis noder endres. Men dette er ikke en seriøs tilnærming; automatisering er mye bedre.

For å få informasjon om noder har Puppet en mekanisme som kalles fakta. Fakta - dette er informasjon om noden, tilgjengelig i manifester i form av vanlige variabler i det globale navnerommet. For eksempel vertsnavn, operativsystemversjon, prosessorarkitektur, liste over brukere, liste over nettverksgrensesnitt og deres adresser, og mye, mye mer. Fakta er tilgjengelig i manifester og maler som vanlige variabler.

Et eksempel på arbeid med fakta:

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

Formelt sett har et faktum et navn (streng) og en verdi (ulike typer er tilgjengelige: strenger, matriser, ordbøker). Spise sett med innebygde fakta. Du kan også skrive din egen. Faktasamlere er beskrevet som funksjoner i Rubyeller hvordan kjørbare filer. Fakta kan også presenteres i skjemaet tekstfiler med data på nodene.

Under drift kopierer dukkeagenten først alle tilgjengelige faktainnsamlere fra pappetserveren til noden, hvoretter den starter dem og sender de innsamlede faktaene til serveren; Etter dette begynner serveren å kompilere katalogen.

Fakta i form av kjørbare filer

Slike fakta er plassert i moduler i katalogen facts.d. Selvfølgelig må filene være kjørbare. Når de kjøres, må de sende ut informasjon til standardutdata i enten YAML- eller nøkkel=verdi-format.

Ikke glem at fakta gjelder for alle noder som er kontrollert av poppet-serveren som modulen din er distribuert til. Derfor, i skriptet, sørg for å sjekke at systemet har alle programmene og filene som er nødvendige for at fakta skal fungere.

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

Ruby fakta

Slike fakta er plassert i moduler i katalogen 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

Tekstfakta

Slike fakta er plassert på noder i katalogen /etc/facter/facts.d i gamle Puppet eller /etc/puppetlabs/facts.d i den nye dukken.

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

Komme til fakta

Det er to måter å nærme seg fakta på:

  • gjennom ordboka $facts: $facts['fqdn'];
  • ved å bruke faktanavnet som variabelnavn: $fqdn.

Det er best å bruke en ordbok $facts, eller enda bedre, angi det globale navneområdet ($::facts).

Her er den relevante delen av dokumentasjonen.

Innebygde variabler

I tillegg til fakta, er det også noen variabler, tilgjengelig i det globale navneområdet.

  • pålitelige fakta — variabler som er hentet fra klientens sertifikat (siden sertifikatet vanligvis utstedes på en poppet-server, kan ikke agenten bare ta og endre sertifikatet sitt, så variablene er “klarert”): navnet på sertifikatet, navnet på vert og domene, utvidelser fra sertifikatet.
  • serverfakta – Variabler relatert til informasjon om serveren – versjon, navn, server IP-adresse, miljø.
  • agent fakta — variabler lagt til direkte av puppet-agent, og ikke av fakta — sertifikatnavn, agentversjon, puppet-versjon.
  • mastervariabler - Pappetmaster-variabler (sic!). Det er omtrent det samme som i serverfakta, pluss konfigurasjonsparameterverdier er tilgjengelige.
  • kompilatorvariabler — kompilatorvariabler som er forskjellige i hvert omfang: navnet på den gjeldende modulen og navnet på modulen der det nåværende objektet ble åpnet. De kan for eksempel brukes til å sjekke at private klasser ikke brukes direkte fra andre moduler.

Tillegg 1: hvordan kjører og feilsøker alt dette?

Artikkelen inneholdt mange eksempler på dukkekode, men fortalte oss ikke i det hele tatt hvordan vi skulle kjøre denne koden. Vel, jeg korrigerer meg selv.

En agent er nok til å kjøre Puppet, men i de fleste tilfeller trenger du også en server.

agenten

I hvert fall siden versjon XNUMX, puppet-agent-pakker fra offisielle Puppetlabs-depot inneholder alle avhengighetene (ruby og de tilsvarende edelstenene), så det er ingen installasjonsproblemer (jeg snakker om Debian-baserte distribusjoner - vi bruker ikke RPM-baserte distribusjoner).

I det enkleste tilfellet, for å bruke dukkekonfigurasjonen, er det nok å starte agenten i serverløs modus: forutsatt at dukkekoden er kopiert til noden, start 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

Det er selvfølgelig bedre å sette opp serveren og kjøre agenter på nodene i daemon-modus - da vil de en gang hver halve time bruke konfigurasjonen som er lastet ned fra serveren.

Du kan imitere push-modellen av arbeid - gå til noden du er interessert i og start sudo puppet agent -t. Nøkkel -t (--test) inkluderer faktisk flere alternativer som kan aktiveres individuelt. Disse alternativene inkluderer følgende:

  • ikke kjør i daemon-modus (som standard starter agenten i daemon-modus);
  • slå av etter bruk av katalogen (som standard vil agenten fortsette å jobbe og bruke konfigurasjonen en gang hver halvtime);
  • skrive en detaljert arbeidslogg;
  • vise endringer i filer.

Agenten har en driftsmodus uten endringer - du kan bruke den når du ikke er sikker på at du har skrevet riktig konfigurasjon og ønsker å sjekke nøyaktig hva agenten vil endre under drift. Denne modusen aktiveres av parameteren --noop på kommandolinjen: sudo puppet agent -t --noop.

I tillegg kan du aktivere feilsøkingsloggen for arbeidet - i den skriver marionett om alle handlingene den utfører: om ressursen den behandler for øyeblikket, om parametrene til denne ressursen, om hvilke programmer den starter. Selvfølgelig er dette en parameter --debug.

Сервер

Jeg vil ikke vurdere hele oppsettet av pappetserveren og distribuere koden til den i denne artikkelen; jeg vil bare si at ut av esken er det en fullt funksjonell versjon av serveren som ikke krever ytterligere konfigurasjon for å fungere med et lite antall noder (si opptil hundre). Et større antall noder vil kreve justering - som standard starter dukketeater ikke mer enn fire arbeidere, for bedre ytelse må du øke antallet og ikke glem å øke minnegrensene, ellers vil serveren samle søppel mesteparten av tiden.

Kodeimplementering - hvis du trenger det raskt og enkelt, så se (på r10k)[https://github.com/puppetlabs/r10k], for små installasjoner bør det være ganske nok.

Tillegg 2: Retningslinjer for koding

  1. Plasser all logikk i klasser og definisjoner.
  2. Hold klasser og definisjoner i moduler, ikke i manifester som beskriver noder.
  3. Bruk fakta.
  4. Ikke lag hvis basert på vertsnavn.
  5. Legg gjerne til parametere for klasser og definisjoner - dette er bedre enn implisitt logikk gjemt i kroppen til klassen/define.

Jeg vil forklare hvorfor jeg anbefaler å gjøre dette i neste artikkel.

Konklusjon

La oss avslutte med introduksjonen. I neste artikkel vil jeg fortelle deg om Hiera, ENC og PuppetDB.

Kun registrerte brukere kan delta i undersøkelsen. Logg inn, vær så snill.

Faktisk er det mye mer materiale - jeg kan skrive artikler om følgende emner, stemme på det du vil være interessert i å lese om:

  • 59,1%Avanserte marionettkonstruksjoner - noe dritt på neste nivå: løkker, kartlegging og andre lambda-uttrykk, ressurssamlere, eksporterte ressurser og kommunikasjon mellom vertene via Puppet, tagger, leverandører, abstrakte datatyper.13
  • 31,8%"I'm my mother's admin" eller hvordan vi i Avito ble venner med flere poppet-servere av forskjellige versjoner, og i prinsippet delen om å administrere poppet-serveren.7
  • 81,8%Hvordan vi skriver dukkekode: instrumentering, dokumentasjon, testing, CI/CD.18

22 brukere stemte. 9 brukere avsto.

Kilde: www.habr.com