Introduksjon til Puppet

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.

Introduksjon til 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 style guide. 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': } og resource { '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.conf

Ressurstyper

Den komplette listen over støttede ressurs typer finnes i dokumentasjonen, 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 filen
    • present — må være en fil av hvilken som helst type (hvis filen ikke er der, vil en vanlig fil bli opprettet)
    • file — vanlig fil
    • directory — katalog
    • link — 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 schema http: (jeg håper det er klart hva som skjer i dette tilfellet), og til og med med schema file: 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 installert
    • latest — siste versjon installert
    • fravæ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 standard false.

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ører
    • stoppet — 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 false og parameter er angitt restart — brukes verdien av denne parameteren. Hvis false og parameter restart ikke er angitt — tjenesten stoppes og startes på nytt for gjenstart (men i systemd brukes kommandoen systemctl restart).
  • hasstatus — angi om init-skriptet for tjenesten støtter kommandoen status. Hvis false, så brukes verdien av parameteren status. Som standard true.

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 eksisterer
    • fravæ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 ble fjernet fra boksen i puppetserver, så det finnes ingen dokumentasjon på det generelle nettstedet. Men det finnes i boksen i puppet-agent, så det er ikke nødvendig å installere det separat. Dokumentasjonen for det kan sees i dokumentasjonen for den femte versjonen av Puppet, eller på GitHub.

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 i Puppet-dokumentasjonen.

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

Dokumentasjonen finnes her.

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

Klasse — 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 ved hjelp av variabler.

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::var

La 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 mange datatyper. 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:

  1. Installerer pakken med denne modulen.
  2. Oppretter en konfigurasjonsfil for denne modulen.
  3. Oppretter en symlink til konfigurasjonen for php-fpm.
  4. Oppretter en symlink til konfigurasjonen for php cli.

I slike tilfeller brukes en konstruksjon som definerer (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

Dokumentasjonen finnes her.

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 — Puppet Forge.

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 manifestene
  • filer — inneholder filene
  • templates — inneholder malene
  • lib — 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

Dokumentasjonen finnes her.

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 hete foo::, eller bare foo.
  • 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 nginx beskrevet i manifestet init.pp;
  • klasse nginx::service beskrevet i manifestet service.pp;
  • definerer nginx::server beskrevet i manifestet server.pp;
  • definerer nginx::server::location beskrevet i manifestet server/location.pp.

Malverk

Sikkert vet du allerede hva maler er, jeg vil ikke gå i detalj her. Men for en hver tilfelle lar jeg lenken til Wikipedia.

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 loggen

Formelt sett har et faktum et navn (streng) og en verdi (ulike typer er tilgjengelige: strenger, matriser, ordbøker). Det finnes et sett med innebygde fakta. Man kan også skrive egne. Faktasamlere beskrives som funksjoner i Ruby, eller som kjørbare filer. Også fakta kan presenteres i form av tekstfiler med data 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
end

Tekstfakta

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: anothervalue

Tilgang 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).

Her er den nødvendige delen av dokumentasjonen.

Innebygde variabler

I tillegg til fakta, finnes det også noen variabler, 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 den offisielle Puppetlabs-repositoriet 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 seconds

Det 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)[https://github.com/puppetlabs/r10k], det burde være tilstrekkelig for små installasjoner.

Tillegg 2: anbefalinger for å skrive kode

  1. Flytt all logikk til klasser og definisjoner.
  2. Hold classes and defines in modules rather than in manifests that describe nodes.
  3. Make use of facts.
  4. Avoid making if statements based on hostnames.
  5. 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. Logg inn, 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

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster