Introduktion till Puppet

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

Jag har arbetat med Puppet i över fem år nu. Denna text är i huvudsak en översatt och omordnad sammanställning av nyckelpunkter från den officiella dokumentationen, vilket gör att nybörjare snabbt kan förstå essensen av Puppet.

Introduktion till Puppet

Grundläggande information

Puppets operativsystem är klient-server, även om det också stöder serverlös drift med begränsad funktionalitet.

En pull-modell används: som standard, en gång varje halvtimme, kontaktar klienter servern för en konfiguration och tillämpar den. Om du har arbetat med Ansible använder de en annan push-modell: administratören initierar processen för att tillämpa konfigurationen, klienterna själva kommer inte att tillämpa någonting.

Under nätverkskommunikation 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 i princip är det möjligt att använda en extern CA.

Introduktion till manifest

I Puppet terminologi till dockservern ansluta knutpunkter (knutpunkter). Konfigurationen för noderna skrivs i manifest i ett speciellt programmeringsspråk - Puppet DSL.

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

  • Filen finns och den har specifikt innehåll.
  • Paketet är installerat.
  • Tjänsten har startat.

Resurser kan kopplas samman:

  • Det finns beroenden, de påverkar i vilken ordning resurserna används.
    Till exempel, "installera först paketet, redigera sedan konfigurationsfilen och starta sedan tjänsten."
  • Det finns aviseringar - om en resurs har ändrats, skickar den meddelanden till resurserna som prenumererar på den.
    Till exempel, om konfigurationsfilen ändras kan du automatiskt starta om tjänsten.

Dessutom har Puppet DSL funktioner och variabler, såväl som villkorliga uttalanden och väljare. Olika mallmekanismer stöds också - EPP och ERB.

Puppet är skrivet i Ruby, så många av konstruktionerna och termerna är hämtade därifrån. Ruby låter dig utöka Puppet - lägg till komplex logik, nya typer av resurser, funktioner.

Medan Puppet körs kompileras manifest för varje specifik nod på servern till en katalog. Katalog är en lista över resurser och deras relationer efter beräkning av värdet av funktioner, variabler och expansion av villkorliga uttalanden.

Syntax och kodstil

Här är delar av den officiella dokumentationen som hjälper dig att förstå syntaxen om exemplen inte räcker:

Här är ett exempel på hur manifestet ser ut:

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

Indrag och radbrytningar är inte en obligatorisk del av manifestet, men det finns en rekommendation stil guide. Sammanfattning:

  • Indrag med två mellanslag, tabbar används inte.
  • Lockiga hängslen är åtskilda av ett mellanslag, kolon är inte åtskilda av ett mellanslag.
  • Komma efter varje parameter, inklusive den sista. Varje parameter finns på en separat rad. Ett undantag görs för fallet utan parametrar och en parameter: du kan skriva på en rad och utan komma (dvs. resource { 'title': } и resource { 'title': param => value }).
  • Pilarna på parametrarna ska vara på samma nivå.
  • Resursrelationspilar är skrivna framför dem.

Placering av filer på pappersserver

För ytterligare förklaring kommer jag att introducera begreppet "rotkatalog". Rotkatalogen är katalogen som innehåller Puppet-konfigurationen för en specifik nod.

Rotkatalogen varierar beroende på versionen av Puppet och de miljöer som används. Miljöer är oberoende uppsättningar av konfigurationer som lagras i separata kataloger. Används vanligtvis i kombination med git, i vilket fall miljöer skapas från git-grenar. Följaktligen är varje nod belägen i en eller annan miljö. Detta kan konfigureras på själva noden, eller i ENC, som jag kommer att prata om i nästa artikel.

  • I den tredje versionen ("gamla Puppet") var baskatalogen /etc/puppet. Användningen av miljöer är valfri - vi använder dem till exempel inte med den gamla Puppet. Om miljöer används lagras de vanligtvis i /etc/puppet/environments, kommer rotkatalogen att vara miljökatalogen. Om miljöer inte används kommer rotkatalogen att vara baskatalogen.
  • Från och med den fjärde versionen ("nya Puppet") blev användningen av miljöer obligatorisk, och baskatalogen flyttades till /etc/puppetlabs/code. Följaktligen lagras miljöer i /etc/puppetlabs/code/environments, är rotkatalogen miljökatalogen.

Det måste finnas en underkatalog i rotkatalogen manifests, som innehåller ett eller flera manifest som beskriver noderna. Dessutom bör det finnas en underkatalog modules, som innehåller modulerna. Jag ska berätta vilka moduler som är lite senare. Dessutom kan den gamla dockan också ha en underkatalog files, som innehåller olika filer som vi kopierar till noderna. I den nya Puppet är alla filer placerade i moduler.

Manifestfiler har tillägget .pp.

Ett par stridsexempel

Beskrivning av noden och resursen på den

På noden server1.testdomain en fil måste skapas /etc/issue med innehåll Debian GNU/Linux n l. Filen måste ägas av en användare och grupp root, åtkomsträttigheter måste vara 644.

Vi skriver ett manifest:

node 'server1.testdomain' {   # блок конфигурации, относящийся к ноде server1.testdomain
    file { '/etc/issue':   # описываем файл /etc/issue
        ensure  => present,   # этот файл должен существовать
        content => 'Debian GNU/Linux n l',   # у него должно быть такое содержимое
        owner   => root,   # пользователь-владелец
        group   => root,   # группа-владелец
        mode    => '0644',   # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Relationer mellan resurser på en nod

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

Låt oss bryta ner problemet:

  • Paketet måste installeras nginx.
  • Det är nödvändigt att konfigurationsfilerna kopieras från servern.
  • Tjänsten måste vara igång nginx.
  • Om konfigurationen uppdateras måste tjänsten startas om.

Vi skriver ett manifest:

node 'server2.testdomain' {   # блок конфигурации, относящийся к ноде server2.testdomain
    package { 'nginx':   # описываем пакет nginx
        ensure => installed,   # он должен быть установлен
    }
  # Прямая стрелка (->) говорит о том, что ресурс ниже должен
  # создаваться после ресурса, описанного выше.
  # Такие зависимости транзитивны.
    -> file { '/etc/nginx':   # описываем файл /etc/nginx
        ensure  => directory,   # это должна быть директория
        source  => 'puppet:///modules/example/nginx-conf',   # её содержимое нужно брать с паппет-сервера по указанному адресу
        recurse => true,   # копировать файлы рекурсивно
        purge   => true,   # нужно удалять лишние файлы (те, которых нет в источнике)
        force   => true,   # удалять лишние директории
    }
  # Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
  # подписаться на изменения ресурса, описанного выше.
  # Волнистая стрелка включает в себя прямую (->).
    ~> service { 'nginx':   # описываем сервис nginx
        ensure => running,   # он должен быть запущен
        enable => true,   # его нужно запускать автоматически при старте системы
    }
  # Когда ресурс типа service получает уведомление,
  # соответствующий сервис перезапускается.
}

För att detta ska fungera behöver du ungefär följande filplats på dockservern:

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

Resurstyper

En komplett lista över resurstyper som stöds finns här i dokumentationen, här kommer jag att beskriva fem grundläggande typer, som i min praktik räcker för att lösa de flesta problem.

fil

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

alternativ:

  • resursnamn — sökväg till filen (valfritt)
  • bana — sökväg till filen (om den inte anges i namnet)
  • säkerställa - filtyp:
    • absent - radera en fil
    • present — det måste finnas en fil av vilken typ som helst (om det inte finns någon fil kommer en vanlig fil att skapas)
    • file - vanlig fil
    • directory - katalog
    • link - symbollänk
  • innehåll — filinnehåll (lämpligt endast för vanliga filer, kan inte användas tillsammans med källa eller mål)
  • källa — en länk till sökvägen från vilken du vill kopiera innehållet i filen (kan inte användas tillsammans med innehåll eller mål). Kan anges som antingen en URI med ett schema puppet: (då kommer filer från dockservern att användas), och med schemat http: (Jag hoppas att det är klart vad som kommer att hända i det här fallet), och även med diagrammet file: eller som en absolut sökväg utan ett schema (då kommer filen från den lokala FS på noden att användas)
  • mål — där symbollänken ska peka (kan inte användas tillsammans med innehåll eller källa)
  • ägaren — användaren som ska äga filen
  • grupp — den grupp som filen ska tillhöra
  • läge — filbehörigheter (som en sträng)
  • återbetala - möjliggör rekursiv katalogbearbetning
  • genomblåsning - gör det möjligt att ta bort filer som inte beskrivs i Puppet
  • styrka - gör det möjligt att ta bort kataloger som inte beskrivs i Puppet

paket

Installerar och tar bort paket. Kan hantera aviseringar - installerar om paketet om parametern är specificerad reinstall_on_refresh.

alternativ:

  • resursnamn — paketnamn (valfritt)
  • namn — paketnamn (om det inte anges i namnet)
  • leverantör — pakethanterare att använda
  • säkerställa — önskat tillstånd för förpackningen:
    • present, installed - valfri version installerad
    • latest - senaste versionen installerad
    • absent - raderade (apt-get remove)
    • purged — raderade tillsammans med konfigurationsfiler (apt-get purge)
    • held - paketversionen är låst (apt-mark hold)
    • любая другая строка — den angivna versionen är installerad
  • reinstall_on_refresh - om true, sedan kommer paketet att installeras om efter mottagandet av meddelandet. Användbar för källbaserade distributioner, där det kan vara nödvändigt att bygga om paket när man ändrar byggparametrar. Standard false.

service

Hanterar tjänster. Kan behandla aviseringar - startar om tjänsten.

alternativ:

  • resursnamn — tjänst som ska hanteras (valfritt)
  • namn — tjänsten som behöver hanteras (om det inte anges i namnet)
  • säkerställa — önskat tillstånd för tjänsten:
    • running - lanseras
    • stopped - slutade
  • möjliggöra — kontrollerar möjligheten att starta tjänsten:
    • true — autorun är aktiverad (systemctl enable)
    • mask - förtäckt (systemctl mask)
    • false — autorun är inaktiverad (systemctl disable)
  • omstart - kommando för att starta om tjänsten
  • status — kommando för att kontrollera tjänstens status
  • har startat om — ange om tjänstens initscript stöder omstart. Om false och parametern anges omstart — värdet på denna parameter används. Om false och parameter omstart inte specificerad - tjänsten stoppas och startas om (men systemd använder kommandot systemctl restart).
  • hasstatus — ange om tjänstens initscript stöder kommandot status. om false, då används parametervärdet status. Standard true.

exec

Kör externa kommandon. Om du inte anger parametrar skapar, bara om, såvida inte eller uppfriskande, kommer kommandot att köras varje gång Puppet körs. Kan behandla aviseringar - kör ett kommando.

alternativ:

  • resursnamn — kommando som ska utföras (valfritt)
  • kommando — kommandot som ska köras (om det inte anges i namnet)
  • bana — sökvägar för att leta efter den körbara filen
  • bara om — om kommandot som anges i denna parameter kompletteras med en nollreturkod, kommer huvudkommandot att utföras
  • såvida inte — om kommandot som anges i denna parameter kompletteras med en returkod som inte är noll, kommer huvudkommandot att utföras
  • skapar — om filen som anges i denna parameter inte finns, kommer huvudkommandot att köras
  • uppfriskande - om true, då kommer kommandot endast att köras när denna exec får meddelande från andra resurser
  • cwd — katalog varifrån kommandot ska köras
  • användare — användaren som kommandot ska köras från
  • leverantör - hur man kör kommandot:
    • posix — en underordnad process skapas helt enkelt, var noga med att specificera bana
    • skal - kommandot startas i skalet /bin/sh, kanske inte specificeras bana, kan du använda globbing, rör och andra skalfunktioner. Upptäcks vanligtvis automatiskt om det finns några specialtecken (|, ;, &&, || och så vidare).

cron

Styr cronjobs.

alternativ:

  • resursnamn - bara någon slags identifierare
  • säkerställa — kronjobbtillstånd:
    • present - skapa om inte finns
    • absent - radera om det finns
  • kommando - vilket kommando som ska köras
  • miljö — i vilken miljö kommandot ska köras (lista över miljövariabler och deras värden via =)
  • användare — från vilken användare som kommandot ska köras
  • minut, timme, veckodag, månad, månad dag — när ska man köra cron. Om något av dessa attribut inte anges kommer dess värde i crontab att vara *.

I Puppet 6.0 cron som om borttagen från lådan i puppetserver, så det finns ingen dokumentation på den allmänna sidan. Men han ligger i lådan i puppet-agent, så det finns ingen anledning att installera den separat. Du kan se dokumentationen för det i dokumentationen för den femte versionen av PuppetEller på GitHub.

Om resurser i allmänhet

Krav på resursunikitet

Det vanligaste misstaget vi stöter på är Dubbeldeklaration. Det här felet uppstår när två eller flera resurser av samma typ med samma namn visas i katalogen.

Därför skriver jag igen: manifest för samma nod bör inte innehålla resurser av samma typ med samma titel!

Ibland finns det behov av att installera paket med samma namn, men med olika pakethanterare. I det här fallet måste du använda parametern nameför att undvika felet:

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

Andra resurstyper har liknande alternativ för att undvika dubbelarbete − name у service, command у exec, och så vidare.

Metaparametrar

Varje resurstyp har några speciella parametrar, oavsett dess natur.

Fullständig lista över metaparametrar i Puppet-dokumentationen.

Kort lista:

  • kräver — denna parameter indikerar vilka resurser denna resurs är beroende av.
  • innan - Den här parametern anger vilka resurser som är beroende av den här resursen.
  • prenumerera — denna parameter anger från vilka resurser denna resurs får meddelanden.
  • meddela — Den här parametern anger vilka resurser som får meddelanden från den här resursen.

Alla de listade metaparametrarna accepterar antingen en enskild resurslänk eller en uppsättning länkar inom hakparenteser.

Länkar till resurser

En resurslänk är helt enkelt ett omnämnande av resursen. De används främst för att indikera beroenden. Att hänvisa till en icke-existerande resurs kommer att orsaka ett kompileringsfel.

Syntaxen för länken är som följer: resurstyp med stor bokstav (om typnamnet innehåller dubbla kolon, är varje del av namnet mellan kolon versaler), sedan resursnamnet inom hakparenteser (namnets skiftläge). ändras inte!). Det ska inte finnas några mellanslag, hakparenteser skrivs omedelbart efter typnamnet.

Exempel:

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

Beroenden och meddelanden

Dokumentation här.

Som nämnts tidigare är enkla beroenden mellan resurser transitiva. Var förresten försiktig när du lägger till beroenden - du kan skapa cykliska beroenden, vilket kommer att orsaka ett kompileringsfel.

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

  • Om resursen får ett meddelande uppdateras det. Uppdateringsåtgärderna beror på resurstypen − exec kör kommandot, service startar om tjänsten, paket installerar om paketet. Om resursen inte har en definierad uppdateringsåtgärd händer ingenting.
  • Under en körning av Puppet uppdateras resursen inte mer än en gång. Detta är möjligt eftersom meddelanden inkluderar beroenden och beroendediagrammet inte innehåller cykler.
  • Om Puppet ändrar status för en resurs, skickar resursen meddelanden till alla resurser som prenumererar på den.
  • Om en resurs uppdateras skickar den meddelanden till alla resurser som prenumererar på den.

Hanterar ospecificerade parametrar

Som regel, om någon resursparameter inte har ett standardvärde och denna parameter inte anges i manifestet, kommer Puppet inte att ändra den här egenskapen för motsvarande resurs på noden. Till exempel om en resurs av typen fil parameter inte specificerad owner, då kommer Puppet inte att ändra ägaren till motsvarande fil.

Introduktion till klasser, variabler och definitioner

Anta att vi har flera noder som har samma del av konfigurationen, men det finns också skillnader - annars skulle vi kunna beskriva det hela i ett block node {}. Naturligtvis kan du helt enkelt kopiera identiska delar av konfigurationen, men generellt sett är detta en dålig lösning - konfigurationen växer, och om du ändrar den allmänna delen av konfigurationen måste du redigera samma sak på många ställen. Samtidigt är det lätt att göra ett misstag, och i allmänhet uppfanns principen DRY (upprepa inte dig själv) av en anledning.

För att lösa detta problem finns en sådan design som klass.

Классы

Klass är ett namngivet block med tallrikskod. Klasser behövs för att återanvända kod.

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

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

Därefter kan klassen användas:

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

Ett exempel från föregående uppgift - låt oss flytta installationen och konfigurationen av nginx till en klass:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    include nginx_example
}

variabler

Klassen från föregående exempel är inte alls flexibel eftersom den alltid ger samma nginx-konfiguration. Låt oss göra sökvägen till konfigurationsvariabeln, sedan kan den här klassen användas för att installera nginx med vilken konfiguration som helst.

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

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

Dessutom kan en variabel endast nås efter att den har deklarerats, annars blir variabelns värde undef.

Exempel på att arbeta med variabler:

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

Puppet har namnrymder, och variablerna har följaktligen synlighetsområde: En variabel med samma namn kan definieras i olika namnområden. När man bestämmer värdet på en variabel, söks variabeln i det aktuella namnområdet, sedan i det omslutande namnområdet och så vidare.

Exempel på namnutrymme:

  • global - variabler utanför klass- eller nodbeskrivningen går dit;
  • nodnamnutrymme i nodbeskrivningen;
  • klassnamnområde i klassbeskrivningen.

För att undvika oklarheter när du kommer åt en variabel kan du ange namnutrymmet i variabelnamnet:

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

Låt oss komma överens om att sökvägen till nginx-konfigurationen ligger i variabeln $nginx_conf_source. Då ser klassen ut så här:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # здесь используем переменную вместо фиксированной строки
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    $nginx_conf_source = 'puppet:///modules/example/nginx-conf'
    include nginx_example
}

Det givna exemplet är dock dåligt eftersom det finns någon "hemlig kunskap" om att någonstans i klassen används en variabel med ett sådant och ett sådant namn. Det är mycket mer korrekt att göra denna kunskap generell – klasser kan ha parametrar.

Klassparametrar är variabler i klassens namnutrymme, de specificeras i klasshuvudet och kan användas som vanliga variabler i klasskroppen. Parametervärden anges när klassen används i manifestet.

Parametern kan ställas in på ett standardvärde. Om en parameter inte har ett standardvärde och värdet inte ställs in när det används, kommer det att orsaka ett kompileringsfel.

Låt oss parametrisera klassen från exemplet ovan och lägga till två parametrar: den första, som krävs, är sökvägen till konfigurationen, och den andra, valfri, är 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 skrivs variabler. Äta många datatyper. Datatyper används vanligtvis för att validera parametervärden som skickas till klasser och definitioner. Om den angivna parametern inte matchar den angivna typen kommer ett kompileringsfel att uppstå.

Typen skrivs omedelbart före parameternamnet:

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

Klasser: inkludera klassnamn kontra klass{'klassnamn':}

Varje klass är en resurs av typ klass. Som med alla andra typer av resurser kan det inte finnas två instanser av samma klass på samma nod.

Om du försöker lägga till en klass till samma nod två gånger med class { 'classname':} (ingen skillnad, med olika eller identiska parametrar), kommer det att uppstå ett kompileringsfel. Men om du använder en klass i resursstilen kan du omedelbart explicit ställa in alla dess parametrar i manifestet.

Däremot om du använder include, då kan klassen läggas till så många gånger som önskas. Faktum är att include är en idempotent funktion som kontrollerar om en klass har lagts till i katalogen. Om klassen inte finns i katalogen lägger den till den, och om den redan finns gör den ingenting. Men vid användning include Du kan inte ställa in klassparametrar under klassdeklaration - alla nödvändiga parametrar måste ställas in i en extern datakälla - Hiera eller ENC. Vi kommer att prata om dem i nästa artikel.

Definierar

Som sades i föregående block, kan samma klass inte finnas på en nod mer än en gång. I vissa fall behöver du dock kunna använda samma kodblock med olika parametrar på samma nod. Det finns med andra ord ett behov av en egen resurstyp.

Till exempel, för att installera PHP-modulen, gör vi följande i Avito:

  1. Installera paketet med denna modul.
  2. Låt oss skapa en konfigurationsfil för den här modulen.
  3. Vi skapar en symbollänk till konfigurationen för php-fpm.
  4. Vi skapar en symbollänk till konfigurationen för php cli.

I sådana fall kan en design som t.ex definiera (definiera, definierad typ, definierad resurstyp). En Define liknar en klass, men det finns skillnader: för det första är varje Define en resurstyp, inte en resurs; för det andra har varje definition en implicit parameter $title, där resursnamnet går när det deklareras. Precis som i fallet med klasser måste först en definition beskrivas, varefter den kan användas.

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

define php74::module (
  $php_module_name = $title,
  $php_package_name = "php7.4-${title}",
  $version = 'installed',
  $priority = '20',
  $data = "extension=${title}.son",
  $php_module_path = '/etc/php/7.4/mods-available',
) {
  package { $php_package_name:
    ensure          => $version,
    install_options => ['-o', 'DPkg::NoTriggers=true'],  # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
  }
  -> file { "${php_module_path}/${php_module_name}.ini":
    ensure  => $ensure,
    content => $data,
  }
  file { "/etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
  file { "/etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
}

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

Det enklaste sättet att fånga Duplicate-deklarationsfelet är i Define. Detta händer om en definition har en resurs med ett konstant namn, och det finns två eller flera instanser av denna definition på någon nod.

Det är lätt att skydda sig mot detta: alla resurser i definitionen måste ha ett namn beroende på $title. Ett alternativ är idempotent tillägg av resurser; i det enklaste fallet räcker det att flytta resurserna som är gemensamma för alla instanser av definitionen till en separat klass och inkludera denna klass i definitionen - funktion include idempotent.

Det finns andra sätt att uppnå idempotens när man lägger till resurser, nämligen att använda funktioner defined и ensure_resources, men jag ska berätta om det i nästa avsnitt.

Beroenden och meddelanden för klasser och definitioner

Klasser och definitioner lägger till följande regler för hantering av beroenden och meddelanden:

  • beroende av en klass/define lägger till beroenden på alla resurser i klassen/define;
  • ett klass/definiera-beroende lägger till beroenden till alla klass/definiera-resurser;
  • class/define notification meddelar alla resurser i klassen/define;
  • class/define prenumeration prenumererar på alla resurser i klassen/define.

Villkorliga uttalanden och väljare

Dokumentation här.

if

Allt är enkelt här:

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

såvida inte

om inte är ett om i omvänd riktning: kodblocket kommer att exekveras om uttrycket är falskt.

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

Vid

Det är inget komplicerat här heller. Du kan använda vanliga värden (strängar, siffror, etc.), reguljära uttryck och datatyper som värden.

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

Väljare

En väljare är en språkkonstruktion som liknar case, men istället för att köra ett kodblock returnerar det ett värde.

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

moduler

När konfigurationen är liten kan den enkelt hållas i ett manifest. Men ju fler konfigurationer vi beskriver, desto fler klasser och noder finns det i manifestet, det växer och det blir obekvämt att arbeta med.

Dessutom finns problemet med kodåteranvändning - när all kod finns i ett manifest är det svårt att dela denna kod med andra. För att lösa dessa två problem har Puppet en enhet som kallas moduler.

moduler - dessa är uppsättningar av klasser, definitioner och andra Puppet-enheter placerade i en separat katalog. Med andra ord är en modul en oberoende del av Puppet-logik. Det kan till exempel finnas en modul för att arbeta med nginx, och den kommer att innehålla vad och bara det som behövs för att arbeta med nginx, eller så kan det finnas en modul för att arbeta med PHP, och så vidare.

Moduler är versionerade, och modulers beroende av varandra stöds också. Det finns ett öppet förråd av moduler - Puppet Forge.

På dockservern finns moduler i underkatalogen moduler i rotkatalogen. Inuti varje modul finns ett standardkatalogschema - manifest, filer, mallar, lib, och så vidare.

Filstruktur i en modul

Modulens rot kan innehålla följande kataloger med beskrivande namn:

  • manifests - den innehåller manifest
  • files - den innehåller filer
  • templates - den innehåller mallar
  • lib — den innehåller Ruby-kod

Detta är inte en komplett lista över kataloger och filer, men det räcker för den här artikeln för nu.

Namn på resurser och namn på filer i modulen

Dokumentation här.

Resurser (klasser, definitioner) i en modul kan inte heta vad du vill. Dessutom finns det en direkt överensstämmelse mellan namnet på en resurs och namnet på filen där Puppet kommer att leta efter en beskrivning av den resursen. Om du bryter mot namnreglerna kommer Puppet helt enkelt inte att hitta resursbeskrivningen, och du kommer att få ett kompileringsfel.

Reglerna är enkla:

  • Alla resurser i en modul måste finnas i modulens namnutrymme. Om modulen anropas foo, då ska alla resurser i den namnges foo::<anything>, eller bara foo.
  • Resursen med modulens namn måste finnas i filen init.pp.
  • För andra resurser är filnamnsschemat följande:
    • prefixet med modulnamnet tas bort
    • alla dubbla kolon, om några, ersätts med snedstreck
    • förlängning läggs till .pp

Jag ska demonstrera med ett exempel. Låt oss säga att jag skriver en modul nginx. Den innehåller följande resurser:

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

mallar

Du vet säkert själv vad mallar är; jag kommer inte att beskriva dem i detalj här. Men jag lämnar det för säkerhets skull länk till Wikipedia.

Hur man använder mallar: Innebörden av en mall kan utökas med en funktion template, som skickas vägen till mallen. För resurser av typ fil används tillsammans med parametern content. Till exempel, så här:

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

Visa sökväg <modulename>/<filename> innebär fil <rootdir>/modules/<modulename>/templates/<filename>.

Dessutom finns en funktion inline_template — den tar emot malltexten som indata, inte filnamnet.

Inom mallar kan du använda alla Puppet-variabler i det aktuella omfånget.

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

Kort om ERB

Kontrollstrukturer:

  • <%= ВЫРАЖЕНИЕ %> — infoga uttryckets värde
  • <% ВЫРАЖЕНИЕ %> — beräkna värdet på ett uttryck (utan att infoga det). Villkorliga uttalanden (if) och loopar (var och en) går vanligtvis här.
  • <%# КОММЕНТАРИЙ %>

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

För att komma åt variabler från manifestet måste du lägga till @ till variabelnamnet. För att ta bort en radbrytning som visas efter en kontrollkonstruktion måste du använda en avslutande tagg -%>.

Exempel på användning av mallen

Låt oss säga att jag skriver en modul för att styra 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 motsvarande mall zoo.cfg.erb - Alltså:

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

dataDir=<%= @datadir %>

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

Fakta och inbyggda variabler

Ofta beror den specifika delen av konfigurationen på vad som för närvarande händer på noden. Till exempel, beroende på vad Debian-versionen är, måste du installera en eller annan version av paketet. Du kan övervaka allt detta manuellt, omskrivning av manifest om noder ändras. Men detta är inte ett seriöst tillvägagångssätt, automatisering är mycket bättre.

För att få information om noder har Puppet en mekanism som kallas fakta. Fakta - detta är information om noden, tillgänglig i manifest i form av vanliga variabler i det globala namnområdet. Till exempel värdnamn, operativsystemversion, processorarkitektur, lista över användare, lista över nätverksgränssnitt och deras adresser och mycket, mycket mer. Fakta finns i manifest och mallar som vanliga variabler.

Ett exempel på att arbeta med fakta:

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

Formellt sett har ett faktum ett namn (sträng) och ett värde (olika typer finns tillgängliga: strängar, arrayer, ordböcker). Äta uppsättning inbyggda fakta. Du kan också skriva din egen. Faktasamlare beskrivs liknande funktioner i Ruby, eller som körbara filer. Fakta kan också presenteras i formuläret textfiler med data på noderna.

Under drift kopierar dock-agenten först alla tillgängliga faktainsamlare från papperservern till noden, varefter den startar dem och skickar insamlad fakta till servern; Efter detta börjar servern kompilera katalogen.

Fakta i form av körbara filer

Sådana fakta placeras i moduler i katalogen facts.d. Naturligtvis måste filerna vara körbara. När de körs måste de mata ut information till standardutdata i antingen YAML- eller nyckel=värde-format.

Glöm inte att fakta gäller för alla noder som styrs av poppet-servern som din modul är distribuerad till. Se därför till i skriptet att kontrollera att systemet har alla program och filer som behövs för att ditt faktum ska fungera.

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

Ruby fakta

Sådana 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

Sådana fakta placeras på noder i katalogen /etc/facter/facts.d i gamla Puppet eller /etc/puppetlabs/facts.d i den nya dockan.

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

Att komma till fakta

Det finns två sätt att närma sig fakta:

  • genom ordboken $facts: $facts['fqdn'];
  • använder faktanamnet som variabelnamn: $fqdn.

Det är bäst att använda en ordbok $facts, eller ännu bättre, indikera det globala namnutrymmet ($::facts).

Här är den relevanta delen av dokumentationen.

Inbyggda variabler

Förutom fakta finns det också vissa variabler, tillgängligt i det globala namnområdet.

  • pålitliga fakta — variabler som hämtas från klientens certifikat (eftersom certifikatet vanligtvis utfärdas på en poppet-server kan agenten inte bara ta och ändra sitt certifikat, så variablerna är "betrodda"): namnet på certifikatet, namnet på värd och domän, tillägg från certifikatet.
  • serverfakta —variabler relaterade till information om servern—version, namn, serverns IP-adress, miljö.
  • agentfakta — variabler som läggs till direkt av puppet-agent, och inte av fakta — certifikatnamn, agentversion, puppet-version.
  • mastervariabler - Pappetmaster-variabler (sic!). Det är ungefär samma som i serverfakta, plus konfigurationsparametervärden är tillgängliga.
  • kompilatorvariabler — kompilatorvariabler som skiljer sig åt i varje scope: namnet på den aktuella modulen och namnet på modulen där det aktuella objektet användes. De kan till exempel användas för att kontrollera att dina privata klasser inte används direkt från andra moduler.

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

Artikeln innehöll många exempel på dockkod, men berättade inte alls för oss hur man kör den här koden. Nåväl, jag rättar mig.

En agent räcker för att köra Puppet, men i de flesta fall behöver du också en server.

medel

Åtminstone sedan version XNUMX, puppet-agent-paket från officiellt Puppetlabs-förråd innehåller alla beroenden (ruby och motsvarande ädelstenar), så det finns inga installationssvårigheter (jag talar om Debian-baserade distributioner - vi använder inte RPM-baserade distributioner).

I det enklaste fallet, för att använda dockkonfigurationen, räcker det att starta agenten i serverlöst läge: förutsatt att marionettkoden kopieras till noden, starta 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 naturligtvis bättre att ställa in servern och köra agenter på noderna i demonläge - då kommer de en gång i halvtimmen att tillämpa konfigurationen som laddats ner från servern.

Du kan imitera push-modellen av arbete - gå till noden du är intresserad av och börja sudo puppet agent -t. Nyckel -t (--test) innehåller faktiskt flera alternativ som kan aktiveras individuellt. Dessa alternativ inkluderar följande:

  • kör inte i demonläge (som standard startar agenten i demonläge);
  • stäng av efter applicering av katalogen (som standard kommer agenten att fortsätta att arbeta och tillämpa konfigurationen en gång var halvtimme);
  • skriva en detaljerad arbetslogg;
  • visa ändringar i filer.

Agenten har ett driftläge utan ändringar - du kan använda det när du inte är säker på att du har skrivit rätt konfiguration och vill kontrollera exakt vad agenten kommer att ändra under drift. Detta läge aktiveras av parametern --noop på kommandoraden: sudo puppet agent -t --noop.

Dessutom kan du aktivera felsökningsloggen för arbetet - i den skriver marionett om alla åtgärder den utför: om resursen som den för närvarande bearbetar, om parametrarna för denna resurs, om vilka program den startar. Naturligtvis är detta en parameter --debug.

Server

Jag kommer inte att överväga den fullständiga installationen av pappetservern och distribuera kod till den i den här artikeln; jag kommer bara att säga att det direkt finns en fullt fungerande version av servern som inte kräver ytterligare konfiguration för att fungera med ett litet antal noder (säg upp till hundra). Ett större antal noder kommer att kräva justering - som standard startar marionettserver inte mer än fyra arbetare, för bättre prestanda måste du öka antalet och glöm inte att öka minnesgränserna, annars kommer servern att samla in skräp för det mesta.

Koddistribution - om du behöver det snabbt och enkelt, titta då (på r10k)[https://github.com/puppetlabs/r10k], för små installationer borde det vara tillräckligt.

Tillägg 2: Riktlinjer för kodning

  1. Placera all logik i klasser och definitioner.
  2. Behåll klasser och definitioner i moduler, inte i manifest som beskriver noder.
  3. Använd fakta.
  4. Gör inte ifs baserat på värdnamn.
  5. Lägg gärna till parametrar för klasser och definitioner - detta är bättre än implicit logik gömd i kroppen av klassen/definitionen.

Jag kommer att förklara varför jag rekommenderar att du gör detta 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 undersökningen. Logga in, Snälla du.

Faktum är att det finns mycket mer material - jag kan skriva artiklar om följande ämnen, rösta på det du skulle vara intresserad av att läsa om:

  • 59,1%Avancerade marionettkonstruktioner - lite skit på nästa nivå: loopar, kartläggning och andra lambda-uttryck, resurssamlare, exporterade resurser och kommunikation mellan värdar via Puppet, taggar, leverantörer, abstrakta datatyper.13
  • 31,8%"I'm my mother's admin" eller hur vi i Avito blev vänner med flera poppet-servrar av olika versioner, och i princip delen om att administrera poppet-servern.7
  • 81,8%Hur vi skriver marionettkod: instrumentering, dokumentation, testning, CI/CD.18

22 användare röstade. 9 användare avstod från att rösta.

Källa: will.com