Inleiding tot marionet

Puppet is 'n konfigurasiebestuurstelsel. Dit word gebruik om gashere na die verlangde toestand te bring en hierdie toestand te handhaaf.

Ek werk nou al meer as vyf jaar saam met Puppet. Hierdie teks is in wese 'n vertaalde en herordende samestelling van sleutelpunte uit die amptelike dokumentasie, wat beginners in staat sal stel om vinnig die essensie van Puppet te verstaan.

Inleiding tot marionet

Basiese inligting

Puppet se bedryfstelsel is kliënt-bediener, hoewel dit ook bedienerlose werking met beperkte funksionaliteit ondersteun.

'n Trek-operasiemodel word gebruik: by verstek, een keer elke halfuur, kontak kliënte die bediener vir 'n konfigurasie en pas dit toe. As jy met Ansible gewerk het, gebruik hulle 'n ander drukmodel: die administrateur begin die proses om die konfigurasie toe te pas, die kliënte self sal niks toepas nie.

Tydens netwerkkommunikasie word tweerigting TLS-enkripsie gebruik: die bediener en kliënt het hul eie private sleutels en ooreenstemmende sertifikate. Tipies reik die bediener sertifikate vir kliënte uit, maar in beginsel is dit moontlik om 'n eksterne CA te gebruik.

Inleiding tot manifeste

In Puppet terminologie na die marionetbediener verbind nodusse (nodes). Die konfigurasie vir die nodusse word geskryf in manifeste in 'n spesiale programmeertaal - Puppet DSL.

Puppet DSL is 'n verklarende taal. Dit beskryf die gewenste toestand van die nodus in die vorm van verklarings van individuele hulpbronne, byvoorbeeld:

  • Die lêer bestaan ​​en het spesifieke inhoud.
  • Die pakket is geïnstalleer.
  • Die diens het begin.

Hulpbronne kan onderling verbind word:

  • Daar is afhanklikhede, dit beïnvloed die volgorde waarin hulpbronne gebruik word.
    Byvoorbeeld, "installeer eers die pakket, wysig dan die konfigurasielêer en begin dan die diens."
  • Daar is kennisgewings - as 'n hulpbron verander het, stuur dit kennisgewings na die hulpbronne wat daarop ingeteken is.
    Byvoorbeeld, as die konfigurasielêer verander, kan jy die diens outomaties herbegin.

Daarbenewens het die Puppet DSL funksies en veranderlikes, sowel as voorwaardelike stellings en kiesers. Verskeie sjabloonmeganismes word ook ondersteun - EPP en ERB.

Puppet is in Ruby geskryf, so baie van die konstrukte en terme is daarvandaan geneem. Ruby laat jou toe om Puppet uit te brei - voeg komplekse logika, nuwe soorte hulpbronne, funksies by.

Terwyl Puppet aan die gang is, word manifeste vir elke spesifieke nodus op die bediener in 'n gids saamgestel. Каталог is 'n lys van hulpbronne en hul verwantskappe na die berekening van die waarde van funksies, veranderlikes en uitbreiding van voorwaardelike stellings.

Sintaksis en kodestyl

Hier is afdelings van die amptelike dokumentasie wat jou sal help om die sintaksis te verstaan ​​as die voorbeelde verskaf nie genoeg is nie:

Hier is 'n voorbeeld van hoe die manifes lyk:

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

Inkeping en lynbreuke is nie 'n vereiste deel van die manifes nie, maar daar is 'n aanbevole styl gids. Opsomming:

  • Twee-spasie inkepings, oortjies word nie gebruik nie.
  • Krulhakies word deur 'n spasie geskei; dubbelpunte word nie deur 'n spasie geskei nie.
  • Kommas na elke parameter, insluitend die laaste een. Elke parameter is op 'n aparte lyn. 'n Uitsondering word gemaak vir die geval sonder parameters en een parameter: jy kan op een reël en sonder 'n komma skryf (d.w.s. resource { 'title': } и resource { 'title': param => value }).
  • Die pyle op die parameters moet op dieselfde vlak wees.
  • Hulpbronverwantskappyle is voor hulle geskryf.

Ligging van lêers op papierbediener

Vir verdere verduideliking sal ek die konsep van "wortelgids" bekendstel. Die wortelgids is die gids wat die Puppet-konfigurasie vir 'n spesifieke nodus bevat.

Die wortelgids wissel na gelang van die weergawe van Puppet en die omgewings wat gebruik word. Omgewings is onafhanklike stelle konfigurasie wat in aparte gidse gestoor word. Word gewoonlik in kombinasie met git gebruik, in welke geval omgewings uit git-takke geskep word. Gevolglik is elke nodus in een of ander omgewing geleë. Dit kan op die nodus self gekonfigureer word, of in ENC, waaroor ek in die volgende artikel sal praat.

  • In die derde weergawe ("ou Puppet") was die basisgids /etc/puppet. Die gebruik van omgewings is opsioneel - ons gebruik dit byvoorbeeld nie met die ou Puppet nie. As omgewings gebruik word, word dit gewoonlik in gestoor /etc/puppet/environments, sal die wortelgids die omgewingsgids wees. As omgewings nie gebruik word nie, sal die wortelgids die basisgids wees.
  • Vanaf die vierde weergawe ("new Puppet") het die gebruik van omgewings verpligtend geword, en die basisgids is geskuif na /etc/puppetlabs/code. Gevolglik word omgewings in gestoor /etc/puppetlabs/code/environments, wortelgids is die omgewingsgids.

Daar moet 'n subgids in die wortelgids wees manifests, wat een of meer manifeste bevat wat die nodusse beskryf. Daarbenewens moet daar 'n subgids wees modules, wat die modules bevat. Ek sal jou bietjie later vertel watter modules is. Daarbenewens kan die ou Puppet ook 'n subgids hê files, wat verskeie lêers bevat wat ons na die nodusse kopieer. In die nuwe Puppet word alle lêers in modules geplaas.

Manifestlêers het die uitbreiding .pp.

'n Paar voorbeelde van gevegte

Beskrywing van die nodus en hulpbron daarop

Op die nodus server1.testdomain 'n lêer moet geskep word /etc/issue met inhoud Debian GNU/Linux n l. Die lêer moet deur 'n gebruiker en groep besit word root, toegangsregte moet wees 644.

Ons skryf 'n manifes:

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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Verwantskappe tussen hulpbronne op 'n nodus

Op die nodus server2.testdomain nginx moet loop, werk met 'n voorheen voorbereide konfigurasie.

Kom ons ontbind die probleem:

  • Die pakket moet geïnstalleer word nginx.
  • Dit is nodig dat die konfigurasielêers vanaf die bediener gekopieer word.
  • Die diens moet loop nginx.
  • As die konfigurasie opgedateer word, moet die diens herbegin word.

Ons skryf 'n manifes:

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 получает уведомление,
  # соответствующий сервис перезапускается.
}

Vir dit om te werk, benodig jy ongeveer die volgende lêerligging op die marionetbediener:

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

Hulpbrontipes

'n Volledige lys van ondersteunde hulpbrontipes kan hier gevind word in die dokumentasie, hier sal ek vyf basiese tipes beskryf, wat in my praktyk genoeg is om die meeste probleme op te los.

lêer

Bestuur lêers, gidse, simskakels, hul inhoud en toegangsregte.

opsies:

  • hulpbron naam - pad na die lêer (opsioneel)
  • pad - pad na die lêer (as dit nie in die naam gespesifiseer is nie)
  • verseker - leër tipe:
    • absent - verwyder 'n lêer
    • present - daar moet 'n lêer van enige tipe wees (as daar geen lêer is nie, sal 'n gewone lêer geskep word)
    • file - gereelde lêer
    • directory - gids
    • link - simboliek
  • inhoud - lêerinhoud (slegs geskik vir gewone lêers, kan nie saam met bron of teiken)
  • bron — 'n skakel na die pad vanwaar u die inhoud van die lêer wil kopieer (kan nie saam met inhoud of teiken). Kan gespesifiseer word as óf 'n URI met 'n skema puppet: (dan sal lêers van die marionetbediener gebruik word), en met die skema http: (Ek hoop dit is duidelik wat in hierdie geval sal gebeur), en selfs met die diagram file: of as 'n absolute pad sonder 'n skema (dan sal die lêer van die plaaslike FS op die nodus gebruik word)
  • teiken - waar die simlink moet wys (kan nie saam met inhoud of bron)
  • eienaar — die gebruiker wat die lêer moet besit
  • groep — die groep waaraan die lêer behoort te behoort
  • af - lêertoestemmings (as 'n string)
  • verpleeg - maak rekursiewe gidsverwerking moontlik
  • skoonmaak - maak dit moontlik om lêers uit te vee wat nie in Puppet beskryf word nie
  • dwing - maak dit moontlik om dopgehou te verwyder wat nie in Puppet beskryf word nie

pakket

Installeer en verwyder pakkette. In staat om kennisgewings te hanteer - installeer die pakket weer as die parameter gespesifiseer is reinstall_on_refresh.

opsies:

  • hulpbron naam - pakketnaam (opsioneel)
  • naam - pakketnaam (indien nie in die naam gespesifiseer nie)
  • verskaffer - pakketbestuurder om te gebruik
  • verseker — gewenste toestand van die pakket:
    • present, installed - enige weergawe geïnstalleer
    • latest - nuutste weergawe geïnstalleer
    • absent - uitgevee (apt-get remove)
    • purged - uitgevee saam met konfigurasielêers (apt-get purge)
    • held - pakket weergawe is gesluit (apt-mark hold)
    • любая другая строка - die gespesifiseerde weergawe is geïnstalleer
  • reinstall_on_refresh - As 'n true, dan na ontvangs van die kennisgewing sal die pakket weer geïnstalleer word. Nuttig vir brongebaseerde verspreidings, waar herbou van pakkette nodig mag wees wanneer bouparameters verander word. Verstek false.

diens

Bestuur dienste. In staat om kennisgewings te verwerk - herbegin die diens.

opsies:

  • hulpbron naam — diens wat bestuur moet word (opsioneel)
  • naam — die diens wat bestuur moet word (indien nie in die naam gespesifiseer nie)
  • verseker — gewenste toestand van die diens:
    • running - van stapel gestuur
    • stopped - gestop
  • in staat te stel - beheer die vermoë om die diens te begin:
    • true - outorun is geaktiveer (systemctl enable)
    • mask - vermom (systemctl mask)
    • false - outorun is gedeaktiveer (systemctl disable)
  • herlaai - opdrag om die diens te herbegin
  • status - opdrag om diensstatus na te gaan
  • het herbegin — dui aan of die diens initscript herbegin ondersteun. As false en die parameter word gespesifiseer herlaai — die waarde van hierdie parameter word gebruik. As false en parameter herlaai nie gespesifiseer nie - die diens word gestop en begin herbegin (maar systemd gebruik die opdrag systemctl restart).
  • het status — dui aan of die diensinskrywing die opdrag ondersteun status. indien false, dan word die parameterwaarde gebruik status. Verstek true.

exec

Voer eksterne opdragte uit. As jy nie parameters spesifiseer nie skep, slegs as, tensy of verfrissend, sal die opdrag uitgevoer word elke keer as Puppet uitgevoer word. In staat om kennisgewings te verwerk - voer 'n opdrag uit.

opsies:

  • hulpbron naam - opdrag wat uitgevoer moet word (opsioneel)
  • opdrag - die opdrag wat uitgevoer moet word (indien dit nie in die naam gespesifiseer is nie)
  • pad - paaie om na die uitvoerbare lêer te soek
  • slegs as - as die opdrag gespesifiseer in hierdie parameter voltooi is met 'n nul-terugvoerkode, sal die hoofopdrag uitgevoer word
  • tensy - as die opdrag gespesifiseer in hierdie parameter voltooi is met 'n nie-nul terugkeerkode, sal die hoofopdrag uitgevoer word
  • skep - as die lêer wat in hierdie parameter gespesifiseer is nie bestaan ​​nie, sal die hoofopdrag uitgevoer word
  • verfrissend - As 'n true, dan sal die opdrag slegs uitgevoer word wanneer hierdie bestuur kennisgewing van ander hulpbronne ontvang
  • cwd - gids waaruit die opdrag uitgevoer moet word
  • gebruiker - die gebruiker van wie om die opdrag uit te voer
  • verskaffer - hoe om die opdrag uit te voer:
    • POSIX - 'n kinderproses word eenvoudig geskep, maak seker om te spesifiseer pad
    • dop - die opdrag word in die dop geloods /bin/sh, mag nie gespesifiseer word nie pad, kan jy globbing, pype en ander dopkenmerke gebruik. Word gewoonlik outomaties bespeur as daar spesiale karakters is (|, ;, &&, || ens).

cron

Beheer cronjobs.

opsies:

  • hulpbron naam - net 'n soort identifiseerder
  • verseker - kroonjob staat:
    • present - skep as dit nie bestaan ​​nie
    • absent - verwyder as dit bestaan
  • opdrag - watter opdrag om uit te voer
  • omgewing - in watter omgewing om die opdrag uit te voer (lys van omgewingsveranderlikes en hul waardes via =)
  • gebruiker - van watter gebruiker die opdrag moet uitvoer
  • minuut, uur, weekdag, maand, maanddag - wanneer om cron te hardloop. As enige van hierdie eienskappe nie gespesifiseer is nie, sal die waarde daarvan in die crontab wees *.

In Puppet 6.0 cron asof uit die boks verwyder in puppetserver, so daar is geen dokumentasie op die algemene webwerf nie. Maar hy is in die boks in puppet-agent, so dit is nie nodig om dit apart te installeer nie. U kan die dokumentasie daarvoor sien in die dokumentasie vir die vyfde weergawe van PuppetOf op GitHub.

Oor hulpbronne in die algemeen

Vereistes vir hulpbron uniekheid

Die mees algemene fout wat ons teëkom is Duplikaat verklaring. Hierdie fout kom voor wanneer twee of meer hulpbronne van dieselfde tipe met dieselfde naam in die gids verskyn.

Daarom skryf ek weer: manifeste vir dieselfde nodus moet nie hulpbronne van dieselfde tipe met dieselfde titel bevat nie!

Soms is dit nodig om pakkette met dieselfde naam te installeer, maar met verskillende pakketbestuurders. In hierdie geval moet u die parameter gebruik nameom die fout te vermy:

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

Ander hulpbrontipes het soortgelyke opsies om duplisering te help vermy − name у diens, command у exec, en so aan.

Metaparameters

Elke hulpbrontipe het 'n paar spesiale parameters, ongeag die aard daarvan.

Volledige lys van meta parameters in die Puppet-dokumentasie.

Kort lys:

  • vereis — hierdie parameter dui aan van watter hulpbronne hierdie hulpbron afhanklik is.
  • voor - Hierdie parameter spesifiseer watter hulpbronne van hierdie hulpbron afhanklik is.
  • skryf — hierdie parameter spesifiseer van watter hulpbronne hierdie hulpbron kennisgewings ontvang.
  • in kennis stel — Hierdie parameter spesifiseer watter hulpbronne kennisgewings van hierdie hulpbron ontvang.

Al die gelyste metaparameters aanvaar óf 'n enkele hulpbronskakel óf 'n verskeidenheid skakels tussen vierkantige hakies.

Skakels na hulpbronne

'n Hulpbronskakel is bloot 'n vermelding van die hulpbron. Hulle word hoofsaaklik gebruik om afhanklikhede aan te dui. Die verwysing na 'n nie-bestaande hulpbron sal 'n samestellingsfout veroorsaak.

Die sintaksis van die skakel is soos volg: hulpbrontipe met 'n hoofletter (as die tipe naam dubbelpunte bevat, dan word elke deel van die naam tussen die dubbelpunte gekapitaliseer), dan die hulpbronnaam tussen vierkantige hakies (die geval van die naam verander nie!). Daar moet geen spasies wees nie; vierkantige hakies word onmiddellik na die tipe naam geskryf.

Voorbeeld:

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

Afhanklikhede en kennisgewings

Dokumentasie hier.

Soos vroeër genoem, is eenvoudige afhanklikhede tussen hulpbronne oorganklik. Terloops, wees versigtig wanneer jy afhanklikhede byvoeg - jy kan sikliese afhanklikhede skep, wat 'n samestellingsfout sal veroorsaak.

In teenstelling met afhanklikhede, is kennisgewings nie oorganklik nie. Die volgende reëls geld vir kennisgewings:

  • As die hulpbron 'n kennisgewing ontvang, word dit opgedateer. Die opdateringsaksies hang af van die hulpbrontipe − exec voer die opdrag uit, diens herbegin die diens, pakket herinstalleer die pakket. As die hulpbron nie 'n opdateringsaksie gedefinieer het nie, gebeur niks nie.
  • Tydens een lopie van Puppet word die hulpbron nie meer as een keer opgedateer nie. Dit is moontlik omdat kennisgewings afhanklikhede insluit en die afhanklikheidsgrafiek nie siklusse bevat nie.
  • As Puppet die toestand van 'n hulpbron verander, stuur die hulpbron kennisgewings aan alle hulpbronne wat daarop ingeteken is.
  • As 'n hulpbron opgedateer word, stuur dit kennisgewings aan alle hulpbronne wat daarop ingeteken is.

Hanteer ongespesifiseerde parameters

As 'n reël, as een of ander hulpbronparameter nie 'n verstekwaarde het nie en hierdie parameter is nie in die manifes gespesifiseer nie, sal Puppet nie hierdie eienskap vir die ooreenstemmende hulpbron op die nodus verander nie. Byvoorbeeld, as 'n hulpbron van tipe lêer parameter nie gespesifiseer nie owner, dan sal Puppet nie die eienaar van die ooreenstemmende lêer verander nie.

Inleiding tot klasse, veranderlikes en definisies

Gestel ons het verskeie nodusse wat dieselfde deel van die konfigurasie het, maar daar is ook verskille - anders kan ons dit alles in een blok beskryf node {}. Natuurlik kan jy eenvoudig identiese dele van die konfigurasie kopieer, maar oor die algemeen is dit 'n slegte oplossing - die konfigurasie groei, en as jy die algemene deel van die konfigurasie verander, sal jy dieselfde ding op baie plekke moet wysig. Terselfdertyd is dit maklik om 'n fout te maak, en oor die algemeen is die DRY (moenie jouself herhaal nie) beginsel met 'n rede uitgevind.

Om hierdie probleem op te los is daar so 'n ontwerp soos klas.

Klasse

Klas is 'n benoemde blok pop-kode. Klasse is nodig om kode te hergebruik.

Eerstens moet die klas beskryf word. Die beskrywing self voeg nêrens enige hulpbronne by nie. Die klas word in manifeste beskryf:

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

Hierna kan die klas gebruik word:

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

'n Voorbeeld van die vorige taak - kom ons skuif die installasie en konfigurasie van nginx na 'n klas:

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
}

veranderlikes

Die klas van die vorige voorbeeld is glad nie buigsaam nie, want dit bring altyd dieselfde nginx-konfigurasie. Kom ons maak die pad na die konfigurasie veranderlike, dan kan hierdie klas gebruik word om nginx met enige konfigurasie te installeer.

Dit kan gedoen word veranderlikes te gebruik.

Aandag: veranderlikes in Puppet is onveranderlik!

Daarbenewens kan 'n veranderlike slegs verkry word nadat dit verklaar is, anders sal die waarde van die veranderlike wees undef.

Voorbeeld van werk met veranderlikes:

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

Puppet het naamruimtes, en die veranderlikes, dienooreenkomstig, het gebied van sigbaarheid: 'n Veranderlike met dieselfde naam kan in verskillende naamruimtes gedefinieer word. Wanneer die waarde van 'n veranderlike opgelos word, word die veranderlike in die huidige naamruimte gesoek, dan in die omsluitende naamruimte, ensovoorts.

Naamruimte voorbeelde:

  • globaal - veranderlikes buite die klas of nodus beskrywing gaan daarheen;
  • nodus naamruimte in die node beskrywing;
  • klasnaamruimte in die klasbeskrywing.

Om dubbelsinnigheid te vermy wanneer toegang tot 'n veranderlike verkry word, kan jy die naamspasie in die veranderlike naam spesifiseer:

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

Kom ons stem saam dat die pad na die nginx-konfigurasie in die veranderlike lê $nginx_conf_source. Dan sal die klas so lyk:

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
}

Die gegewe voorbeeld is egter sleg omdat daar 'n "geheime kennis" is dat iewers binne die klas 'n veranderlike met so en so 'n naam gebruik word. Dit is baie meer korrek om hierdie kennis algemeen te maak - klasse kan parameters hê.

Klas parameters veranderlikes in die klasnaamruimte is, word hulle in die klasopskrif gespesifiseer en kan soos gewone veranderlikes in die klasliggaam gebruik word. Parameterwaardes word gespesifiseer wanneer die klas in die manifes gebruik word.

Die parameter kan op 'n verstekwaarde gestel word. As 'n parameter nie 'n verstekwaarde het nie en die waarde is nie gestel wanneer dit gebruik word nie, sal dit 'n samestellingsfout veroorsaak.

Kom ons parameteriseer die klas uit die voorbeeld hierbo en voeg twee parameters by: die eerste, vereis, is die pad na die konfigurasie, en die tweede, opsioneel, is die naam van die pakket met nginx (in Debian, byvoorbeeld, is daar pakkette 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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

In Puppet word veranderlikes getik. Eet baie datatipes. Datatipes word tipies gebruik om parameterwaardes wat na klasse en definisies deurgegee word, te valideer. As die geslaagde parameter nie ooreenstem met die gespesifiseerde tipe nie, sal 'n samestellingsfout voorkom.

Die tipe word onmiddellik voor die parameternaam geskryf:

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

Klasse: sluit klasnaam in teenoor klas{'klasnaam':}

Elke klas is 'n soort hulpbron klas. Soos met enige ander soort hulpbron, kan daar nie twee gevalle van dieselfde klas op dieselfde nodus wees nie.

As jy probeer om 'n klas by dieselfde nodus twee keer met behulp van class { 'classname':} (geen verskil, met verskillende of identiese parameters), sal daar 'n samestellingsfout wees. Maar as jy 'n klas in die hulpbronstyl gebruik, kan jy dadelik al sy parameters uitdruklik in die manifes stel.

As jy egter gebruik include, dan kan die klas soveel keer bygevoeg word as wat verlang word. Die feit is dat include is 'n idempotente funksie wat kontroleer of 'n klas by die gids gevoeg is. As die klas nie in die gids is nie, voeg dit dit by, en as dit reeds bestaan, doen dit niks. Maar in die geval van gebruik include Jy kan nie klasparameters stel tydens klasverklaring nie - alle vereiste parameters moet in 'n eksterne databron gestel word - Hiera of ENC. Ons sal in die volgende artikel oor hulle praat.

Definieer

Soos in die vorige blok gesê is, kan dieselfde klas nie meer as een keer op 'n nodus teenwoordig wees nie. In sommige gevalle moet jy egter dieselfde blok kode met verskillende parameters op dieselfde nodus kan gebruik. Met ander woorde, daar is 'n behoefte aan 'n hulpbrontipe van sy eie.

Byvoorbeeld, om die PHP-module te installeer, doen ons die volgende in Avito:

  1. Installeer die pakket met hierdie module.
  2. Kom ons skep 'n konfigurasielêer vir hierdie module.
  3. Ons skep 'n simskakel na die konfigurasie vir php-fpm.
  4. Ons skep 'n simskakel na die config vir php cli.

In sulke gevalle, 'n ontwerp soos definieer (definieer, gedefinieerde tipe, gedefinieerde hulpbrontipe). 'n Define is soortgelyk aan 'n klas, maar daar is verskille: eerstens, elke Define is 'n hulpbrontipe, nie 'n hulpbron nie; tweedens het elke definisie 'n implisiete parameter $title, waarheen die hulpbronnaam gaan wanneer dit verklaar word. Net soos in die geval van klasse, moet 'n definisie eers beskryf word, waarna dit gebruik kan word.

'n Vereenvoudigde voorbeeld met 'n module vir 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' }
}

Die maklikste manier om die duplikaatverklaringsfout op te spoor, is in Define. Dit gebeur as 'n definisie 'n hulpbron met 'n konstante naam het, en daar is twee of meer gevalle van hierdie definisie op een of ander nodus.

Dit is maklik om jouself hierteen te beskerm: alle hulpbronne binne die definisie moet 'n naam hê, afhangende van $title. 'n Alternatief is idempotente toevoeging van hulpbronne; in die eenvoudigste geval is dit genoeg om die hulpbronne wat algemeen is vir alle gevalle van die definisie na 'n aparte klas te skuif en hierdie klas in die definisie - funksie in te sluit include idempotent.

Daar is ander maniere om idempotensie te bereik wanneer hulpbronne bygevoeg word, naamlik die gebruik van funksies defined и ensure_resources, maar ek sal jou daarvan vertel in die volgende episode.

Afhanklikhede en kennisgewings vir klasse en definisies

Klasse en definisies voeg die volgende reëls by om afhanklikhede en kennisgewings te hanteer:

  • afhanklikheid van 'n klas/definieer voeg afhanklikhede van alle hulpbronne van die klas/definieer by;
  • 'n klas/definieer afhanklikheid voeg afhanklikhede by alle klas/definieer hulpbronne;
  • klas/definieer kennisgewing stel alle hulpbronne van die klas/definieer in kennis;
  • klas/definieer-intekening teken in op alle hulpbronne van die klas/definieer.

Voorwaardelike stellings en keurders

Dokumentasie hier.

if

Dit is eenvoudig hier:

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

tensy

tensy is 'n as in omgekeerde: die blok kode sal uitgevoer word as die uitdrukking vals is.

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

geval

Hier is ook niks ingewikkeld nie. Jy kan gereelde waardes (stringe, getalle, ens.), Gereelde uitdrukkings en datatipes as waardes gebruik.

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

Keurders

'n Kieser is 'n taalkonstruksie soortgelyk aan case, maar in plaas daarvan om 'n blok kode uit te voer, gee dit 'n waarde terug.

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

Modules

Wanneer die konfigurasie klein is, kan dit maklik in een manifes gehou word. Maar hoe meer konfigurasies ons beskryf, hoe meer klasse en nodusse is daar in die manifes, dit groei, en dit word ongerieflik om mee te werk.

Daarbenewens is daar die probleem van kode hergebruik - wanneer al die kode in een manifes is, is dit moeilik om hierdie kode met ander te deel. Om hierdie twee probleme op te los, het Puppet 'n entiteit genaamd modules.

Modules - dit is stelle klasse, definisies en ander Puppet-entiteite wat in 'n aparte gids geplaas word. Met ander woorde, 'n module is 'n onafhanklike stuk Puppet-logika. Daar kan byvoorbeeld 'n module wees om met nginx te werk, en dit sal bevat wat en net wat nodig is om met nginx te werk, of daar kan 'n module wees om met PHP te werk, ensovoorts.

Modules is weergawes, en afhanklikhede van modules van mekaar word ook ondersteun. Daar is 'n oop repository van modules - Puppet Forge.

Op die marionetbediener is modules in die modules subgids van die wortelgids geleë. Binne elke module is daar 'n standaard gidsskema - manifeste, lêers, sjablone, lib, ensovoorts.

Lêerstruktuur in 'n module

Die wortel van die module kan die volgende gidse met beskrywende name bevat:

  • manifests - dit bevat manifeste
  • files - dit bevat lêers
  • templates - dit bevat sjablone
  • lib - dit bevat Ruby-kode

Dit is nie 'n volledige lys van gidse en lêers nie, maar dit is vir eers genoeg vir hierdie artikel.

Name van hulpbronne en name van lêers in die module

Dokumentasie hier.

Hulpbronne (klasse, definisies) in 'n module kan nie genoem word wat jy ook al wil nie. Daarbenewens is daar 'n direkte ooreenstemming tussen die naam van 'n hulpbron en die naam van die lêer waarin Puppet sal soek vir 'n beskrywing van daardie hulpbron. As jy die naamreëls oortree, sal Puppet eenvoudig nie die hulpbronbeskrywing vind nie, en jy sal 'n samestellingsfout kry.

Die reëls is eenvoudig:

  • Alle hulpbronne in 'n module moet in die module naamruimte wees. As die module genoem word foo, dan moet alle hulpbronne daarin genoem word foo::<anything>, of net foo.
  • Die hulpbron met die naam van die module moet in die lêer wees init.pp.
  • Vir ander hulpbronne is die lêernaamskema soos volg:
    • die voorvoegsel met die modulenaam word weggegooi
    • alle dubbelpunte, indien enige, word met skuinsstreepies vervang
    • uitbreiding word bygevoeg .pp

Ek sal met 'n voorbeeld demonstreer. Kom ons sê ek skryf 'n module nginx. Dit bevat die volgende hulpbronne:

  • klas nginx beskryf in die manifes init.pp;
  • klas nginx::service beskryf in die manifes service.pp;
  • definieer nginx::server beskryf in die manifes server.pp;
  • definieer nginx::server::location beskryf in die manifes server/location.pp.

templates

U weet sekerlik self wat sjablone is; ek sal dit nie hier in detail beskryf nie. Maar ek sal dit los vir ingeval skakel na Wikipedia.

Hoe om sjablone te gebruik: Die betekenis van 'n sjabloon kan uitgebrei word deur 'n funksie te gebruik template, wat die pad na die sjabloon deurgegee word. Vir hulpbronne van tipe lêer saam met die parameter gebruik word content. Byvoorbeeld, soos volg:

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

Kyk pad <modulename>/<filename> impliseer lêer <rootdir>/modules/<modulename>/templates/<filename>.

Daarbenewens is daar 'n funksie inline_template - dit ontvang die sjabloon teks as invoer, nie die lêernaam nie.

Binne sjablone kan jy alle Puppet-veranderlikes in die huidige omvang gebruik.

Puppet ondersteun sjablone in ERB- en EPP-formaat:

Kortliks oor ERB

Beheerstrukture:

  • <%= ВЫРАЖЕНИЕ %> - voeg die waarde van die uitdrukking in
  • <% ВЫРАЖЕНИЕ %> — bereken die waarde van 'n uitdrukking (sonder om dit in te voeg). Voorwaardelike stellings (if) en lusse (elk) gaan gewoonlik hierheen.
  • <%# КОММЕНТАРИЙ %>

Uitdrukkings in ERB word in Ruby geskryf (ERB is eintlik Embedded Ruby).

Om toegang te verkry tot veranderlikes vanaf die manifes, moet jy byvoeg @ na die veranderlike naam. Om 'n reëlbreuk te verwyder wat na 'n kontrolekonstruksie verskyn, moet jy 'n sluitingsmerker gebruik -%>.

Voorbeeld van die gebruik van die sjabloon

Kom ons sê ek skryf 'n module om ZooKeeper te beheer. Die klas wat verantwoordelik is vir die skep van die konfigurasie lyk so:

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

En die ooreenstemmende sjabloon zoo.cfg.erb - Dus:

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

Feite en ingeboude veranderlikes

Dikwels hang die spesifieke deel van die konfigurasie af van wat tans op die nodus gebeur. Byvoorbeeld, afhangende van wat Debian-weergawe is, moet jy een of ander weergawe van die pakket installeer. U kan dit alles met die hand monitor, en herskryf manifesteer as nodusse verander. Maar dit is nie 'n ernstige benadering nie; outomatisering is baie beter.

Om inligting oor nodusse te bekom, het Puppet 'n meganisme wat feite genoem word. feite - dit is inligting oor die nodus, beskikbaar in manifeste in die vorm van gewone veranderlikes in die globale naamruimte. Byvoorbeeld, gasheernaam, bedryfstelselweergawe, verwerkerargitektuur, lys gebruikers, lys netwerkkoppelvlakke en hul adresse, en nog baie, baie meer. Feite is beskikbaar in manifeste en sjablone as gereelde veranderlikes.

'n Voorbeeld van werk met feite:

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

Formeel gesproke het 'n feit 'n naam (string) en 'n waarde (verskeie tipes is beskikbaar: stringe, skikkings, woordeboeke). Eet stel ingeboude feite. Jy kan ook jou eie skryf. Feiteversamelaars word beskryf soos funksies in Rubyof hoe uitvoerbare lêers. Feite kan ook in die vorm aangebied word tekslêers met data op die nodusse.

Tydens operasie kopieer die marionetagent eers alle beskikbare feiteversamelaars vanaf die pappetbediener na die nodus, waarna dit hulle begin en die versamelde feite na die bediener stuur; Hierna begin die bediener die katalogus saamstel.

Feite in die vorm van uitvoerbare lêers

Sulke feite word in modules in die gids geplaas facts.d. Natuurlik moet die lêers uitvoerbaar wees. Wanneer dit uitgevoer word, moet hulle inligting na standaarduitvoer uitvoer in óf YAML- óf sleutel=waarde-formaat.

Moenie vergeet dat die feite van toepassing is op alle nodusse wat beheer word deur die pop-bediener waarheen jou module ontplooi is nie. Daarom, in die skrif, sorg dat u seker maak dat die stelsel al die programme en lêers het wat nodig is vir u feit om te werk.

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

Ruby feite

Sulke feite word in modules in die gids geplaas 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

Teksfeite

Sulke feite word op nodusse in die gids geplaas /etc/facter/facts.d in ou Puppet of /etc/puppetlabs/facts.d in die nuwe Puppet.

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

Om by die feite uit te kom

Daar is twee maniere om die feite te benader:

  • deur die woordeboek $facts: $facts['fqdn'];
  • gebruik die feitnaam as die veranderlike naam: $fqdn.

Dit is die beste om 'n woordeboek te gebruik $facts, of selfs beter, dui die globale naamruimte aan ($::facts).

Hier is die relevante afdeling van die dokumentasie.

Ingeboude veranderlikes

Behalwe die feite, is daar ook sommige veranderlikes, beskikbaar in die globale naamruimte.

  • vertroude feite — veranderlikes wat uit die kliënt se sertifikaat geneem word (aangesien die sertifikaat gewoonlik op 'n pop-bediener uitgereik word, kan die agent nie net sy sertifikaat neem en verander nie, dus word die veranderlikes “vertrou”): die naam van die sertifikaat, die naam van die gasheer en domein, uitbreidings van die sertifikaat.
  • bediener feite -veranderlikes wat verband hou met inligting oor die bediener-weergawe, naam, bediener IP-adres, omgewing.
  • agent feite - veranderlikes direk bygevoeg deur marionet-agent, en nie deur feit nie - sertifikaatnaam, agentweergawe, marionetweergawe.
  • meester veranderlikes - Pappetmaster veranderlikes (sic!). Dit is omtrent dieselfde as in bediener feite, plus konfigurasieparameterwaardes is beskikbaar.
  • samesteller veranderlikes — samestellerveranderlikes wat in elke omvang verskil: die naam van die huidige module en die naam van die module waarin toegang tot die huidige objek verkry is. Hulle kan byvoorbeeld gebruik word om te kontroleer dat jou privaatklasse nie direk vanaf ander modules gebruik word nie.

Byvoeging 1: hoe om dit alles uit te voer en te ontfout?

Die artikel het baie voorbeelde van marionetkode bevat, maar het glad nie vir ons gesê hoe om hierdie kode uit te voer nie. Wel, ek korrigeer myself.

'n Agent is genoeg om Puppet te laat loop, maar vir die meeste gevalle sal jy ook 'n bediener nodig hê.

agent

Ten minste sedert weergawe XNUMX, puppet-agent pakkette van amptelike Puppetlabs-bewaarplek bevat al die afhanklikhede (robyn en die ooreenstemmende juwele), so daar is geen installasieprobleme nie (ek praat van Debian-gebaseerde verspreidings - ons gebruik nie RPM-gebaseerde verspreidings nie).

In die eenvoudigste geval, om die marionetkonfigurasie te gebruik, is dit genoeg om die agent in bedienerlose modus te begin: mits die marionetkode na die nodus gekopieer word, begin 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

Dit is natuurlik beter om die bediener op te stel en agente op die nodusse in daemonmodus te laat loop - dan sal hulle een keer elke halfuur die konfigurasie toepas wat van die bediener afgelaai is.

Jy kan die stootmodel van werk naboots - gaan na die nodus waarin jy belangstel en begin sudo puppet agent -t. Sleutel -t (--test) bevat eintlik verskeie opsies wat individueel geaktiveer kan word. Hierdie opsies sluit die volgende in:

  • moenie in daemonmodus hardloop nie (by verstek begin die agent in daemonmodus);
  • sluit af nadat die katalogus toegepas is (by verstek sal die agent aanhou werk en die konfigurasie een keer elke halfuur toepas);
  • skryf 'n gedetailleerde werklogboek;
  • wys veranderinge in lêers.

Die agent het 'n bedryfsmodus sonder veranderinge - jy kan dit gebruik wanneer jy nie seker is dat jy die korrekte konfigurasie geskryf het nie en wil kyk wat presies die agent sal verander tydens werking. Hierdie modus word deur die parameter geaktiveer --noop op die opdragreël: sudo puppet agent -t --noop.

Daarbenewens kan u die ontfoutingslogboek van die werk aktiveer - daarin skryf marionet oor al die aksies wat dit uitvoer: oor die hulpbron wat dit tans verwerk, oor die parameters van hierdie hulpbron, oor watter programme dit begin. Natuurlik is dit 'n parameter --debug.

Bediener

Ek sal nie die volledige opstelling van die pappetbediener en die implementering van kode daarvoor in hierdie artikel oorweeg nie; Ek sal net sê dat daar uit die boks 'n ten volle funksionele weergawe van die bediener is wat nie bykomende konfigurasie benodig om met 'n klein aantal nodusse (sê, tot honderd). 'n Groter aantal nodusse sal tuning vereis - by verstek, puppetserver begin nie meer as vier werkers nie, vir groter werkverrigting moet jy hul aantal vermeerder en moenie vergeet om die geheuelimiete te verhoog nie, anders sal die bediener die meeste van die tyd vullis versamel.

Kode-ontplooiing - as jy dit vinnig en maklik nodig het, kyk dan (teen R10k)[https://github.com/puppetlabs/r10k], vir klein installasies behoort dit heeltemal genoeg te wees.

Bylaag 2: Koderriglyne

  1. Plaas alle logika in klasse en definisies.
  2. Hou klasse en definisies in modules, nie in manifeste wat nodusse beskryf nie.
  3. Gebruik die feite.
  4. Moenie if's maak op grond van gasheername nie.
  5. Voeg gerus parameters vir klasse en definisies by - dit is beter as implisiete logika wat in die liggaam van die klas/definisie versteek is.

Ek sal in die volgende artikel verduidelik hoekom ek aanbeveel om dit te doen.

Gevolgtrekking

Kom ons eindig met die inleiding. In die volgende artikel sal ek jou vertel van Hiera, ENC en PuppetDB.

Slegs geregistreerde gebruikers kan aan die opname deelneem. Meld aan, asseblief.

Trouens, daar is baie meer materiaal - ek kan artikels skryf oor die volgende onderwerpe, stem oor waaroor jy sou belangstel om te lees:

  • 59,1%Gevorderde marionetkonstruksies - 'n paar volgende vlak kak: lusse, kartering en ander lambda-uitdrukkings, hulpbronversamelaars, uitgevoerde bronne en inter-gasheer kommunikasie via Puppet, etikette, verskaffers, abstrakte datatipes.13
  • 31,8%"I'm my mother's admin" of hoe ons in Avito vriende gemaak het met verskeie poppet-bedieners van verskillende weergawes, en in beginsel die deel oor die administrasie van die poppet-bediener.7
  • 81,8%Hoe ons marionetkode skryf: instrumentasie, dokumentasie, toetsing, CI/CD.18

22 gebruikers het gestem. 9 gebruikers het buite stemming gebly.

Bron: will.com