Puppet er et konfigurasjonsstyringssystem. Det brukes til å bringe verter til ønsket tilstand og opprettholde denne tilstanden.
Jeg har jobbet med Puppet i over fem år. Denne teksten er i hovedsak en oversatt og omorganisert kompilering av nøkkelpunkter fra den offisielle dokumentasjonen, som vil hjelpe nybegynnere raskt å forstå essensen av Puppet.

Grunnleggende informasjon
Puppet opererer i en klient-server arkitektur, selv om det også støtter en serverløs variant med begrenset funksjonalitet.
Pull-modellen benyttes: som standard henvender klientene seg til serveren hver halvtime for å hente konfigurasjon og bruke den. Hvis du har arbeidet med Ansible, bruker det en annen, push-modell: administratoren initierer prosessen med å bruke konfigurasjon, kundene vil ikke bruke noe på egen hånd.
Ved nettverksinteraksjon brukes toveis TLS-kryptering: både serveren og klienten har sine egne private nøkler og tilhørende sertifikater. Normalt utsteder serveren sertifikater for klientene, men det er i prinsippet mulig å bruke en extern CA.
Bli kjent med manifestene
I Puppet-termer til Puppet-serveren tilkobles noder (noder). Konfigurasjonen for nodene skrives i manifestene på et spesiallaget programmeringsspråk - Puppet DSL.
Puppet DSL er et deklarativt språk. Det beskriver ønsket tilstand for en node i form av erklæringer om individuelle ressurser, for eksempel:
- Filen eksisterer, og den har et bestemt innhold.
- Pakke er installert.
- Tjenesten kjører.
Ressurser kan være sammenkoblede:
- Det er avhengigheter som påvirker rekkefølgen av anvendelse av ressurser.
For eksempel, "først installer pakken, så endre konfigurasjonsfilen, deretter start tjenesten." - Det er varsler - hvis en ressurs endres, sender den varsler til ressursene som er påmeldt den.
For eksempel, hvis konfigurasjonsfilen endres, kan tjenesten automatisk startes på nytt.
I tillegg har Puppet DSL funksjoner og variabler, samt betingede operatorer og velgere. Ulike malmekanismer som EPP og ERB støttes også.
Puppet er skrevet i Ruby, så mange strukturer og termer er hentet derfra. Ruby lar deg utvide Puppet - legge til kompleks logikk, nye ressurs typer, funksjoner.
Under Puppet operation, manifests for each specific node on the server are compiled into a directory. Katalog — this is a list of resources and their interconnections after evaluating the values of functions, variables, and expanding conditional operators.
Syntax and code style
Here are the sections of the official documentation that will help you understand the syntax if the provided examples are not enough:
Here is an example of what a manifest looks like:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}Indentation and line breaks are not mandatory parts of the manifest, but a recommended . Brief summary:
- Two-space indentation, tabs are not used.
- Curly braces are spaced, colons are not.
- Commas after each parameter, including the last one. Each parameter should be on a separate line. An exception is made for cases with no parameters and one parameter: it can be written on one line and without a comma (i.e.
resource { 'title': }ogresource { 'title': param => value }). - Arrows for parameters must be aligned on the same level.
- Resource relationship arrows are written before them.
File locations on the puppetserver
For further explanations, I will introduce the concept of "root directory." The root directory is the directory where the Puppet configuration for a specific node is located.
The root directory varies depending on the version of Puppet and the use of environments. Environments are independent configurations stored in separate directories. They are commonly used with Git; in that case, environments are created from Git branches. Accordingly, each node is situated within some environment. This is configured either on the node itself or in the ENC, which I will discuss in the next article.
- In version three ("old Puppet"), the base directory was
/etc/puppet. The use of environments is optional — for example, we do not use them with the old Puppet. If environments are used, they are usually stored in/etc/puppet/environments, the root directory will be the environment directory. If environments are not used, the root directory will be the base one. - Starting with version four ("new Puppet"), the use of environments has become mandatory, and the base directory was moved to
/etc/puppetlabs/code. Derfor lagres omgivelsene i/etc/puppetlabs/code/environments, rotkatalogen er kataloget for omgivelsene.
I rotkatalogen må det være en underkatalog manifester, der det ligger en eller flere manifestfiler som beskriver nodene. I tillegg må det være en underkatalog modules, der modulene ligger. Hva moduler er, skal jeg fortelle om litt senere. I tillegg kan det i den gamle Puppet også være en underkatalog filer, der det ligger forskjellige filer som vi kopierer til nodene. I den nye Puppet er derimot alle filene flyttet til modulene.
Manifestfiler har filendelsen .pp.
Et par praktiske eksempler
Beskrivelse av noden og ressursen på den
På noden server1.testdomain må det opprettes en fil /etc/issue med innholdet Debian GNU/Linux n l. Filen må tilhøre bruker og gruppe root, tilgangsrettighetene må være 644.
Skriver manifest:
node 'server1.testdomain' { # konfigurasjonsblokken som tilhører noden server1.testdomain
file { ' /etc/issue': # beskriver filen /etc/issue
ensure => present, # denne filen må eksistere
content => 'Debian GNU/Linux n l', # den må ha slikt innhold
owner => root, # bruker-eier
group => root, # gruppe-eier
mode => '0644', # rettighetene på filen. De er angitt som en streng (i anførselstegn) fordi et tall med 0 i begynnelsen ellers vil bli tolket som skrevet i oktalsystemet, og alt vil gå galt
}
}Ressursforhold på noden
På noden server2.testdomain må nginx være startet, som arbeider med den forhåndsforberedte konfigurasjonen.
Dekomponerer oppgaven:
- Det må installeres en pakke
nginx. - Det må kopieres konfigurasjonsfiler fra serveren.
- Det må startes en tjeneste
nginx. - Ved oppdatering av konfigurasjonen må tjenesten startes på nytt.
Skriver manifest:
node 'server2.testdomain' { # konfigurasjonsblokk for noden server2.testdomain
package { 'nginx': # her beskriver vi nginx-pakken
ensure => installed, # den må være installert
}
# Den direkte pilen (->) indikerer at ressursen nedenfor må
# opprettes etter ressursen beskrevet ovenfor.
# Slike avhengigheter er transitive.
-> file { '/etc/nginx': # her beskriver vi filen /etc/nginx
ensure => directory, # dette skal være en katalog
source => 'puppet:///modules/example/nginx-conf', # innholdet må hentes fra puppets server på angitt adresse
recurse => true, # kopiere filer rekursivt
purge => true, # slette ekstra filer (de som ikke er i kilden)
force => true, # slette ekstra kataloger
}
# Den bølgete pilen (~>) indikerer at ressursen nedenfor må
# abonnere på endringer fra ressursen beskrevet ovenfor.
# Den bølgete pilen inkluderer den direkte (->).
~> service { 'nginx': # her beskriver vi nginx-tjenesten
ensure => running, # den må være i gang
enable => true, # den må starte automatisk ved systemoppstart
}
# Når ressursen av typen service får varsling,
# blir den aktuelle tjenesten startet på nytt.
}For at dette skal fungere, trenger vi omtrent en slik filplassering på puppets server:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.confRessurstyper
Den komplette listen over støttede ressurs typer finnes , her vil jeg beskrive fem grunnleggende typer som i min erfaring er tilstrekkelige for å løse de fleste oppgaver.
file
Håndterer filer, kataloger, symboliske lenker, innholdet deres, og tilgangsrettigheter.
Parametere:
- navn på ressurs — sti til filen (valgfritt)
- path — sti til filen (hvis ikke angitt i navnet)
- ensure — type fil:
fraværende— slette filenpresent— må være en fil av hvilken som helst type (hvis filen ikke er der, vil en vanlig fil bli opprettet)file— vanlig fildirectory— kataloglink— symbolsk lenke
- content — innholdet i filen (gjelder kun for vanlige filer, kan ikke brukes sammen med kilde eller mål)
- kilde — sti til kilden hvor innholdet i filen skal kopieres fra (kan ikke brukes sammen med content eller mål). Kan angis både som URI med schema
puppet:(da vil filer fra puppets server bli brukt), samt med schemahttp:(jeg håper det er klart hva som skjer i dette tilfellet), og til og med med schemafile:eller som en absolut sti uten schema (da brukes filen fra lokal FS på noden) - mål — hvor den symboliske lenken skal peke (kan ikke brukes sammen med content eller kilde)
- owner — bruker som filen skal tilhøre
- gruppe — gruppe som filen skal tilhøre
- modus — rettigheter for filen (i form av en streng)
- rekursiv — inkluderer rekursiv behandling av kataloger
- purge — inkluderer sletting av filer som ikke er beskrevet i Puppet
- force — inkluderer sletting av kataloger som ikke er beskrevet i Puppet
pakke
Installerer og fjerner pakker. Kan håndtere varsler — gjeninstallerer pakken dersom parameter er angitt reinstall_on_refresh.
Parametere:
- navn på ressurs — pakkens navn (valgfritt)
- name — pakkens navn (hvis ikke angitt i navnet)
- provider — pakkeadministrator som skal brukes
- ensure — ønsket tilstand for pakken:
present,installert— hvilken som helst versjon installertlatest— siste versjon installertfraværende— fjernet (apt-get remove)purged— fjernet sammen med konfigurasjonsfilene (apt-get purge)held— pakken er blokkert (apt-mark hold)hvilken som helst annen streng— angitt versjon er installert
- reinstall_on_refresh — hvis
true, så når varslet mottas, vil pakken bli gjeninstallert. Nyttig for source-baserte distribusjoner, hvor pakker kan trenge å bli rekonstruert ved endring av byggparametere. Som standardfalse.
tjeneste
Håndterer tjenester. Kan håndtere varsler — gjenstarter tjenesten.
Parametere:
- navn på ressurs — tjenesten som skal håndteres (valgfritt)
- name — tjenesten som skal håndteres (hvis ikke angitt i navnet)
- ensure — ønsket tilstand for tjenesten:
løping— kjørerstoppet— stoppet
- enable — håndterer oppstartsmuligheten for tjenesten:
true— automatisk oppstart aktivert (systemctl enable)mask— maskert (systemctl mask)false— automatisk oppstart deaktivert (systemctl disable)
- restart — kommando for å gjenstarte tjenesten
- status — kommando for å sjekke status for tjenesten
- hasrestart — angi om init-skriptet for tjenesten støtter gjenstart. Hvis
falseog parameter er angitt restart — brukes verdien av denne parameteren. Hvisfalseog parameter restart ikke er angitt — tjenesten stoppes og startes på nytt for gjenstart (men i systemd brukes kommandoensystemctl restart). - hasstatus — angi om init-skriptet for tjenesten støtter kommandoen
status. Hvisfalse, så brukes verdien av parameteren status. Som standardtrue.
exec
Kjører eksterne kommandoer. Hvis ingen parametere angis creates, onlyif, unless eller refreshonly, vil kommandoen kjøres ved hver kjøring av Puppet. Kan håndtere varsler — kjører kommandoen.
Parametere:
- navn på ressurs — kommandoen som skal utføres (valgfritt)
- command — kommandoen som skal utføres (hvis ikke angitt i navnet)
- path — veier for å søke etter det kjørbare programmet
- onlyif — hvis kommandoen angitt i denne parameteren avsluttes med null returkode, vil hovedkommandoen bli utført
- unless — hvis kommandoen angitt i denne parameteren avsluttes med en annen returkode enn null, vil hovedkommandoen bli utført
- creates — hvis filen angitt i denne parameteren ikke eksisterer, vil hovedkommandoen bli utført
- refreshonly — hvis
true, så vil kommandoen bli kjørt bare når denne exec får et varsel fra andre ressurser - cwd — katalogen der kommandoen skal kjøres
- , passord: — brukeren som skal kjøre kommandoen
- provider — hvordan kommandoen skal kjøres:
- posix — oppretter bare en underprosess, må spesifiseres path
- shell — kommandoen kjøres i et skall
/bin/sh, kan være valgfritt path, kan bruke globbing, pipes og andre shell-funksjoner. Vanligvis bestemmes dette automatisk hvis det finnes spesialtegn (|,;,&&,||og så videre).
cron
Administrerer cron-jobber.
Parametere:
- navn på ressurs — bare en eller annen identifikator
- ensure — tilstanden til cron-jobben:
present— opprett hvis den ikke eksistererfraværende— slett hvis den eksisterer
- command — hvilken kommando som skal kjøres
- miljø — i hvilket miljø kommandoen skal kjøres (liste over miljøvariabler og deres verdier gjennom
=) - , passord: — fra hvilken bruker kommandoen skal kjøres
- minutt, time, ukedag, måned, måneddag — når å kjøre cron. Hvis noen av disse attributtene ikke er angitt, vil verdien i crontab være
*.
I Puppet 6.0 cron som om i puppetserver, så det finnes ingen dokumentasjon på det generelle nettstedet. Men det i puppet-agent, så det er ikke nødvendig å installere det separat. Dokumentasjonen for det kan sees , eller .
Om ressursene generelt
Krav til unikhet for ressurser
Den vanligste feilen vi møter er — Duplikatdeklarasjon. Denne feilen oppstår når to eller flere ressurser av samme type med samme navn havner i katalogen.
Så jeg gjentar: i manifestene for en node, skal det ikke være ressurser av samme type med samme navn (tittel)!
Noen ganger er det nødvendig å installere pakker med samme navn, men med forskjellige pakkebehandlere. I så fall må man bruke parameteren name, for å unngå feilen:
package { 'ruby-mysql':
ensure => installed,
name => 'mysql',
provider => 'gem',
}
package { 'python-mysql':
ensure => installed,
name => 'mysql',
provider => 'pip',
}I andre typer ressurser finnes lignende parametere som bidrar til å unngå duplisering, — name til tjeneste, command til exec, og så videre.
Metaparametere
Noen spesielle parametere finnes for hver ressursstype, uavhengig av dens art.
Fullstendig liste over metaparametere .
Kort liste:
- require — i denne parameteren angis hvilke ressurser den bestemte ressursen er avhengig av.
- before — i denne parameteren angis hvilke ressurser som er avhengige av den bestemte ressursen.
- subscribe — i denne parameteren angis hvilke ressurser den bestemte ressursen mottar varsler fra.
- notify — i denne parameteren angis hvilke ressurser som mottar varsler fra den bestemte ressursen.
Alle de nevnte metaparametrene kan ta enten én ressursreferanse eller et array av referanser i firkantede parenteser.
Ressursreferanser
En referanse til en ressurs er rett og slett en omtale av ressursen. De brukes hovedsakelig for å angi avhengigheter. En referanse til en ikke-eksisterende ressurs vil forårsake en kompilasjonsfeil.
Syntaksen for en referanse er som følger: ressurs type med stor forbokstav (hvis navnet på type inneholder dobbel kolon, skrives hver del av navnet mellom kolonene med stor forbokstav), deretter i firkantede parenteser navnet på ressursen (størrelsen på navnet endres ikke!). Det skal ikke være mellomrom, firkantede parenteser skrives direkte etter navnet på typen.
Eksempel:
file { '/file1': ensure => present }
file { '/file2':
ensure => directory,
before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']Avhengigheter og varsler
Som nevnt tidligere, er enkle avhengigheter mellom ressurser transitive. Vær også forsiktig når du setter opp avhengigheter — det kan oppstå sykliske avhengigheter, noe som vil føre til en kompilasjonsfeil.
I motsetning til avhengigheter er varsler ikke transitive. For varsler gjelder følgende regler:
- Hvis en ressurs mottar et varsel, blir den oppdatert. Handlingene ved oppdatering avhenger av hvilken type ressurs det er — exec den kjører en kommando, tjeneste den gjenstarter en tjeneste, pakke den gjeninstallerer en pakke. Hvis ingen handling er definert for oppdateringen av ressursen, skjer det ingenting.
- I løpet av én kjøring av Puppet oppdateres en ressurs ikke mer enn én gang. Dette er mulig fordi varsler inkluderer avhengigheter, og avhengighetsgrafen inneholder ikke sykluser.
- Hvis Puppet endrer tilstanden til en ressurs, sender ressursen varsler til alle som er abonnert på den.
- Hvis ressursen oppdateres, sender den varsler til alle som er abonnert på den.
Behandling av ikke angitte parametere
Som regel, hvis en ressursparameter ikke har en standardverdi, og parameteren ikke er angitt i manifestet, vil ikke Puppet endre denne egenskapen på den aktuelle ressursen på noden. For eksempel, hvis ressursen av typen file ikke har parameter owner, vil ikke Puppet endre eieren til den aktuelle filen.
Introduksjon til klasser, variabler og definisjoner
La oss anta at vi har flere noder med identiske deler av konfigurasjonen, men det er også forskjeller — ellers kunne vi ha beskrevet alt dette i én blokk. node {}. Selvfølgelig kan man bare kopiere de identiske delene av konfigurasjonen, men generelt er dette en dårlig løsning — konfigurasjonen vokser, og når den felles delen av konfigurasjonen endres, må man redigere det samme på mange steder. Det er lett å gjøre feil, og prinsippet om DRY (don’t repeat yourself) ble ikke laget for ingenting.
For å løse et slikt problem finnes det en konstruksjon som klasse.
Klasser
— er en navngitt blokk med Puppet-kode. Klasser er nødvendige for gjenbruk av kode.
Først må klassen beskrives. Selve beskrivelsen legger ikke til noen ressurser noe sted. Klassen beskrives i manifestene:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}Etter dette kan klassen brukes:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальшеEksempelet fra tidligere 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',
recurse => 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 gjøre stien til konfigurasjonen variabel, da kan denne klassen brukes til å installere nginx med hvilken som helst konfigurasjon.
Dette kan gjøres .
Merk: variabler i Puppet er uforanderlige!
I tillegg kan man bare referere til en variabel etter at den er erklært, 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}"I Puppet er det navnerom, og variablene har derfor synlighet: en variabel med samme navn kan defineres i forskjellige navnerom. Når verdien av en variabel skal bestemmes, søkes det først i det nåværende navnerommet, deretter i det omsluttende, og så videre.
Eksempler på navnerom:
- globalt — her havner variabler utenfor beskrivelsen av klassen eller noden;
- navnerommet til noden i beskrivelsen av noden;
- navnerommet til klassen i beskrivelsen av klassen.
For å unngå tvetydighet når man refererer til en variabel, kan man angi navnerommet i variabelens navn:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::varLa oss bli enige om at stien 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, # her bruker vi variabelen i stedet for en fast tekststreng
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
}Men eksemplet er dårlig fordi det er et slags „hemmelig kunnskap“ om at det et sted inne i klassen brukes en variabel med et bestemt navn. Det er mye mer korrekt å gjøre denne kunnskapen generell - klasser kan ha parametre.
Klasseparametre — er variabler i klasse-navnerommet, de settes i klassens header og kan brukes som vanlige variabler i klassens kropp. Verdiene til parametrene spesifiseres ved bruk av klassen i manifestet.
Parameteren kan få en standardverdi. Hvis parameteren ikke har en standardverdi og verdien ikke spesifiseres ved bruk, vil dette forårsake en kompilasjonsfeil.
La oss parameterisere klassen fra eksempelet ovenfor og legge til to parametere: den første, obligatoriske - stien til konfigurasjonen, og den andre, valgfrie - navnet på pakken med nginx (i Debian, for eksempel, finnes 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 er variabler typet. Det finnes . Datatyper brukes vanligvis til å validere verdier av parametere som sendes til klasser og definisjoner. Hvis den sendte parameteren ikke samsvarer med den spesifiserte typen, vil det oppstå en kompilasjonsfeil.
Typen skrives rett foran parameterens navn:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}Klasser: inkludere classname vs class{'classname':}
Hver klasse er en ressurs av typen klasse. Som med andre typer ressurser, kan ikke to instanser av samme klasse være til stede på én node.
Hvis man prøver å legge til klassen på samme node to ganger ved hjelp av class { 'classname':} (uansett om med forskjellige eller med de samme parameterne), vil det være en kompileringsfeil. Men i tilfelle av å bruke klassen i ressursstil, kan man umiddelbart spesifisere alle parameterne i manifestet.
Men hvis man bruker include, kan klassen legges til så mange ganger man vil. Poenget er at include er en idempotent funksjon som sjekker om klassen er lagt til katalogen. Hvis klassen ikke finnes i katalogen - legger den til, og hvis den allerede finnes, gjør den ingenting. Men ved å bruke include kan man ikke spesifisere klasseparameterne under klasseerklæringen - alle obligatoriske parametere må være spesifisert i en ekstern datakilde - Hiera eller ENC. Om dem skal vi snakke i neste artikkel.
Definerer
Som nevnt i forrige avsnitt, kan ikke samme klasse være til stede på en node mer enn én gang. Men i noen tilfeller må man ha muligheten til å bruke samme kodeblokk med forskjellige parametere på én node. Med andre ord, det er behov for en egen ressurs type.
For eksempel, for å installere en PHP-modul gjør vi i Avito følgende:
- Installerer pakken med denne modulen.
- Oppretter en konfigurasjonsfil for denne modulen.
- Oppretter en symlink til konfigurasjonen for php-fpm.
- Oppretter en symlink til konfigurasjonen for php cli.
I slike tilfeller brukes en konstruksjon som (define, defined type, defined resource type). Define ligner på en klasse, men det er forskjeller: for det første er hver define en ressurs type, ikke en ressurs; for det andre har hver define en implisitt parameter $title, hvor navnet på ressursen havner når den erklæres. Akkurat som med klassene, må define først beskrives før den kan brukes.
Et forenklet eksempel med modulen 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'], # Debian PHP package triggers create symlinks and restart the PHP-FPM service on their own - we don't need that, as we control both the symlinks and the service using 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' }
}Det er enklest å fange opp feilen Duplicate declaration i definisjonen. Dette skjer hvis definisjonen har en ressurs med et konstant navn, og på en node er det to eller flere instanser av denne definisjonen.
For å beskytte seg mot dette er det enkelt: Alle ressurser innenfor definisjonen bør ha navn som er avhengig av $title. Som et alternativ kan man gjøre ressursene idempotente ved enkelt å flytte felles ressurser for alle instanser av definisjonen til en egen klasse og inkludere den klassen i definisjonen - funksjonen include er idempotent.
Det finnes også andre måter å oppnå idempotens ved å legge til ressurser, nemlig ved å bruke funksjoner defined og ensure_resources, men det vil jeg fortelle om i neste serie.
Avhengigheter og varsler for klasser og definisjoner
Klasser og definisjoner legger til følgende regler for behandling av avhengigheter og varsler:
- En avhengighet fra en klasse/definisjon legger til avhengigheter fra alle ressurser i klassen/definisjonen;
- En avhengighet fra en klasse/definisjon legger til avhengigheter for alle ressurser i klassen/definisjonen;
- Et varsel fra en klasse/definisjon varsler alle ressurser i klassen/definisjonen;
- Abonnement på en klasse/definisjon abonnerer på alle ressurser i klassen/definisjonen.
Betingede operatorer og velgere
if
Her er alt enkelt:
if UTTRYKK1 {
...
} elsif UTTRYKK2 {
...
} else {
...
}unless
unless - dette er if motsatt: kodeblokken vil bli utført hvis uttrykket er usant.
unless UTTRYKK {
...
}case
Her er det heller ikke noe komplisert. Som verdier kan man bruke vanlige verdier (strenger, tall osv.), regulære uttrykk, samt datatyper.
case UTTRYKK {
VERDI1: { ... }
VERDI2, VERDI3: { ... }
default: { ... }
}Velgere
En velger er en språklig konstruksjon som ligner på case, men i stedet for å utføre en kodeblokk returnerer den en verdi.
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }Moduler
Når konfigurasjonen er liten, kan den enkelt holdes i én manifest. Men jo mer konfigurasjon vi beskriver, jo flere klasser og noder blir det i manifestet, det vokser, og det blir vanskeligere å håndtere.
I tillegg er det et problem med gjenbruk av kode — når all kode er i ett manifest, er det vanskelig å dele den koden med andre. For å løse disse to problemene finnes det en enhet i Puppet kjent som moduler.
Moduler — er sett med klasser, definisjoner og andre Puppet-enheter som er plassert i en egen katalog. Med andre ord, en modul er en uavhengig del av Puppet-logikk. For eksempel kan det være en modul for å jobbe med nginx, og i den vil det være akkurat det som er nødvendig for å jobbe med nginx, og det kan også være en modul for å jobbe med PHP, og så videre.
Moduler versjoneres, og det støttes også avhengigheter mellom moduler. Det finnes et åpent repositorium for moduler — .
På puppet-serveren ligger modulene i underkatalogen modules i rotkatalogen. Inne i hver modul er det et standard mappesystem — manifests, files, templates, lib og så videre.
Filstrukturen i en modul
I roten av modulen kan det finnes følgende kataloger med beskrivende navn:
manifester— inneholder manifestenefiler— inneholder filenetemplates— inneholder malenelib— inneholder Ruby-koden
Dette er ikke en fullstendig liste over kataloger og filer, men for denne artikkelen er det tilstrekkelig.
Ressursnavn og filnavn i modulen
Ressurser (klasser, definisjoner) i modulen kan ikke kalles hva som helst. I tillegg er det direkte samsvar mellom ressursnavnet og filnavnet der Puppet vil lete etter beskrivelsen av denne ressursen. Hvis navngivningsreglene brytes, vil Puppet rett og slett ikke finne beskrivelsen av ressursene, og det vil bli en kompilasjonsfeil.
Reglene er enkle:
- Alle ressurser i modulen må være i modulens navnerom. Hvis modulen heter
foo, må alle ressursene i den hetefoo::, eller barefoo. - Ressursen med modulens navn må være i filen
init.pp. - For de andre ressursene er navngivningsskjemaet følgende:
- prefikset med modulens navn frafalles
- alle doble kolon, om de er, blir erstattet med skråstreker
- utvidelsen legges til
.pp
Jeg vil demonstrere med et eksempel. Anta at jeg skriver et modul nginx. Den inneholder følgende ressurser:
- klasse
nginxbeskrevet i manifestetinit.pp; - klasse
nginx::servicebeskrevet i manifestetservice.pp; - definerer
nginx::serverbeskrevet i manifestetserver.pp; - definerer
nginx::server::locationbeskrevet i manifestetserver/location.pp.
Malverk
Sikkert vet du allerede hva maler er, jeg vil ikke gå i detalj her. Men for en hver tilfelle lar jeg .
Hvordan bruke maler: verdien av malen kan avsløres ved hjelp av funksjonen mal, som får banen til malen. For ressurser av typen file brukes sammen med parameteren content. For eksempel slik:
file { '/tmp/example': content => template('modulename/templatename.erb')Stien av typen / inngår filen /modules//templates/.
I tillegg finnes det en funksjon inline_template — den tar teksten til malen som input, ikke filnavnet.
Inne i malene kan man bruke alle Puppet-variabler i den gjeldende synligheten.
Puppet støtter maler i ERB- og EPP-format:
Kort om ERB
Kontrollstrukturer:
<%= ВЫРАЖЕНИЕ %>— sett inn verdien av uttrykket<% ВЫРАЖЕНИЕ %>— beregn verdien av uttrykket (uten å sette det inn). Her kommer vanlige betingelsesoperatører (if), sløyfer (each) inn.<%# КОММЕНТАРИЙ %>
Uttrykk i ERB skrives i Ruby (faktisk, ERB er Embedded Ruby).
For å få tilgang til variabler fra manifestet, må man legge til @ til variabelnavnet. For å fjerne linjeskiftet som dukker opp etter kontrollkonstruksjonen, må man bruke avslutningstegnet -%>.
Eksempel på bruk av mal
La oss anta at jeg skriver et modul for å administrere ZooKeeper. Klassen som ansvarer 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 ser slik ut:
0 -%>
server.=:::
dataDir=
=Fakta og innebygde variabler
Ofte avhenger en bestemt del av konfigurasjonen av hva som skjer på noden for øyeblikket. For eksempel, avhengig av hvilken Debian-versjon som er installert, må man installere en bestemt versjon av pakken. Man kan overvåke dette manuelt ved å omskrive manifestene ved endringer i nodene. Men dette er ikke en seriøs tilnærming; automatisering er mye bedre.
For å få informasjon om nodene i Puppet finnes det en mekanisme kalt fakta. Fakta er informasjon om noden som er tilgjengelig i manifestene i form av vanlige variabler i det globale navnerommet. For eksempel vertsnavn, operativsystemversjon, prosessorarkitektur, liste over brukere, liste over nettverksgrensesnittene og deres adresser, og mye, mye mer. Fakta er tilgjengelige i manifestene og malene som vanlige variabler.
Eksempel på arbeid med fakta:
notify { "Kjører OS ${facts['os']['name']} versjon ${facts['os']['release']['full']}": }
# en ressurs av typen notify skriver bare en melding til loggenFormelt sett har et faktum et navn (streng) og en verdi (ulike typer er tilgjengelige: strenger, matriser, ordbøker). Det finnes . Man kan også skrive egne. Faktasamlere beskrives , eller som . Også fakta kan presenteres i form av på nodene.
Under drift kopierer puppet-agenten først alle tilgjengelige faktasamlere fra puppet-serveren til noden, deretter kjører den dem og sender de innsamlede fakta til serveren; først etter dette begynner serveren å kompilere katalogen.
Fakta i form av kjørbare filer
Disse fakta legges i moduler i katalogen facts.d. Selvfølgelig må filene være kjørbare. Når de kjøres, må de skrive ut informasjon til standardutgangen i enten YAML-format eller i formatet "nøkkel=verdi".
Husk at fakta distribueres til alle noder som administreres av puppet-serveren som modulen din distribueres til. Så i skriptet, vær oppmerksom på å sjekke at alle nødvendige programmer og filer for at faktaene dine skal fungere er til stede i systemet.
#!/bin/sh
echo "testfact=success"#!/bin/sh
echo '{"testyamlfact":"success"}'Fakta i Ruby
Disse fakta legges 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
endTekstfakta
Disse fakta legges på nodene i katalogen /etc/facter/facts.d i den gamle Puppet eller /etc/puppetlabs/facts.d i den nye Puppet.
examplefact=examplevalue---
examplefact2: examplevalue2
anotherfact: anothervalueTilgang til fakta
Du kan henvende deg til fakta på to måter:
- gjennom ordboken
$facts:$facts['fqdn']; - ved å bruke navnet på fakta som variabelnavn:
$fqdn.
Det er best å bruke ordboken $facts, og enda bedre å spesifisere det globale navnerommet ($::facts).
Innebygde variabler
I tillegg til fakta, finnes det også , tilgjengelig i det globale navnerommet.
- trusted facts – variabler som hentes fra klientens sertifikat (siden sertifikatet vanligvis utstedes på puppets-serveren, kan ikke agenten bare endre sitt sertifikat, derfor er variablene 'dokumenterte'): sertifikatets navn, vertsnavn og domene, utvidelser fra sertifikatet.
- server facts – variabler som tilhører informasjonen om serveren – versjon, navn, IP-adresse til serveren, miljø.
- agent facts – variabler som legges direkte til av puppet-agenten, ikke facter – sertifikatets navn, agentversjon, puppets versjon.
- master variables – variabler fra puppet-masteren (sic!). Det er omtrent det samme som i server facts, pluss verdier av konfigurasjonsparametere er tilgjengelige.
- compiler variables – kompilatorvariabler som varierer i hver synlighet: navnet på den nåværende modulen og navnet på modulen der det ble henvist til den nåværende objekten. De kan brukes, for eksempel, til å kontrollere at dine private klasser ikke brukes direkte fra andre moduler.
Tillegg 1: hvordan kjøre og debugge dette?
Artikkelen hadde mange eksempler på puppet-kode, men det ble ikke sagt noe om hvordan denne koden kjøres. Vel, jeg retter opp i det.
For å bruke Puppet, trenger du bare agenten, men for de fleste tilfeller vil serveren også være nødvendig.
Agent
Minst fra versjon fem inneholder puppet-agent-pakkene fra alle avhengigheter (ruby og de tilsvarende gem'ene), så det er ingen problemer med installasjonen (jeg snakker om Debian-baserte distribusjoner – vi bruker ikke RPM-baserte distribusjoner).
I det enkleste tilfellet er det tilstrekkelig å kjøre agenten i 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 secondsDet er selvsagt bedre å sette opp serveren og kjøre agenter på nodene i daemon-modus — da vil de anvende konfigurasjonen som lastes ned fra serveren hvert halvtime.
Du kan imitere push-modellen — gå til noden du er interessert i og kjør sudo puppet agent -t. Nøkkelen -t (--test) inkluderer faktisk flere alternativer som kan aktiveres individuelt. Blant disse alternativene er følgende:
- ikke kjøre i daemon-modus (som standard kjører agenten i daemon-modus);
- stopp etter å ha anvendt katalogen (som standard fortsetter agenten å kjøre og vil anvende konfigurasjonen hvert halvtime);
- skrive detaljerte logger;
- vise endringer i filer.
Agenten har en modus uten endringer — den kan brukes når du er usikker på at du har skrevet riktig konfigurasjon og ønsker å sjekke hva agenten faktisk vil endre under kjøring. Denne modusen aktiveres med parameteren --noop i kommandolinjen: sudo puppet agent -t --noop.
I tillegg kan du aktivere en feilsøkingslogg — der skriver puppet om alle handlingene det utfører: om ressursen det behandler for øyeblikket, om parametrene til denne ressursen, om hvilke programmer som kjøres. Dette er selvsagt parameteren --debug.
Server
Jeg vil ikke gå inn på fullstendig konfigurasjon av puppete-serveren og distribusjon av kode på den i denne artikkelen, jeg vil bare si at det leveres en fullt fungerende versjon av serveren, som ikke krever ekstra konfigurasjon for drift med et begrenset antall noder (si, opp til hundre). Et større antall noder vil allerede kreve tuning — som standard kjører puppetserver ikke mer enn fire arbeidere, for bedre ytelse bør antallet økes og minnelimitterne justeres, ellers vil serveren bruke mesteparten av tiden på søppelsamling.
Distribusjon av kode — hvis du trenger noe raskt og enkelt, se (på r10k)[], det burde være tilstrekkelig for små installasjoner.
Tillegg 2: anbefalinger for å skrive kode
- Flytt all logikk til klasser og definisjoner.
- Hold classes and defines in modules rather than in manifests that describe nodes.
- Make use of facts.
- Avoid making if statements based on hostnames.
- Don’t hesitate to add parameters for classes and defines — it's better than implicit logic hidden in the body of a class/define.
Why I recommend this — I will explain in the next article.
Konklusjon
Let’s finish with the introduction. In the next article, I will talk about Hiera, ENC, and PuppetDB.
Kun registrerte brukere kan delta i undersøkelsen. , vær så snill.
In fact, there is much more material — I can write articles on the following topics, vote on what you would like to read about:
- 59,1%Advanced puppet constructs — some next-level stuff: cycles, mapping and other lambda expressions, resource collectors, exported resources, and inter-host communication via Puppet, tags, providers, abstract data types.
- 31,8%"I’m an admin at mom’s" or how we at Avito connected several Puppet servers of different versions, and generally a part about administering a Puppet server.
- 81,8%How we write Puppet code: tooling, documentation, testing, CI/CD.
22 users voted. 9 users abstained.
Kilde: habr.com
