Introduktion till Puppet

Puppet är ett konfigurationshanteringssystem. Det används för att föra värdar till önskat tillstånd och för att upprätthålla detta tillstånd.

Jag har arbetat med Puppet i mer än fem år. Denna text är i grunden en översatt och omorganiserad sammanställning av nyckelpunkter från den officiella dokumentationen, som gör det möjligt för nybörjare att snabbt förstå kärnan i Puppet.

Introduktion till Puppet

Grundläggande information

Arbetsmodellen för Puppet är klient-server, även om en variant utan server med begränsad funktionalitet även stöds.

En pull-modell används: som standard kontaktar klienter servern var trettionde minut för att hämta konfigurationen och tillämpar den. Om du har arbetat med Ansible används en annan, push-modell: administratören initierar processen att tillämpa konfigurationen, klienterna tillämpar ingenting av sig själva.

Vid nätverksinteraktion används tvåvägs TLS-kryptering: servern och klienten har sina egna privata nycklar och motsvarande certifikat. Vanligtvis utfärdar servern certifikat för klienter, men det är i princip möjligt att använda en extern CA.

Introduktion till manifest

I Puppet-terminologi ansluter till puppets-servern kopplas noder (noder). Konfigurationen för noder skrivs i manifest på ett speciellt programmeringsspråk – Puppet DSL.

Puppet DSL är ett deklarativt språk. Det beskriver det önskade tillståndet för en nod i form av deklarationer av individuella resurser, till exempel:

  • En fil finns, och den har ett visst innehåll.
  • Ett paket är installerat.
  • En tjänst är igång.

Resurser kan vara sammanlänkade:

  • Det finns beroenden som påverkar ordningen i vilken resurser tillämpas.
    Till exempel, "installera först paketet, ändra sedan konfigurationsfilen, därefter starta tjänsten".
  • Det finns aviseringar – om en resurs ändras, skickar den aviseringar till resurser som är anmälda.
    Till exempel, om konfigurationsfilen ändras, kan tjänsten automatiskt startas om.

Dessutom finns det funktioner och variabler i Puppet DSL, samt villkorssatser och selektorer. Olika mallningsmekanismer stöds också – EPP och ERB.

Puppet är skrivet i Ruby, så många konstruktioner och termer är lånade därifrån. Ruby gör det möjligt att utöka Puppet – skriva komplex logik, nya typer av resurser och funktioner.

Under Puppet's operation, manifests for each specific node on the server are compiled into a directory. Katalog — this is a list of resources and their relationships after evaluating the values of functions, variables, and resolving conditional operators.

Syntax and code style

Here are sections of the official documentation that will help you understand the syntax if the provided examples are insufficient:

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

Indentations and line breaks are not a mandatory part of the manifest, however, there is a recommended style guide. Summary:

  • Double-space indents, tabs are not used.
  • Curly brackets are separated by a space, colons are not.
  • Commas after each parameter, including the last one. Each parameter on a separate line. An exception is made for cases with no parameters and one parameter: it can be written on a single line and without a comma (i.e. resource { 'title': } och resource { 'title': param => value }).
  • The arrows for parameters must be at the same level.
  • The arrows for resource relationships are written before them.

File locations on the Puppet server

For further explanations, I will introduce the concept of the '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 Puppet version and the use of environments. Environments are independent sets of configurations stored in separate directories. Generally used in conjunction with Git, in which case environments are created from Git branches. Accordingly, each node is located in one environment or another. This is configured on the node itself or in the ENC, which I will discuss in the next article.

  • In the third version ('old Puppet'), the base directory was /etc/puppet. The use of environments is optional — for example, we do not use them with old Puppet. If environments are used, they are typically stored in /etc/puppet/environments, the root directory will be the directory of the environment. If environments are not used, the root directory will be the base.
  • Starting from the fourth version ('new Puppet'), the use of environments has become mandatory, and the base directory has been relocated to /etc/puppetlabs/code. Det är därför miljöer lagras i /etc/puppetlabs/code/environments, rotkatalogen är miljöets katalog.

I rotkatalogen måste det finnas en underkatalog manifests, där en eller flera manifestfiler med beskrivning av noderna finns. Dessutom måste det finnas en underkatalog modules, där modulerna finns. Vad moduler är, kommer jag att berätta lite senare. Dessutom kan det i den gamla Puppet också finnas en underkatalog filer, där olika filer finns som vi kopierar till noderna. I den nya Puppet har dock alla filer flyttats till moduler.

Manifestfiler har filändelsen .pp.

Exempel på verkligt bruk

Beskrivning av noden och resurserna på den

På noden server1.testdomain måste en fil skapas /etc/issue med innehållet Debian GNU/Linux n l. Filen måste tillhöra användaren och gruppen root, åtkomsträttigheterna måste vara 644.

Skriv manifestet:

node 'server1.testdomain' {   # konfigurationsblock som hör till noden server1.testdomain
    file { '\/etc\/issue':   # beskriver filen \/etc\/issue
        ensure  => present,   # denna fil måste existera
        content => 'Debian GNU\/Linux n l',   # det ska ha detta innehåll
        owner   => root,   # ägare av användare
        group   => root,   # ägargrupp
        mode    => '0644',   # rättigheterna för filen. De anges som en sträng (i citattecken) eftersom annars skulle ett nummer som börjar med 0 uppfattas som skrivet i oktalsystemet, och allt skulle gå fel som planerat
    }
}

Resursrelationer på noden

På noden server2.testdomain måste nginx köras med en förberedd konfiguration.

Dekomponera uppgiften:

  • Det behöver installeras ett paket nginx.
  • Konfigurationsfiler måste kopieras från servern.
  • Tjänsten måste köras nginx.
  • Vid konfigurationsuppdatering måste tjänsten startas om.

Skriv manifestet:

node 'server2.testdomain' {   # konfiguration block som rör noden server2.testdomain
    package { 'nginx':   # beskriver paketet nginx
        ensure => installed,   # det ska vara installerat
    }
  # Den direkta pilen (->) indikerar att resursen nedan ska
  # skapas efter resursen som beskrivs ovan.
  # Sådana beroenden är transitiva.
    -> file { '/etc/nginx':   # beskriver filen /etc/nginx
        ensure  => directory,   # detta ska vara en katalog
        source  => 'puppet:////modules/example/nginx-conf',   # dess innehåll ska hämtas från puppets server på angiven adress
        recurse => true,   # kopiera filer rekursivt
        purge   => true,   # onödiga filer (de som inte finns i källan) ska tas bort
        force   => true,   # ta bort onödiga kataloger
    }
  # Den vågiga pilen (~>) indikerar att resursen nedan ska
  # prenumerera på förändringar av resursen som beskrivs ovan.
  # Den vågiga pilen inkluderar direktpilen (->).
    ~> service { 'nginx':   # beskriver tjänsten nginx
        ensure => running,   # den ska vara igång
        enable => true,   # den ska starta automatiskt vid systemstart
    }
  # När en resurs av typ service får ett meddelande,
  # startar den motsvarande tjänsten om.
}

För att detta ska fungera behöver filerna ha ungefärlig placering 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

Resurstyper

Den fullständiga listan över stödda resurstyper finns i dokumentationen, här kommer jag att beskriva fem grundläggande typer som i min praxis räcker för att lösa de flesta uppgifter.

file

Hantera filer, kataloger, symboliska länkar, deras innehåll, och åtkomsträttigheter.

Parametrar:

  • resursnamn — sökväg till filen (valfritt)
  • path — sökväg till filen (om den inte anges i namnet)
  • ensure — filtyp:
    • frånvarande — ta bort filen
    • present — ska vara en fil av något slag (om filen inte finns kommer en vanlig fil att skapas)
    • file — vanlig fil
    • directory — katalog
    • link — symbolisk länk
  • innehåll — filinnehåll (gäller endast vanliga filer, kan ej användas tillsammans med source eller target)
  • source — länk till sökvägen från vilken filinnehållet ska kopieras (kan ej användas tillsammans med innehåll eller target). Kan anges antingen som en URI med schemat puppet: (då kommer filer från puppets server att användas), eller med schemat http: (detta borde vara tydligt i detta fall), och även med schemat file: eller som en absolut sökväg utan schema (då kommer filen från den lokala filsystemet på noden)
  • target — vart den symboliska länken ska peka (kan inte användas tillsammans med innehåll eller source)
  • owner — användare som filen ska tillhöra
  • grupp — grupp som filen ska tillhöra
  • mode — rättigheter för filen (i form av en sträng)
  • recurse — inkluderar rekursiv bearbetning av kataloger
  • purge — inkluderar borttagning av filer som inte beskrivs i Puppet
  • force — inkluderar borttagning av kataloger som inte beskrivs i Puppet

paket

Installerar och tar bort paket. Kan hantera notifieringar — installerar om paketet om parametern anges reinstall_on_refresh.

Parametrar:

  • resursnamn — namn på paketet (valfritt)
  • name — namn på paketet (om det inte anges i titeln)
  • provider — paketförvaltare som ska användas
  • ensure — önskat tillstånd för paketet:
    • present, installerat — vilken version som helst installerad
    • latest — senaste version installerad
    • frånvarande — borttagen (apt-get remove)
    • purged — borttagen tillsammans med konfigurationsfiler (apt-get purge)
    • held — paketversionen är blockerad (apt-mark hold)
    • valfri annan sträng — angiven version installerad
  • reinstall_on_refresh — om true, kommer paketet att installeras om vid notifiering. Nyttigt för source-baserade distributioner där återkompilering av paket kan vara nödvändig vid ändrade byggparametrar. Som standard false.

service

Hantera tjänster. Kan hantera notifieringar — startar om tjänsten.

Parametrar:

  • resursnamn — tjänst som ska hanteras (valfritt)
  • name — tjänst som ska hanteras (om den inte anges i titeln)
  • ensure — önskat tillstånd för tjänsten:
    • löpning — igång
    • stopped — stoppad
  • enable — hanterar möjlighet till automatisk start av tjänsten:
    • true — autostart aktiverad (systemctl enable)
    • mask — maskerad (systemctl mask)
    • false — autostart avaktiverad (systemctl disable)
  • restart — kommando för att starta om tjänsten
  • status — kommando för att kontrollera tjänstens status
  • hasrestart — ange om tjänstens init script stödjer omstart. Om false och parametern anges restart — används värdet av den parametern. Om false och parametern restart inte anges — tjänsten stoppas och startas om för att omstarta (men i systemd används kommandot systemctl restart).
  • hasstatus — ange om tjänstens init script stödjer kommandot status. Om false, används värdet av parametern status. Som standard true.

exec

Kör externa kommandon. Om inga parametrar anges creates, onlyif, unless eller refreshonly, kommer kommandot att köras vid varje körning av Puppet. Kan hantera notifieringar — kör kommandot.

Parametrar:

  • resursnamn — kommando som ska utföras (valfritt)
  • command — kommando som ska utföras (om det inte anges i titeln)
  • path — vägar att söka efter körbar fil
  • onlyif — om kommandot angivet i denna parameter avslutades med en nollkod, kommer huvudkommandot att utföras
  • unless — om kommandot angivet i denna parameter avslutades med en icke-nollkod, kommer huvudkommandot att utföras
  • creates — om den angivna filen i denna parameter inte finns, kommer huvudkommandot att utföras
  • refreshonly — om true, så kommer kommandot att köras endast om denna exec får en notifikation från andra resurser
  • cwd — katalogen från vilken kommandot ska köras
  • användare — användare från vilken kommandot ska köras
  • provider — med hjälp av vad kommandot ska köras:
    • posix — skapar helt enkelt en underprocess, måste anges path
    • shell — kommandot körs i en shell /bin/sh, kan lämnas outtalad path, kan använda globbing, pipelines och andra funktioner i shell. Bestäms vanligtvis automatiskt om det finns olika specialtecken (|, ;, &&, || och så vidare).

cron

Hantera cron-jobb.

Parametrar:

  • resursnamn — bara någon identifierare
  • ensure — status för cron-jobbet:
    • present — skapa om den inte finns
    • frånvarande — ta bort om den finns
  • command — vilket kommando som ska köras
  • miljö — i vilken miljö kommandot ska köras (lista över miljövariabler och deras värden genom =)
  • användare — från vilken användare kommandot ska köras
  • minut, timme, veckodag, månad, måndag — när kommandot ska köras. Om någon av dessa attribut inte anges, kommer dess värde i crontab att vara *.

I Puppet 6.0 cron typ har tagits bort ur boxen i puppetserver, därför finns det ingen dokumentation på den allmänna sidan. Men den finns i boxen i puppet-agent, så den behöver inte installeras separat. Dokumentationen för den kan ses i dokumentationen för den femte versionen av Puppet, eller på GitHub.

Om resurser i allmänhet

Krav på unikhet av resurser

Det vanligaste felet som vi stöter på är Duplicerad deklaration. Detta fel uppstår när två eller flera resurser av samma typ med samma namn hamnar i katalogen.

Så jag upprepar: i manifest för en nod får det inte finnas resurser av samma typ med samma namn (title)!

Ibland finns det ett behov av att installera paket med samma namn, men med olika paketchefer. I sådana fall måste man använda parametern name, för att undvika felet:

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

I andra typer av resurser finns liknande parametrar som hjälper till att undvika duplication, — name för service, command för exec, och så vidare.

Metaparamentrar

Vissa specialparametrar finns för varje typ av resurs, oavsett dess entitet.

Fullständig lista över metaparamentrar i dokumentationen för Puppet.

Kort lista:

  • require — i denna parameter anges vilka resurser som denna resurs är beroende av.
  • before — i denna parameter anges vilka resurser som är beroende av denna resurs.
  • subscribe — i denna parameter anges från vilka resurser denna resurs får meddelanden.
  • notify — i denna parameter anges vilka resurser som får meddelanden från denna resurs.

Alla nämnda metaparamentrar accepterar antingen en referens till en resurs eller en array av referenser i kvadratiska parenteser.

Referenser till resurser

En länk till en resurs är helt enkelt en hänvisning till resursen. De används huvudsakligen för att ange beroenden. En hänvisning till en icke-existerande resurs kommer att orsaka ett kompilationsfel.

Syntaxen för en länk är följande: resurstyp med stor bokstav (om namnet på typen innehåller dubbla kolon, skrivs varje del av namnet med stor bokstav), följt av namnet på resursen i kvadratiska parenteser (ställ inte om registren i namnet!). Det får inte finnas några mellanslag, de kvadratiska parenteserna skrivs omedelbart efter namnet på typen.

Exempel:

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

Beroenden och meddelanden

Dokumentationen finns här.

Som nämnts tidigare är enkla beroenden mellan resurser transitiva. Var försiktig när du ställer in beroenden — det går att skapa cykliska beroenden, vilket kommer att orsaka ett kompilationsfel.

Till skillnad från beroenden är meddelanden inte transitiva. För meddelanden gäller följande regler:

  • Om en resurs får ett meddelande uppdateras den. Åtgärder vid uppdatering beror på resurstypen — exec startar ett kommando, service startar om en tjänst, paket installerar om ett paket. Om ingen åtgärd vid uppdatering är definierad för resursen så händer ingenting.
  • Under ett enda körning av Puppet uppdateras en resurs högst en gång. Detta är möjligt, eftersom meddelanden inkluderar beroenden, och grafen över beroenden innehåller inga cykler.
  • Om Puppet ändrar tillståndet för en resurs, skickar resursen notifieringar till alla som prenumererar på den.
  • Om en resurs uppdateras, skickar den notifieringar till alla som prenumererar på den.

Behandling av icke angivna parametrar

Generellt sett, om en parameter för en resurs inte har något standardvärde och denna parameter inte anges i manifestet, kommer Puppet inte att ändra denna egenskap hos den motsvarande resursen på noden. Till exempel, om en resurs av typ file inte har en parameter angiven owner, kommer Puppet inte att ändra ägaren för den motsvarande filen.

Introduktion till klasser, variabler och definieringar

Anta att vi har flera noder med en gemensam del av konfigurationen, men det finns också skillnader – annars skulle vi kunna beskriva allt i ett block node {}. Självklart kan man helt enkelt kopiera de gemensamma delarna av konfigurationen, men i allmänhet är det en dålig lösning - konfigurationen växer, och när den gemensamma delen av konfigurationen ändras måste man ändra samma sak på många ställen. Det är lätt att göra misstag, och principen DRY (don't repeat yourself) är inte skapad utan anledning.

För att lösa ett sådant problem finns det en konstruktion som klass.

Klasser

Klassen – är en namngiven block av Puppet-kod. Klasser behövs för att återanvända kod.

Först måste klassen beskrivas. Själva beskrivningen lägger inte till några resurser någonstans. Klassen beskrivs i manifest:

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

Efter det kan klassen användas:

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

Exemplet från den föregående uppgiften – vi flyttar installation och konfiguration av nginx till en klass:

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 från det föregående exemplet är inte flexibel alls, eftersom den alltid ger samma nginx-konfiguration. Låt oss göra så att sökvägen till konfigurationen blir variabel, så att denna klass kan användas för att installera nginx med vilken konfiguration som helst.

Detta kan göras med hjälp av variabler.

Varning: variabler i Puppet är oföränderliga!

Dessutom kan man endast referera till en variabel efter att den har deklarerats, annars blir värdet på variabeln undef.

Exempel på arbete 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 finns det namnrymder, och variabler har därmed synlighet: en variabel med samma namn kan definieras i olika namnområden. När variabelvärdet löses söks variabeln först i det aktuella namnområdet, sedan i det omgivande, och så vidare.

Exempel på namnområden:

  • globalt — dit går variabler utanför klassens eller nodens beskrivning;
  • nodens namnområde i nodens beskrivning;
  • klassens namnområde i klassens beskrivning.

För att undvika tvetydighet vid referens till en variabel kan man ange namnområdet i variabelns namn:

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

Låt oss komma överens om att vägen till nginx-konfigurationen ligger i variabeln $nginx_conf_source. Då kommer klassen att se ut på följande sätt:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # här använder vi en variabel istället för en fast sträng
        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 det givna exemplet är dåligt eftersom det finns en slags "hemlig kunskap" om att det någonstans i klassen används en variabel med ett visst namn. Mycket mer korrekt är det att göra denna kunskap gemensam — klasser kan ha parametrar.

Klassparametrar — dvs variabler i klassens namnområde, de definieras i klassens rubrik och kan användas som vanliga variabler i klassens kropp. Parameterna anges vid användning av klassen i manifestet.

Man kan ge en parameter ett standardvärde. Om en parameter inte har något standardvärde och värdet inte anges vid användning, kommer detta att orsaka ett kompilationsfel.

Låt oss parametrera klassen från exemplet ovan och lägga till två parametrar: den första, obligatoriska — vägen till konfigurationen, och den andra, valfria — namnet på paketet med nginx (i Debian, till exempel, finns det paket 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 är variabler typade. Det finns många datatyper.Datatyper används vanligtvis för att validera parametrarnas värden, som skickas till klasser och definitioner. Om den angivna parametern inte överensstämmer med den angivna typen, kommer ett kompilationsfel att inträffa.

Typen skrivs direkt före parameter namn:

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

Klasser: inkludera klassnamn vs class{‘klassnamn’:}

Varje klass är en resurstyp klass. Precis som med andra typer av resurser kan inte två instanser av samma klass förekomma på en nod.

Om man försöker lägga till en klass på samma nod två gånger med hjälp av class { 'klassnamn':} (oavsett om de har olika eller samma parametrar) kommer det att ge ett kompilationsfel. Men när man använder en klass i resurstypsformat kan man direkt i manifestet ange alla dess parametrar.

Men om man använder include, kan klassen läggas till hur många gånger som helst. Anledningen är att include är en idempotent funktion som kontrollerar om klassen har lagts till i katalogen. Om klassen inte finns i katalogen läggs den till, men om den redan finns händer inget. Men när man använder include kan man inte ange klassens parametrar vid deklarationen av klassen — alla obligatoriska parametrar måste anges i en extern datakälla — Hiera eller ENC. Vi kommer att diskutera detta i nästa artikel.

Definierar

Som nämnts i föregående avsnitt kan inte samma klass förekomma mer än en gång på en nod. Men i vissa fall behöver man kunna använda samma kodblock med olika parametrar på en nod. Med andra ord, det finns ett behov av en egen resurstyp.

Till exempel, för att installera en PHP-modul gör vi följande på Avito:

  1. Installerar paketet med denna modul.
  2. Skapar en konfigurationsfil för denna modul.
  3. Skapar en symlink till konfigurationsfilen för php-fpm.
  4. Skapar en symlink till konfigurationsfilen för php cli.

I sådana fall används en konstruktion som definiera (define, defined type, defined resource type). En definierare liknar en klass, men det finns skillnader: för det första är varje definierare en resurstyp och inte en resurs; för det andra har varje definierare en implicit parameter $title, där namnet på resursen hamnar vid dess deklaration. Precis som med klasser måste en definierare först beskrivas för att sedan kunna användas.

Ett förenklat exempel med en modul för PHP:

definiera php74::modul (
  $php_module_name = $title,
  $php_package_name = "php7.4-${title}",
  $version = 'installerad',
  $priority = '20',
  $data = "extension=${title}.son",
  $php_module_path = ' /etc/php/7.4/mods-available',
) {
  paket { $php_package_name:
    säkerställ          => $version,
    install_options => ['-o', 'DPkg::NoTriggers=true'],  # Debian PHP-paketens triggers skapar automatiskt symlänkar och startar php-fpm - vi behöver inte detta, eftersom vi hanterar både symlänkar och tjänsten med Puppet
  }
  -> fil { "${php_module_path}/${php_module_name}.ini":
    säkerställ  => $ensure,
    innehåll => $data,
  }
  fil { " /etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
    säkerställ  => länk,
    mål  => "${php_module_path}/${php_module_name}.ini",
  }
  fil { " /etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
    säkerställ  => länk,
    mål  => "${php_module_path}/${php_module_name}.ini",
  }
}

nod server3.testdomain {
  php74::modul { 'sqlite3': }
  php74::modul { 'amqp': php_package_name => 'php-amqp' }
  php74::modul { 'msgpack': priority => '10' }
}

Det är enklast att fånga Duplicate declaration-felet i definiera. Detta händer om det i definiera finns en resurs med ett konstant namn, och på någon nod finns två eller fler instanser av denna definiera.

Det är enkelt att skydda sig mot detta: alla resurser inom definiera måste ha ett namn som är beroende av $title. Som alternativ - en idempotent tilläggning av resurser, i enklaste fall räcker det att flytta gemensamma resurser för alla instanser av definiera till en separat klass och inkludera denna klass i definiera - funktionen include är idempotent.

Det finns även andra sätt att uppnå idempotens vid tilläggning av resurser, nämligen att använda funktioner defined och ensure_resources, men det kommer jag att berätta om i nästa avsnitt.

Beroenden och meddelanden för klasser och definiera

Klasser och definiera lägger till följande regler för behandling av beroenden och meddelanden:

  • ett beroende av klass/definiera lägger till beroenden från alla resurser i klass/definiera;
  • ett beroende av klass/definiera lägger till beroenden till alla resurser i klass/definiera;
  • ett meddelande från klass/definiera meddelar alla resurser i klass/definiera;
  • prenumeration på klass/definiera prenumererar på alla resurser i klass/definiera.

Villkorsoperatorer och växlingssatser

Dokumentationen finns här.

if

Här är allt enkelt:

if UTTRYCK1 {
  ...
} elsif UTTRYCK2 {
  ...
} else {
  ...
}

unless

unless - det är if motsatt: kodblocket kommer att utföras om uttrycket är falskt.

unless UTTRYCK {
  ...
}

vid datainport.

Här är också inget svårt. Som värden kan ordinarie värden (strängar, nummer och så vidare), reguljära uttryck samt datatyper användas.

case UTRYCK {
  VÄRDE1: { ... }
  VÄRDE2, VÄRDE3: { ... }
  default: { ... }
}

Väljare

En väljare är en språklig konstruktion som liknar vid datainport., men istället för att köra en kodblock returnerar den ett värde.

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

Moduler

När konfigurationen är liten kan den lätt hållas i en manifestfil. Men ju fler konfigurationer vi beskriver, desto fler klasser och noder finns i manifestet, det växer, och det blir besvärligt att arbeta med.

Dessutom finns det ett problem med återanvändning av kod – när all kod är i en manifestfil är det svårt att dela den med andra. För att lösa dessa två problem finns i Puppet en entitet som kallas moduler.

Moduler – är uppsättningar av klasser, definitioner och andra Puppet-enheter som flyttats till en separat katalog. Med andra ord är en modul en oberoende del av Puppet-logiken. Till exempel kan det finnas en modul för att arbeta med nginx, och den kommer att innehålla just det som behövs för att arbeta med nginx, och det kan finnas en modul för att arbeta med PHP, och så vidare.

Moduler versioneras, och beroenden mellan moduler stöds också. Det finns ett öppet register för moduler – Puppet Forge.

På Puppet-servern finns moduler i underkatalogen modules i rotkatalogen. Inuti varje modul finns en standard mappstruktur – manifests, files, templates, lib och så vidare.

Filstruktur i modulen

I roten av modulen kan följande kataloger med beskrivande namn finnas:

  • manifests – här ligger manifestfiler
  • filer – här ligger filer
  • templates – här ligger mallar
  • lib – här ligger Ruby-kod

Detta är inte en komplett lista över kataloger och filer, men för denna artikel är det tillräckligt.

Resursnamn och filnamn i modulen

Dokumentationen finns här.

Resurser (klasser, definitioner) i modulen kan inte namnges hur som helst. Dessutom finns det en direkt korrespondens mellan resursnamnet och filnamnet där Puppet kommer att leta efter beskrivningen av denna resurs. Om namngivningsreglerna bryts kommer Puppet helt enkelt inte att hitta beskrivningen av resurserna, och det kommer att leda till en kompileringsfel.

Reglerna är enkla:

  • Alla resurser i modulen måste ligga i modulens namnrymd. Om modulen heter foo, ska alla resurser i den kallas foo::, eller helt enkelt foo.
  • Resursen med modulens namn måste ligga i filen init.pp.
  • För övriga resurser är namngivningsschemat följande:
    • prefixet med modulens namn tas bort
    • alla dubbla kolon, om de finns, ersätts med snedstreck
    • ett tillägg skrivs till .pp

Jag ska demonstrera med ett exempel. Anta att jag skriver en modul nginx. Den innehåller följande resurser:

  • klass nginx beskriven i manifestet init.pp;
  • klass nginx::service beskriven i manifestet service.pp;
  • definiera nginx::server beskriven i manifestet server.pp;
  • definiera nginx::server::location beskriven i manifestet server/location.pp.

Mallar

Du vet säkert redan vad mallar är, jag kommer inte att gå in på detaljer här. Men för säkerhets skull lämnar jag en länk till Wikipedia.

Hur man använder mallar: värdet av en mall kan avtäcka med hjälp av funktionen template, som får sökvägen till mallen. För resurser av typen file används tillsammans med parametern innehåll. Till exempel så här:

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

En sökväg av typen / antyder en fil /modules//templates/.

Dessutom finns det en funktion inline_template — den tar emot texten av en mall, inte namnet på en fil.

Inuti mallar kan alla variabler i Puppet som är i aktuell synlighet användas.

Puppet stöder mallar i ERB och EPP-format:

Kort om ERB

Kontrollstrukturer:

  • <%= ВЫРАЖЕНИЕ %> — infoga värdet av uttrycket
  • <% ВЫРАЖЕНИЕ %> — beräkna värdet av uttrycket (utan att infoga det). Här används vanliga villkorssatser (if), loopar (each).
  • <%# КОММЕНТАРИЙ %>

Uttryck i ERB skrivs i Ruby (faktiskt är ERB Embedded Ruby).

För att få åtkomst till variabler från manifestet måste man lägga till @ till variabelnamnet. För att ta bort radbrytningen som uppstår efter kontrollstrukturen, använd stängningstaggen -%>.

Exempel på användning av en mall

Anta att jag skriver en modul för hantering av ZooKeeper. Klassen som ansvarar för att skapa konfigurationen ser ut ungefär så här:

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

Och den motsvarande mallen zoo.cfg.erb — ser ut så här:

0 -%>

server.=:::



dataDir=


=

Fakta och inbyggda variabler

Ofta beror en specifik del av konfigurationen på vad som händer vid den aktuella noden. Till exempel, beroende på vilken version av Debian som är installerad, behöver man installera den aktuella versionen av paketet. Man kan övervaka detta manuellt genom att skriva om manifesten när noderna förändras. Men det är en oseriös metod; automatisering är mycket bättre.

För att få information om noder i Puppet finns det en mekanism kallad fakta. Fakta är information om noden som finns tillgänglig i manifesten som vanliga variabler i det globala namnutrymmet. Till exempel nodens namn, operativsystemets version, processorarkitektur, användarlista, lista över nätverksgränssnitt och deras adresser, och mycket, mycket mer. Fakta är tillgängliga i manifesten och mallarna som vanliga variabler.

Exempel på hur man arbetar med fakta:

notify { "Kör OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# notify-typen skriver helt enkelt ut meddelandet i loggen

Formellt sett har ett faktum ett namn (sträng) och ett värde (olika typer finns tillgängliga: strängar, arrayer, ordböcker). Det finns ett antal inbyggda fakta. Man kan också skriva egna. Faktsamlarna beskrivs som funktioner i Ruby, eller som exekverbara filer. Fakta kan också representeras som textfiler med data på noderna.

Under arbetet kopierar Puppet-agenten först alla tillgängliga faktasamlare från Puppet-servern till noden, kör dem sedan och skickar de insamlade fakta till servern; först efter detta börjar servern kompilera katalogen.

Fakta i form av exekverbara filer

Dessa fakta placeras i moduler i katalogen facts.d. Självklart måste filerna vara exekverbara. När de körs måste de skriva ut information till standardutmatningen, antingen i YAML-format eller i "nyckel=värde"-format.

Glöm inte att fakta sprids till alla noder som styrs av Puppet-servern som din modul distribueras på. Därför bör skriptet se till att alla program och filer som krävs för att ditt faktum ska fungera finns i systemet.

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

Fakta i Ruby

Dessa fakta placeras 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

Textfakta

Dessa fakta placeras på noderna i katalogen /etc/facter/facts.d i gamla Puppet eller /etc/puppetlabs/facts.d i nya Puppet.

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

Åtkomst till fakta

Du kan komma åt fakta på två sätt:

  • via ordboken $facts: $facts['fqdn'];
  • genom att använda namnet på faktan som namn för variabeln: $fqdn.

Det är bäst att använda ordboken $facts, och ännu bättre att ange det globala namnutrymmet ($::facts).

Här är den relevanta delen av dokumentationen.

Inbyggda variabler

Förutom fakta finns det även vissa variabler, tillgängliga i det globala namnutrymmet.

  • trusted facts — variabler som hämtas från klientens certifikat (eftersom certifikatet vanligtvis utfärdas på puppetsservern, kan agenten inte bara byta sitt certifikat, därför är variablerna "betrodda": certifikatets namn, värdnamn och domän, certifikatets förlängningar.
  • server facts — variabler relaterade till serverinformation — version, namn, serverns IP-adress, miljö.
  • agent facts — variabler som läggs till direkt av puppet-agenten, och inte av facter — certifikatets namn, agentversion, puppetversion.
  • master variables — variabler hos puppetmaster (sic!). Här är ungefär samma information som i server facts, plus värdena för konfigurationsparametrar.
  • compiler variables — kompilatorvariabler som skiljer sig i varje synlighet: namnet på den aktuella modulen och namnet på den modul där det gjordes anrop till det aktuella objektet. De kan användas för att kontrollera att dina privata klasser inte används direkt från andra moduler.

Tillägg 1: Hur kör man och felsöker detta?

I artikeln fanns det många exempel på puppetkod, men det nämndes inget om hur man kör denna kod. Jag rättar till detta.

För att använda Puppet räcker det med en agent, men i de flesta fall behövs även en server.

Agent

Minst från version fem innehåller puppet-agentpaketen från Puppetlabs officiella repository alla beroenden (ruby och motsvarande gem:ar), så det finns inga svårigheter med installationen (jag pratar om Debian-baserade distributioner — vi använder inte RPM-baserade distributioner).

I det enklaste fallet räcker det att köra puppet-konfigurationen genom att starta agenten i lös server-läge: förutsatt att puppetkoden har kopierats till noden kör du 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 är självklart bättre att starta servern och köra agenterna på noderna i daemonläge — då kommer de att tillämpa konfigurationen som laddas ner från servern var halvtimme.

Man kan simulera en push-modell — gå in på den nod du är intresserad av och köra sudo puppet agent -t. Nyckeln -t (--test) aktiverar faktiskt flera alternativ som kan aktiveras var för sig. Bland dessa alternativ finns följande:

  • inte arbeta i daemonläge (som standard körs agenten i daemonläge);
  • avsluta efter att ha tillämpat katalogen (som standard fortsätter agenten att arbeta och tillämpar konfigurationen var halvtimme);
  • skriva en detaljerad logg över arbetet;
  • visa ändringar i filer.

Agenten har en förändringslös läge — den kan användas när du inte är säker på att du har skrivit en korrekt konfiguration och vill kontrollera vad agenten kommer att ändra under körningen. Detta läge aktiveras med parametern --noop i kommandoraden: sudo puppet agent -t --noop.

Dessutom kan du aktivera en felsökningslogg — i den skriver puppet om alla åtgärder som utförs: om resursen som för närvarande behandlas, om parametrarna för denna resurs, och vilka program som startas. Självklart är detta parameter --debug.

Servern

Jag kommer inte att gå igenom en fullständig konfiguration av puppetserver och distribution av kod till den i denna artikel, jag kan bara säga att en fullt fungerande version av servern installeras utan ytterligare konfiguration för arbete i förhållanden med ett litet antal noder (säg, upp till hundra). Ett större antal noder kräver redan justering — som standard startar puppetserver inte fler än fyra arbetare, för att öka prestandan måste antalet ökas och glöm inte att öka minnesgränserna, annars kommer servern att ägna största delen av tiden åt garbage collect.

Distribution av kod — om du behöver något snabbt och enkelt, titta på (r10k)[https://github.com/puppetlabs/r10k], för små installationer borde det vara tillräckligt.

Tillägg 2: rekommendationer för kodskrivning

  1. Flytta all logik till klasser och definierade.
  2. Håll klasser och definitioner i moduler, inte i manifest med nodbeskrivningar.
  3. Använd fakta.
  4. Gör inte if-satser baserat på värdnamn.
  5. Tveka inte att lägga till parametrar för klasser och definitioner — det är bättre än implicit logik som döljs i klassen/definitionens kropp.

Och varför rekommenderar jag att göra så — kommer jag att förklara i nästa artikel.

Slutsats

Låt oss avsluta med introduktionen. I nästa artikel kommer jag att berätta om Hiera, ENC och PuppetDB.

Endast registrerade användare kan delta i omröstningen. Logga in, tack.

Det finns faktiskt mycket mer material — jag kan skriva artiklar om följande ämnen, välj vad du skulle vara intresserad av att läsa:

  • 59,1%Avancerade Puppet-konstruktioner — lite nästa nivå: loopar, mappning och andra lambda-uttryck, resurskollektorer, exporterade resurser och mellanvärdsinteraktion genom Puppet, taggar, leverantörer, abstrakta datatyper.13
  • 31,8%"Jag är admin hos mamma" eller hur vi på Avito kopplade ihop flera Puppet-servrar av olika versioner, och i princip en del om administration av Puppet-servern.7
  • 81,8%Så skriver vi Puppet-kod: verktygsbundling, dokumentation, testning, CI/CD.18

22 användare röstade. 9 användare avstod.

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster