Introduktion til Puppet

Puppet er et konfigurationsstyringssystem. Det bruges til at bringe værter til den ønskede tilstand og opretholde denne tilstand.

Jeg har arbejdet med Puppet i over fem år nu. Denne tekst er i det væsentlige en oversat og omorganiseret samling af nøglepunkter fra den officielle dokumentation, som giver begyndere mulighed for hurtigt at forstå essensen af ​​Puppet.

Introduktion til Puppet

Grundlæggende oplysninger

Puppets operativsystem er klient-server, selvom det også understøtter serverløs drift med begrænset funktionalitet.

En pull-driftsmodel bruges: som standard kontakter klienter serveren en gang hver halve time for at få en konfiguration og anvender den. Hvis du har arbejdet med Ansible, så bruger de en anden push-model: administratoren starter processen med at anvende konfigurationen, klienterne selv vil ikke anvende noget.

Under netværkskommunikation bruges to-vejs TLS-kryptering: Serveren og klienten har deres egne private nøgler og tilhørende certifikater. Typisk udsteder serveren certifikater til klienter, men i princippet er det muligt at bruge en ekstern CA.

Introduktion til manifester

I Puppet terminologi til dukkeserveren Opret forbindelse noder (knudepunkter). Konfigurationen for noderne er skrevet i manifester i et særligt programmeringssprog - Puppet DSL.

Puppet DSL er et deklarativt sprog. Den beskriver den ønskede tilstand af noden i form af erklæringer om individuelle ressourcer, for eksempel:

  • Filen eksisterer, og den har specifikt indhold.
  • Pakken er installeret.
  • Tjenesten er startet.

Ressourcer kan sammenkobles:

  • Der er afhængigheder, de påvirker rækkefølgen, som ressourcerne bruges i.
    For eksempel, "installer først pakken, rediger derefter konfigurationsfilen, og start derefter tjenesten."
  • Der er meddelelser - hvis en ressource er ændret, sender den meddelelser til de ressourcer, der abonnerer på den.
    For eksempel, hvis konfigurationsfilen ændres, kan du automatisk genstarte tjenesten.

Derudover har Puppet DSL funktioner og variabler samt betingede sætninger og vælgere. Forskellige skabelonmekanismer understøttes også - EPP og ERB.

Puppet er skrevet i Ruby, så mange af konstruktionerne og termerne er taget derfra. Ruby giver dig mulighed for at udvide Puppet - tilføje kompleks logik, nye typer ressourcer, funktioner.

Mens Puppet kører, kompileres manifester for hver specifik node på serveren i en mappe. Каталог er en liste over ressourcer og deres relationer efter beregning af værdien af ​​funktioner, variable og udvidelse af betingede udsagn.

Syntaks og kodestil

Her er sektioner af den officielle dokumentation, der hjælper dig med at forstå syntaksen, hvis de angivne eksempler ikke er nok:

Her er et eksempel på, hvordan manifestet ser ud:

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

Indrykninger og linjeskift er ikke en obligatorisk del af manifestet, men der er en anbefalet stil guide. Resumé:

  • Indryk med to mellemrum, faner bruges ikke.
  • Krøllede seler er adskilt af et mellemrum; kolon er ikke adskilt af et mellemrum.
  • Kommaer efter hver parameter, inklusive den sidste. Hver parameter er på en separat linje. En undtagelse er lavet for tilfældet uden parametre og én parameter: du kan skrive på én linje og uden komma (dvs. resource { 'title': } и resource { 'title': param => value }).
  • Pilene på parametrene skal være på samme niveau.
  • Ressourcerelationspile er skrevet foran dem.

Placering af filer på pappetserver

For yderligere forklaring vil jeg introducere begrebet "rodmappe". Rodbiblioteket er det bibliotek, der indeholder Puppet-konfigurationen for en specifik node.

Rodbiblioteket varierer afhængigt af versionen af ​​Puppet og de anvendte miljøer. Miljøer er uafhængige sæt af konfigurationer, der er gemt i separate mapper. Bruges normalt i kombination med git, i hvilket tilfælde miljøer er skabt ud fra git-grene. Følgelig er hver knude placeret i et eller andet miljø. Dette kan konfigureres på selve noden eller i ENC, som jeg vil tale om i næste artikel.

  • I den tredje version ("gamle Puppet") var basismappen /etc/puppet. Brugen af ​​miljøer er valgfri - for eksempel bruger vi dem ikke sammen med den gamle Puppet. Hvis der bruges miljøer, opbevares de normalt i /etc/puppet/environments, vil rodmappen være miljømappen. Hvis miljøer ikke bruges, vil rodmappen være basismappen.
  • Fra den fjerde version ("ny marionet") blev brugen af ​​miljøer obligatorisk, og basismappen blev flyttet til /etc/puppetlabs/code. Derfor lagres miljøer i /etc/puppetlabs/code/environments, rodmappen er miljømappen.

Der skal være en undermappe i rodmappen manifests, som indeholder et eller flere manifester, der beskriver noderne. Derudover skal der være en undermappe modules, som indeholder modulerne. Jeg fortæller dig, hvilke moduler der er lidt senere. Derudover kan den gamle Puppet også have en undermappe files, som indeholder forskellige filer, som vi kopierer til noderne. I den nye Puppet er alle filer placeret i moduler.

Manifestfiler har udvidelsen .pp.

Et par kampeksempler

Beskrivelse af noden og ressourcen på den

På knudepunktet server1.testdomain en fil skal oprettes /etc/issue med indhold Debian GNU/Linux n l. Filen skal ejes af en bruger og en gruppe root, skal adgangsrettigheder 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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Relationer mellem ressourcer på en knude

På knudepunktet server2.testdomain nginx skal køre og arbejde med en tidligere forberedt konfiguration.

Lad os nedbryde problemet:

  • Pakken skal installeres nginx.
  • Det er nødvendigt, at konfigurationsfilerne kopieres fra serveren.
  • Tjenesten skal køre nginx.
  • Hvis konfigurationen opdateres, skal tjenesten genstartes.

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 virke, skal du have cirka følgende filplacering 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

Ressourcetyper

En komplet liste over understøttede ressourcetyper kan findes her i dokumentationen, vil jeg her beskrive fem grundtyper, som i min praksis er nok til at løse de fleste problemer.

fil

Administrerer filer, mapper, symbolske links, deres indhold og adgangsrettigheder.

Valg:

  • ressourcenavn — sti til filen (valgfrit)
  • sti — sti til filen (hvis den ikke er angivet i navnet)
  • sikre - filtype:
    • absent - slet en fil
    • present — der skal være en fil af enhver type (hvis der ikke er nogen fil, oprettes en almindelig fil)
    • file - almindelig fil
    • directory - bibliotek
    • link - symbolsk link
  • indhold — filindhold (kun egnet til almindelige filer, kan ikke bruges sammen med kilde eller mål)
  • kilde — et link til stien, hvorfra du vil kopiere indholdet af filen (kan ikke bruges sammen med indhold eller mål). Kan angives som enten en URI med et skema puppet: (så vil filer fra dukkeserveren blive brugt), og med skemaet http: (Jeg håber, det er klart, hvad der vil ske i dette tilfælde), og endda med diagrammet file: eller som en absolut sti uden et skema (så vil filen fra den lokale FS på noden blive brugt)
  • mål — hvor symbollinket skal pege (kan ikke bruges sammen med indhold eller kilde)
  • ejer — den bruger, der skal eje filen
  • gruppe — den gruppe, som filen skal tilhøre
  • tilstand — filtilladelser (som en streng)
  • genoprette - muliggør rekursiv katalogbehandling
  • udrensning - gør det muligt at slette filer, der ikke er beskrevet i Puppet
  • styrke - gør det muligt at slette mapper, der ikke er beskrevet i Puppet

pakke

Installerer og fjerner pakker. Kan håndtere meddelelser - geninstallerer pakken, hvis parameteren er angivet reinstall_on_refresh.

Valg:

  • ressourcenavn — pakkenavn (valgfrit)
  • navn — pakkenavn (hvis ikke angivet i navnet)
  • udbyder — pakkehåndtering at bruge
  • sikre — ønsket tilstand af pakken:
    • present, installed - enhver version installeret
    • latest - seneste version installeret
    • absent - slettet (apt-get remove)
    • purged — slettet sammen med konfigurationsfiler (apt-get purge)
    • held - pakkeversionen er låst (apt-mark hold)
    • любая другая строка — den angivne version er installeret
  • reinstall_on_refresh - hvis true, så geninstalleres pakken efter modtagelse af meddelelsen. Nyttigt til kildebaserede distributioner, hvor det kan være nødvendigt at genopbygge pakker ved ændring af build-parametre. Standard false.

tjeneste

Administrerer tjenester. I stand til at behandle meddelelser - genstarter tjenesten.

Valg:

  • ressourcenavn — tjeneste, der skal administreres (valgfrit)
  • navn — den service, der skal administreres (hvis ikke angivet i navnet)
  • sikre — ønsket tilstand af tjenesten:
    • running - lanceret
    • stopped - holdt op
  • muliggøre — styrer muligheden for at starte tjenesten:
    • true — autorun er aktiveret (systemctl enable)
    • mask - forklædt (systemctl mask)
    • false — autorun er deaktiveret (systemctl disable)
  • genstart - kommando for at genstarte tjenesten
  • status — kommando til at kontrollere servicestatus
  • har genstartet — angiv, om tjenestens initscript understøtter genstart. Hvis false og parameteren er angivet genstart — værdien af ​​denne parameter bruges. Hvis false og parameter genstart ikke angivet - tjenesten er stoppet og genstartet (men systemd bruger kommandoen systemctl restart).
  • har status — angiv, om tjenestens initscript understøtter kommandoen status. hvis false, så bruges parameterværdien status. Standard true.

exec

Kører eksterne kommandoer. Hvis du ikke angiver parametre skaber, kun hvis, medmindre eller forfriskende, vil kommandoen blive kørt hver gang Puppet køres. I stand til at behandle meddelelser - kører en kommando.

Valg:

  • ressourcenavn — kommando, der skal udføres (valgfrit)
  • kommando — kommandoen, der skal udføres (hvis den ikke er angivet i navnet)
  • sti — stier til at lede efter den eksekverbare fil
  • kun hvis — hvis kommandoen specificeret i denne parameter er afsluttet med en nulreturkode, vil hovedkommandoen blive udført
  • medmindre — hvis kommandoen specificeret i denne parameter er afsluttet med en returkode, der ikke er nul, vil hovedkommandoen blive udført
  • skaber — hvis filen specificeret i denne parameter ikke eksisterer, vil hovedkommandoen blive udført
  • forfriskende - hvis true, så vil kommandoen kun blive kørt, når denne exec modtager besked fra andre ressourcer
  • cwd — mappe, hvorfra kommandoen skal køres
  • bruger — den bruger, som kommandoen skal køres fra
  • udbyder - hvordan man kører kommandoen:
    • POSIX — en underordnet proces er simpelthen oprettet, sørg for at specificere sti
    • Shell - kommandoen startes i skallen /bin/sh, kan ikke specificeres sti, kan du bruge globbing, rør og andre skalfunktioner. Detekteres normalt automatisk, hvis der er nogen specialtegn (|, ;, &&, || etc).

cron

Styrer cronjobs.

Valg:

  • ressourcenavn - bare en form for identifikator
  • sikre — kronjobtilstand:
    • present - opret hvis ikke eksisterer
    • absent - slet hvis det findes
  • kommando - hvilken kommando der skal køres
  • miljø - i hvilket miljø kommandoen skal køres (liste over miljøvariabler og deres værdier via =)
  • bruger - fra hvilken bruger kommandoen skal køres
  • minut, time, ugedag, måned, månedsdag — hvornår skal man køre cron. Hvis nogen af ​​disse attributter ikke er specificeret, vil dens værdi i crontab være *.

I Puppet 6.0 cron som om fjernet fra kassen i puppetserver, så der er ingen dokumentation på den generelle side. Men han er i kassen i puppet-agent, så det er ikke nødvendigt at installere det separat. Du kan se dokumentationen for det i dokumentationen til den femte version af PuppetEller på GitHub.

Om ressourcer generelt

Krav til ressourcens unikhed

Den mest almindelige fejl, vi støder på, er Duplikaterklæring. Denne fejl opstår, når to eller flere ressourcer af samme type med samme navn vises i mappen.

Derfor skriver jeg igen: manifester for den samme node bør ikke indeholde ressourcer af samme type med samme titel!

Nogle gange er der behov for at installere pakker med samme navn, men med forskellige pakkeadministratorer. I dette tilfælde skal du bruge parameteren namefor at undgå fejlen:

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

Andre ressourcetyper har lignende muligheder for at undgå duplikering − name у tjeneste, command у exec, og så videre.

Metaparametre

Hver ressourcetype har nogle specielle parametre, uanset dens natur.

Fuld liste over metaparametre i Puppet-dokumentationen.

Kort liste:

  • kræver — denne parameter angiver, hvilke ressourcer denne ressource afhænger af.
  • før - Denne parameter angiver, hvilke ressourcer der afhænger af denne ressource.
  • Hold mig opdateret — denne parameter angiver, fra hvilke ressourcer denne ressource modtager meddelelser.
  • underrette — Denne parameter angiver, hvilke ressourcer der modtager meddelelser fra denne ressource.

Alle de anførte metaparametre accepterer enten et enkelt ressourcelink eller et array af links i firkantede parenteser.

Links til ressourcer

Et ressourcelink er blot en omtale af ressourcen. De bruges hovedsageligt til at angive afhængigheder. Henvisning til en ikke-eksisterende ressource vil forårsage en kompileringsfejl.

Syntaksen for linket er som følger: ressourcetype med stort bogstav (hvis typenavnet indeholder dobbeltkolon, er hver del af navnet mellem kolonerne med stort), derefter ressourcenavnet i firkantede parenteser (navnet ændres ikke!). Der bør ikke være mellemrum; firkantede parenteser skrives umiddelbart efter typenavnet.

Eksempel:

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

Afhængigheder og meddelelser

Dokumentation her.

Som nævnt tidligere er simple afhængigheder mellem ressourcer transitive. Vær i øvrigt forsigtig, når du tilføjer afhængigheder - du kan oprette cykliske afhængigheder, som vil forårsage en kompileringsfejl.

I modsætning til afhængigheder er meddelelser ikke transitive. Følgende regler gælder for underretninger:

  • Hvis ressourcen modtager en meddelelse, opdateres den. Opdateringshandlingerne afhænger af ressourcetypen − exec kører kommandoen, tjeneste genstarter tjenesten, pakke geninstallerer pakken. Hvis ressourcen ikke har en opdateringshandling defineret, sker der ikke noget.
  • Under en kørsel af Puppet opdateres ressourcen ikke mere end én gang. Dette er muligt, fordi meddelelser inkluderer afhængigheder, og afhængighedsgrafen ikke indeholder cyklusser.
  • Hvis Puppet ændrer en ressources tilstand, sender ressourcen meddelelser til alle ressourcer, der abonnerer på den.
  • Hvis en ressource opdateres, sender den meddelelser til alle ressourcer, der abonnerer på den.

Håndtering af uspecificerede parametre

Som regel, hvis en ressourceparameter ikke har en standardværdi, og denne parameter ikke er angivet i manifestet, vil Puppet ikke ændre denne egenskab for den tilsvarende ressource på noden. For eksempel hvis en ressource af typen fil parameter ikke angivet owner, så vil Puppet ikke ændre ejeren af ​​den tilsvarende fil.

Introduktion til klasser, variabler og definitioner

Antag, at vi har flere noder, der har den samme del af konfigurationen, men der er også forskelle - ellers kunne vi beskrive det hele i én blok node {}. Selvfølgelig kan du blot kopiere identiske dele af konfigurationen, men generelt er det en dårlig løsning - konfigurationen vokser, og hvis du ændrer den generelle del af konfigurationen, skal du redigere det samme mange steder. Samtidig er det nemt at lave en fejl, og generelt blev DRY (ikke gentag dig selv) princippet opfundet af en grund.

For at løse dette problem er der et sådant design som klasse.

klasser

Klasse er en navngivet blok af poppet-kode. Klasser er nødvendige for at genbruge kode.

Først skal klassen beskrives. Selve beskrivelsen tilføjer ingen ressourcer nogen steder. Klassen er beskrevet i manifester:

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

Herefter kan klassen bruges:

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

Et eksempel fra den forrige opgave - lad os flytte installationen og konfigurationen af ​​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 det foregående eksempel er slet ikke fleksibel, fordi den altid bringer den samme nginx-konfiguration. Lad os lave stien til konfigurationsvariablen, så kan denne klasse bruges til at installere nginx med enhver konfiguration.

Det kan lade sig gøre ved hjælp af variabler.

Bemærk: variabler i Puppet er uforanderlige!

Derudover kan en variabel kun tilgås, efter at den er blevet erklæret, ellers bliver værdien af ​​variablen undef.

Eksempel på arbejde med variable:

# создание переменных
$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 navnerum, og variablerne har derfor synlighedsområde: En variabel med samme navn kan defineres i forskellige navneområder. Når værdien af ​​en variabel løses, søges variablen i det aktuelle navneområde, derefter i det omsluttende navneområde, og så videre.

Eksempler på navneområder:

  • global - variabler uden for klasse- eller nodebeskrivelsen går derhen;
  • nodenavneområde i nodebeskrivelsen;
  • klassenavneområde i klassebeskrivelsen.

For at undgå tvetydighed, når du får adgang til en variabel, kan du angive navneområdet i variabelnavnet:

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

Lad os blive enige om, at stien til nginx-konfigurationen ligger i variablen $nginx_conf_source. Så ser klassen sådan ud:

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
}

Det givne eksempel er dog dårligt, fordi der er en vis "hemmelig viden" om, at der et eller andet sted inde i klassen bruges en variabel med sådan et navn. Det er meget mere korrekt at gøre denne viden generel - klasser kan have parametre.

Klasse parametre er variabler i klassens navneområde, er de angivet i klassehovedet og kan bruges som almindelige variabler i klassens krop. Parameterværdier angives ved brug af klassen i manifestet.

Parameteren kan indstilles til en standardværdi. Hvis en parameter ikke har en standardværdi, og værdien ikke er indstillet, når den bruges, vil det forårsage en kompileringsfejl.

Lad os parametrisere klassen fra eksemplet ovenfor og tilføje to parametre: den første, påkrævet, er stien til konfigurationen, og den anden, valgfri, er navnet på pakken med nginx (i Debian, for eksempel, er der 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 bruges typisk til at validere parameterværdier, der overføres til klasser og definitioner. Hvis den beståede parameter ikke matcher den angivne type, vil der opstå en kompileringsfejl.

Typen skrives umiddelbart før parameternavnet:

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

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

Hver klasse er en ressource af typen klasse. Som med enhver anden type ressource kan der ikke være to forekomster af den samme klasse på den samme node.

Hvis du forsøger at tilføje en klasse til den samme node to gange vha class { 'classname':} (ingen forskel, med forskellige eller identiske parametre), vil der være en kompileringsfejl. Men hvis du bruger en klasse i ressourcestilen, kan du straks eksplicit indstille alle dens parametre i manifestet.

Men hvis du bruger include, så kan klassen tilføjes så mange gange det ønskes. Faktum er, at include er en idempotent funktion, der kontrollerer, om en klasse er blevet tilføjet til biblioteket. Hvis klassen ikke er i mappen, tilføjer den den, og hvis den allerede eksisterer, gør den intet. Men i tilfælde af brug include Du kan ikke indstille klasseparametre under klassedeklaration - alle nødvendige parametre skal indstilles i en ekstern datakilde - Hiera eller ENC. Vi vil tale om dem i den næste artikel.

Definerer

Som det blev sagt i forrige blok, kan den samme klasse ikke være til stede på en node mere end én gang. I nogle tilfælde skal du dog være i stand til at bruge den samme kodeblok med forskellige parametre på den samme node. Der er med andre ord behov for en egen ressourcetype.

For at installere PHP-modulet gør vi f.eks. følgende i Avito:

  1. Installer pakken med dette modul.
  2. Lad os oprette en konfigurationsfil til dette modul.
  3. Vi opretter et symbollink til konfigurationen for php-fpm.
  4. Vi opretter et symbollink til konfigurationen for php cli.

I sådanne tilfælde vil et design som f.eks Definere (definer, defineret type, defineret ressourcetype). En Define ligner en klasse, men der er forskelle: For det første er hver Define en ressourcetype, ikke en ressource; for det andet har hver definition en implicit parameter $title, hvor ressourcenavnet går hen, når det er deklareret. Ligesom ved klasser skal der først beskrives en definition, hvorefter den kan bruges.

Et forenklet eksempel med et modul til 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 nemmeste måde at fange Duplicate-deklarationsfejlen på er i Define. Dette sker, hvis en definition har en ressource med et konstant navn, og der er to eller flere forekomster af denne definition på en node.

Det er nemt at beskytte dig selv mod dette: alle ressourcer inde i definitionen skal have et navn afhængigt af $title. Et alternativ er idempotent tilføjelse af ressourcer; i det enkleste tilfælde er det nok at flytte ressourcerne, der er fælles for alle forekomster af definitionen til en separat klasse og inkludere denne klasse i definitionen - funktion include idempotent.

Der er andre måder at opnå idempotens på, når man tilføjer ressourcer, nemlig ved at bruge funktioner defined и ensure_resources, men jeg fortæller dig om det i næste afsnit.

Afhængigheder og meddelelser for klasser og definitioner

Klasser og definitioner tilføjer følgende regler til håndtering af afhængigheder og meddelelser:

  • afhængighed af en klasse/define tilføjer afhængigheder til alle ressourcer i klassen/define;
  • en klasse/definer afhængighed tilføjer afhængigheder til alle klasse/definer ressourcer;
  • class/define notification underretter alle ressourcer i klassen/define;
  • class/define-abonnement abonnerer på alle ressourcer i klassen/define.

Betingede erklæringer og vælgere

Dokumentation her.

if

Det er enkelt her:

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

medmindre

medmindre er et hvis omvendt: kodeblokken vil blive udført, hvis udtrykket er falsk.

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

tilfælde

Her er heller ikke noget kompliceret. Du kan bruge regulære værdier (strenge, tal osv.), regulære udtryk og datatyper som værdier.

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

Vælgere

En selektor er en sprogkonstruktion, der ligner case, men i stedet for at udføre en kodeblok, returnerer den en værdi.

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

moduler

Når konfigurationen er lille, kan den nemt opbevares i ét manifest. Men jo flere konfigurationer vi beskriver, jo flere klasser og noder er der i manifestet, det vokser, og det bliver ubelejligt at arbejde med.

Derudover er der problemet med genbrug af kode – når al koden er i ét manifest, er det svært at dele denne kode med andre. For at løse disse to problemer har Puppet en enhed kaldet moduler.

moduler - disse er sæt af klasser, definitioner og andre Puppet-enheder placeret i en separat mappe. Med andre ord er et modul et selvstændigt stykke Puppet-logik. For eksempel kan der være et modul til at arbejde med nginx, og det vil indeholde hvad og kun hvad der skal til for at arbejde med nginx, eller der kan være et modul til at arbejde med PHP og så videre.

Moduler er versionerede, og modulers afhængighed af hinanden understøttes også. Der er et åbent lager af moduler - Puppet Forge.

På dukkeserveren er moduler placeret i modulunderbiblioteket i rodmappen. Inde i hvert modul er der et standard biblioteksskema - manifester, filer, skabeloner, lib og så videre.

Filstruktur i et modul

Roden af ​​modulet kan indeholde følgende mapper med beskrivende navne:

  • manifests - den indeholder manifester
  • files - den indeholder filer
  • templates - den indeholder skabeloner
  • lib - den indeholder Ruby-kode

Dette er ikke en komplet liste over mapper og filer, men det er nok til denne artikel for nu.

Navne på ressourcer og navne på filer i modulet

Dokumentation her.

Ressourcer (klasser, definitioner) i et modul kan ikke navngives, hvad du vil. Derudover er der en direkte overensstemmelse mellem navnet på en ressource og navnet på den fil, hvor Puppet vil lede efter en beskrivelse af den ressource. Hvis du overtræder navnereglerne, så finder Puppet simpelthen ikke ressourcebeskrivelsen, og du får en kompileringsfejl.

Reglerne er enkle:

  • Alle ressourcer i et modul skal være i modulnavnerummet. Hvis modulet kaldes foo, så skal alle ressourcer i den navngives foo::<anything>, eller bare foo.
  • Ressourcen med navnet på modulet skal være i filen init.pp.
  • For andre ressourcer er filnavneskemaet som følger:
    • præfikset med modulnavnet kasseres
    • alle dobbelte koloner, hvis nogen, erstattes med skråstreger
    • udvidelse tilføjes .pp

Jeg vil demonstrere med et eksempel. Lad os sige, at jeg skriver et modul nginx. Den indeholder følgende ressourcer:

  • 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.

Skabeloner

Du ved helt sikkert selv, hvad skabeloner er, jeg vil ikke beskrive dem i detaljer her. Men jeg forlader det for en sikkerheds skyld link til Wikipedia.

Sådan bruges skabeloner: Betydningen af ​​en skabelon kan udvides ved hjælp af en funktion template, som sendes vejen til skabelonen. For ressourcer af typen fil bruges sammen med parameteren content. For eksempel sådan her:

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

Se stien <modulename>/<filename> indebærer fil <rootdir>/modules/<modulename>/templates/<filename>.

Derudover er der en funktion inline_template — den modtager skabelonteksten som input, ikke filnavnet.

Inden for skabeloner kan du bruge alle Puppet-variabler i det aktuelle omfang.

Puppet understøtter skabeloner i ERB- og EPP-format:

Kort om ERB

Kontrolstrukturer:

  • <%= ВЫРАЖЕНИЕ %> — indsæt udtrykkets værdi
  • <% ВЫРАЖЕНИЕ %> — beregn værdien af ​​et udtryk (uden at indsætte det). Betingede udsagn (hvis) og loops (hver) går normalt her.
  • <%# КОММЕНТАРИЙ %>

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

For at få adgang til variabler fra manifestet skal du tilføje @ til variabelnavnet. For at fjerne et linjeskift, der vises efter en kontrolkonstruktion, skal du bruge et afsluttende tag -%>.

Eksempel på brug af skabelonen

Lad os sige, at jeg skriver et modul til at styre ZooKeeper. Klassen, der er ansvarlig for at oprette konfigurationen, ser sådan her ud:

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 skabelon zoo.cfg.erb - Altså:

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

Ofte afhænger den specifikke del af konfigurationen af, hvad der i øjeblikket sker på noden. For eksempel, afhængigt af hvad Debian-udgivelsen er, skal du installere en eller anden version af pakken. Du kan overvåge alt dette manuelt, omskrivning af manifester, hvis noder ændres. Men dette er ikke en seriøs tilgang; automatisering er meget bedre.

For at få information om noder har Puppet en mekanisme kaldet fakta. Fakta - dette er information om noden, tilgængelig i manifester i form af almindelige variabler i det globale navneområde. For eksempel værtsnavn, operativsystemversion, processorarkitektur, liste over brugere, liste over netværksgrænseflader og deres adresser og meget, meget mere. Fakta er tilgængelige i manifester og skabeloner som almindelige variabler.

Et eksempel på at arbejde med fakta:

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

Formelt set har et faktum et navn (streng) og en værdi (forskellige typer er tilgængelige: strenge, arrays, ordbøger). Spise sæt indbyggede fakta. Du kan også skrive din egen. Faktasamlere er beskrevet lignende funktioner i Rubyeller hvordan eksekverbare filer. Fakta kan også præsenteres i skemaet tekstfiler med data på noderne.

Under drift kopierer dukkeagenten først alle tilgængelige faktasamlere fra pappetserveren til noden, hvorefter den starter dem og sender de indsamlede fakta til serveren; Herefter begynder serveren at kompilere kataloget.

Fakta i form af eksekverbare filer

Sådanne fakta er placeret i moduler i biblioteket facts.d. Selvfølgelig skal filerne være eksekverbare. Når de køres, skal de udsende information til standardoutput i enten YAML- eller nøgle=værdi-format.

Glem ikke, at fakta gælder for alle noder, der styres af den poppet-server, som dit modul er installeret på. Derfor skal du i scriptet sørge for at kontrollere, at systemet har alle de programmer og filer, der er nødvendige for, at dit faktum fungerer.

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

Rubin fakta

Sådanne fakta er placeret i moduler i biblioteket 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

Sådanne fakta er placeret på noder i mappen /etc/facter/facts.d i gammel Dukke el /etc/puppetlabs/facts.d i den nye Puppet.

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

At komme til fakta

Der er to måder at nærme sig fakta på:

  • gennem ordbogen $facts: $facts['fqdn'];
  • ved at bruge faktanavnet som variabelnavnet: $fqdn.

Det er bedst at bruge en ordbog $facts, eller endnu bedre, angive det globale navneområde ($::facts).

Her er det relevante afsnit af dokumentationen.

Indbyggede variabler

Udover fakta er der også nogle variabler, tilgængelig i det globale navneområde.

  • pålidelige fakta — variabler, der er taget fra klientens certifikat (da certifikatet normalt udstedes på en poppet-server, kan agenten ikke bare tage og ændre sit certifikat, så variablerne er "trusted")): navnet på certifikatet, navnet på vært og domæne, udvidelser fra certifikatet.
  • serverfakta – variabler relateret til information om serveren – version, navn, server IP-adresse, miljø.
  • agent fakta — variabler tilføjet direkte af puppet-agent og ikke af fakta — certifikatnavn, agentversion, puppet-version.
  • master variabler - Pappetmaster variabler (sic!). Det er omtrent det samme som i serverfakta, plus konfigurationsparameterværdier er tilgængelige.
  • compiler variabler — kompilatorvariabler, der adskiller sig i hvert omfang: navnet på det aktuelle modul og navnet på det modul, hvori det aktuelle objekt blev tilgået. De kan for eksempel bruges til at kontrollere, at dine private klasser ikke bliver brugt direkte fra andre moduler.

Tilføjelse 1: hvordan kører og fejlretter man alt dette?

Artiklen indeholdt mange eksempler på marionetkode, men fortalte os overhovedet ikke, hvordan man kører denne kode. Nå, jeg retter mig selv.

En agent er nok til at køre Puppet, men i de fleste tilfælde skal du også bruge en server.

agent

I hvert fald siden version XNUMX, puppet-agent-pakker fra officielle Puppetlabs-depot indeholder alle afhængighederne (ruby og de tilsvarende ædelstene), så der er ingen installationsproblemer (jeg taler om Debian-baserede distributioner - vi bruger ikke RPM-baserede distributioner).

I det enkleste tilfælde, for at bruge dukkekonfigurationen, er det nok at starte agenten i serverløs tilstand: forudsat at dukkekoden er kopieret 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 at sætte serveren op og køre agenter på noderne i dæmontilstand - så vil de en gang hver halve time anvende den konfiguration, der er downloadet fra serveren.

Du kan efterligne push-modellen af ​​arbejde - gå til den node, du er interesseret i, og start sudo puppet agent -t. Nøgle -t (--test) indeholder faktisk flere muligheder, der kan aktiveres individuelt. Disse muligheder omfatter følgende:

  • kør ikke i dæmontilstand (som standard starter agenten i dæmontilstand);
  • lukke ned efter anvendelse af kataloget (som standard vil agenten fortsætte med at arbejde og anvende konfigurationen en gang hver halve time);
  • skrive en detaljeret arbejdslog;
  • vise ændringer i filer.

Agenten har en driftstilstand uden ændringer - du kan bruge den, når du ikke er sikker på, at du har skrevet den korrekte konfiguration og vil tjekke, hvad agenten præcist vil ændre under driften. Denne tilstand aktiveres af parameteren --noop på kommandolinjen: sudo puppet agent -t --noop.

Derudover kan du aktivere værkets fejlfindingslog - i den skriver marionet om alle de handlinger, den udfører: om den ressource, den i øjeblikket behandler, om parametrene for denne ressource, om hvilke programmer den starter. Dette er selvfølgelig en parameter --debug.

Server

Jeg vil ikke overveje den fulde opsætning af pappetserveren og implementering af kode til den i denne artikel; Jeg vil kun sige, at der ud af boksen er en fuldt funktionel version af serveren, der ikke kræver yderligere konfiguration for at arbejde med et lille antal noder (f.eks. op til hundrede). Et større antal noder vil kræve tuning - som standard starter puppetserver ikke mere end fire arbejdere, for større ydeevne skal du øge deres antal og glem ikke at øge hukommelsesgrænserne, ellers vil serveren samle skrald det meste af tiden.

Kodeimplementering - hvis du har brug for det hurtigt og nemt, så se (på r10k)[https://github.com/puppetlabs/r10k], for små installationer burde det være ganske nok.

Tillæg 2: Retningslinjer for kodning

  1. Placer al logik i klasser og definitioner.
  2. Hold klasser og definitioner i moduler, ikke i manifester, der beskriver noder.
  3. Brug fakta.
  4. Lav ikke hvis baseret på værtsnavne.
  5. Tilføj gerne parametre for klasser og definitioner - dette er bedre end implicit logik gemt i kroppen af ​​klassen/define.

Jeg vil forklare, hvorfor jeg anbefaler at gøre dette i den næste artikel.

Konklusion

Lad os slutte af med introduktionen. I den næste artikel vil jeg fortælle dig om Hiera, ENC og PuppetDB.

Kun registrerede brugere kan deltage i undersøgelsen. Log ind, Vær venlig.

Faktisk er der meget mere materiale - jeg kan skrive artikler om følgende emner, stemme på det, du kunne være interesseret i at læse om:

  • 59,1 %Avancerede marionetkonstruktioner - noget lort på næste niveau: sløjfer, kortlægning og andre lambda-udtryk, ressourcesamlere, eksporterede ressourcer og kommunikation mellem værter via Puppet, tags, udbydere, abstrakte datatyper.13
  • 31,8 %"Jeg er min mors admin" eller hvordan vi i Avito blev venner med flere poppet-servere af forskellige versioner, og i princippet den del om at administrere poppet-serveren.7
  • 81,8 %Sådan skriver vi dukkekode: instrumentering, dokumentation, test, CI/CD.18

22 brugere stemte. 9 brugere undlod at stemme.

Kilde: www.habr.com