Enkonduko al Pupeto

Puppet estas agorda administradsistemo. Ĝi estas uzata por alporti gastigantojn al la dezirata stato kaj konservi ĉi tiun staton.

Mi laboras kun Puppet jam pli ol kvin jarojn. Ĉi tiu teksto estas esence tradukita kaj reordigita kompilo de ĉefpunktoj el la oficiala dokumentaro, kiu permesos al komencantoj rapide kompreni la esencon de Puppet.

Enkonduko al Pupeto

Bazaj informoj

La operaciumo de Puppet estas kliento-servilo, kvankam ĝi ankaŭ subtenas senservila operacio kun limigita funkcieco.

Tira modelo de operacio estas uzata: defaŭlte, unufoje ĉiun duonhoron, klientoj kontaktas la servilon por agordo kaj aplikas ĝin. Se vi laboris kun Ansible, tiam ili uzas malsaman puŝan modelon: la administranto komencas la procezon de aplikado de la agordo, la klientoj mem ne aplikos ion ajn.

Dum retkomunikado, dudirekta TLS-ĉifrado estas uzata: la servilo kaj kliento havas siajn proprajn privatajn ŝlosilojn kaj respondajn atestojn. Tipe la servilo eldonas atestojn por klientoj, sed principe eblas uzi eksteran CA.

Enkonduko al manifestoj

En Pupa terminologio al la pupservilo konekti nodoj (nodoj). La agordo por la nodoj estas skribita en manifestoj en speciala programlingvo - Puppet DSL.

Puppet DSL estas deklara lingvo. Ĝi priskribas la deziratan staton de la nodo en la formo de deklaroj de individuaj rimedoj, ekzemple:

  • La dosiero ekzistas kaj ĝi havas specifan enhavon.
  • La pako estas instalita.
  • La servo komenciĝis.

Rimedoj povas esti interligitaj:

  • Estas dependecoj, ili influas la ordon en kiu rimedoj estas uzataj.
    Ekzemple, "unue instalu la pakaĵon, poste redaktu la agordan dosieron, poste lanĉu la servon."
  • Estas sciigoj - se rimedo ŝanĝiĝis, ĝi sendas sciigojn al la rimedoj abonitaj al ĝi.
    Ekzemple, se la agorda dosiero ŝanĝiĝas, vi povas aŭtomate rekomenci la servon.

Aldone, la Puppet DSL havas funkciojn kaj variablojn, same kiel kondiĉajn deklarojn kaj elektilojn. Diversaj ŝablonaj mekanismoj ankaŭ estas subtenataj - EPP kaj ERB.

Marioneto estas skribita en Ruby, tiel ke multaj el la konstrukcioj kaj esprimoj estas prenitaj de tie. Ruby permesas pligrandigi Puppet - aldonu kompleksan logikon, novajn specojn de rimedoj, funkciojn.

Dum Puppet funkcias, manifestoj por ĉiu specifa nodo sur la servilo estas kompilitaj en dosierujon. Gvidlibro estas listo de rimedoj kaj iliaj rilatoj post kalkulado de la valoro de funkcioj, variabloj kaj ekspansio de kondiĉaj deklaroj.

Sintakso kaj kodstilo

Jen sekcioj de la oficiala dokumentaro, kiuj helpos vin kompreni la sintakson se la ekzemploj provizitaj ne sufiĉas:

Jen ekzemplo de kiel aspektas la manifesto:

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

Indentaĵo kaj liniorompoj ne estas postulata parto de la manifesto, sed estas rekomendinda stila gvidilo. Resumo:

  • Du-spacaj strekoj, langetoj ne estas uzataj.
  • Buklaj krampoj estas apartigitaj per spaco; dupunktoj ne estas apartigitaj per spaco.
  • Komoj post ĉiu parametro, inkluzive de la lasta. Ĉiu parametro estas sur aparta linio. Escepto estas farita por la kazo sen parametroj kaj unu parametro: oni povas skribi sur unu linio kaj sen komo (t.e. resource { 'title': } и resource { 'title': param => value }).
  • La sagoj sur la parametroj devus esti je la sama nivelo.
  • Rimedorilataj sagoj estas skribitaj antaŭ ili.

Loko de dosieroj sur pappetserver

Por plia klarigo, mi enkondukos la koncepton de "radika dosierujo". La radika dosierujo estas la dosierujo kiu enhavas la Puppet-agordon por specifa nodo.

La radika dosierujo varias laŭ la versio de Puppet kaj la uzataj medioj. Medioj estas sendependaj aroj de agordo, kiuj estas stokitaj en apartaj dosierujoj. Kutime uzata en kombinaĵo kun git, en kiu kazo medioj estas kreitaj el git-branĉoj. Sekve, ĉiu nodo situas en unu medio aŭ alia. Ĉi tio povas esti agordita sur la nodo mem, aŭ en ENC, pri kiu mi parolos en la sekva artikolo.

  • En la tria versio ("malnova Marioneto") la baza dosierujo estis /etc/puppet. La uzo de medioj estas nedeviga - ekzemple, ni ne uzas ilin kun la malnova Puppeto. Se medioj estas uzataj, ili estas kutime konservitaj /etc/puppet/environments, la radika dosierujo estos la medio-dosierujo. Se medioj ne estas uzataj, la radika dosierujo estos la baza dosierujo.
  • Komencante de la kvara versio ("nova Marioneto"), la uzo de medioj iĝis deviga, kaj la baza dosierujo estis movita al /etc/puppetlabs/code. Sekve, medioj estas stokitaj en /etc/puppetlabs/code/environments, radika dosierujo estas la medio-dosierujo.

Devas esti subdosierujo en la radika dosierujo manifests, kiu enhavas unu aŭ plurajn manifestojn priskribantajn la nodojn. Krome, devus esti subdosierujo modules, kiu enhavas la modulojn. Mi diros al vi, kiaj moduloj estas iom poste. Krome, la malnova Pupo ankaŭ povas havi subdosierujon files, kiu enhavas diversajn dosierojn, kiujn ni kopias al la nodoj. En la nova Puppeto, ĉiuj dosieroj estas metitaj en modulojn.

Manifest-dosieroj havas la etendon .pp.

Paro da batalekzemploj

Priskribo de la nodo kaj rimedo sur ĝi

Sur la nodo server1.testdomain dosiero devas esti kreita /etc/issue kun enhavo Debian GNU/Linux n l. La dosiero devas esti posedata de uzanto kaj grupo root, alirrajtoj devas esti 644.

Ni skribas manifeston:

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

Rilatoj inter rimedoj sur nodo

Sur la nodo server2.testdomain nginx devas funkcii, laborante kun antaŭe preta agordo.

Ni malkomponu la problemon:

  • La pako devas esti instalita nginx.
  • Necesas, ke la agordaj dosieroj estu kopiitaj de la servilo.
  • La servo devas funkcii nginx.
  • Se la agordo estas ĝisdatigita, la servo devas esti rekomencita.

Ni skribas manifeston:

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

Por ke ĉi tio funkciu, vi bezonas proksimume la sekvan dosierlokon sur la marioneta servilo:

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

Tipoj de Rimedoj

Kompleta listo de subtenataj rimedtipoj troveblas ĉi tie en la dokumentado, ĉi tie mi priskribos kvin bazajn tipojn, kiuj en mia praktiko sufiĉas por solvi plej multajn problemojn.

dosiero

Administras dosierojn, dosierujojn, simbolligojn, ilian enhavon kaj alirrajtojn.

Parametroj:

  • nomo de rimedo - vojo al la dosiero (laŭvola)
  • Vojo — vojo al la dosiero (se ĝi ne estas specifita en la nomo)
  • certigi - dosiertipo:
    • absent - forigi dosieron
    • present — devas esti iu ajn dosiero (se ne estas dosiero, oni kreos regulan dosieron)
    • file - regula dosiero
    • directory - dosierujo
    • link - simbolligo
  • enhavo — dosierenhavo (taŭga nur por regulaj dosieroj, ne povas esti uzata kune kun fontocelo)
  • fonto — ligilo al la vojo de kiu vi volas kopii la enhavon de la dosiero (ne povas esti uzata kune kun enhavocelo). Povas esti specifita kiel aŭ URI kun skemo puppet: (tiam oni uzos dosierojn el la pupservilo), kaj kun la skemo http: (Mi esperas, ke estas klare, kio okazos en ĉi tiu kazo), kaj eĉ kun la diagramo file: aŭ kiel absoluta vojo sen skemo (tiam la dosiero de la loka FS sur la nodo estos uzata)
  • celo — kien la simbolligo devus montri (ne povas esti uzata kune kun enhavofonto)
  • posedanto — la uzanto kiu devus posedi la dosieron
  • grupo — la grupo al kiu la dosiero devus aparteni
  • reĝimo — dosierpermesoj (kiel ĉeno)
  • malbeni - ebligas rekursivan dosierujan prilaboradon
  • purgado - ebligas forigi dosierojn kiuj ne estas priskribitaj en Puppet
  • forto - ebligas forigi dosierujojn kiuj ne estas priskribitaj en Puppet

pakaĵo

Instalas kaj forigas pakaĵojn. Kapabla pritrakti sciigojn - reinstalas la pakaĵon se la parametro estas specifita reinstall_on_refresh.

Parametroj:

  • nomo de rimedo - pakaĵnomo (laŭvola)
  • nomo - pakaĵnomo (se ne specifita en la nomo)
  • Provizanto — pakaĵmanaĝero por uzi
  • certigi — dezirata stato de la pako:
    • present, installed - ajna versio instalita
    • latest - lasta versio instalita
    • absent - forigita (apt-get remove)
    • purged - forigita kune kun agordaj dosieroj (apt-get purge)
    • held - paka versio estas ŝlosita (apt-mark hold)
    • любая другая строка — la specifita versio estas instalita
  • reinstall_on_refresh - se a true, tiam post ricevo de la sciigo la pakaĵo estos reinstalita. Utila por font-bazitaj distribuoj, kie rekonstruado de pakaĵoj povas esti necesa dum ŝanĝado de konstruaj parametroj. Defaŭlte false.

servo

Administras servojn. Kapabla prilabori sciigojn - rekomencas la servon.

Parametroj:

  • nomo de rimedo — administrota servo (laŭvola)
  • nomo - la servo, kiu devas esti administrita (se ne specifita en la nomo)
  • certigi — dezirata stato de la servo:
    • running - lanĉita
    • stopped — haltis
  • ebligi — regas la kapablon komenci la servon:
    • true — Aŭtomata ruliĝo estas ebligita (systemctl enable)
    • mask - alivestita (systemctl mask)
    • false — aŭtomata ruliĝo estas malŝaltita (systemctl disable)
  • rekomenci - komando por rekomenci la servon
  • statuso — komando por kontroli servostaton
  • hasrestart — indiku ĉu la servo initscript subtenas rekomencon. Se false kaj la parametro estas specifita rekomenci — la valoro de ĉi tiu parametro estas uzata. Se false kaj parametro rekomenci ne specifita - la servo estas haltita kaj komencita rekomenci (sed systemd uzas la komandon systemctl restart).
  • hasstatus — indiku ĉu la servo initscript subtenas la komandon status. Se false, tiam la parametrovaloro estas uzata statuso. Defaŭlte true.

exec

Rulas eksterajn komandojn. Se vi ne specifas parametrojn kreas, nur se, krom serefreshonly, la komando estos rulita ĉiufoje kiam Puppet estas rulita. Kapabla prilabori sciigojn - rulas komandon.

Parametroj:

  • nomo de rimedo - komando plenumenda (laŭvola)
  • komando — la komando plenumenda (se ĝi ne estas specifita en la nomo)
  • Vojo — vojoj en kiuj serĉi la plenumeblan dosieron
  • nur se — se la komando specifita en ĉi tiu parametro kompletigita kun nula revenkodo, la ĉefa komando estos ekzekutita
  • krom se — se la komando specifita en ĉi tiu parametro kompletigita per ne-nula revenkodo, la ĉefa komando estos ekzekutita
  • kreas — se la dosiero specifita en ĉi tiu parametro ne ekzistas, la ĉefa komando estos ekzekutita
  • refreshonly - se a true, tiam la komando ruliĝos nur kiam ĉi tiu ekzekutisto ricevas sciigon de aliaj rimedoj
  • cwd — dosierujo el kiu ruli la komandon
  • surhavi — la uzanto de kiu ruli la komandon
  • Provizanto - kiel ruli la komandon:
    • posikso — infana procezo estas simple kreita, nepre specifu Vojo
    • ŝelo - la komando estas lanĉita en la ŝelo /bin/sh, eble ne estas specifita Vojo, vi povas uzi globadon, pipojn kaj aliajn ŝelajn funkciojn. Kutime detektita aŭtomate se estas iuj specialaj signoj (|, ;, &&, || ktp).

cron

Kontrolas kronlaborojn.

Parametroj:

  • nomo de rimedo - nur ia identigilo
  • certigi — stato de kronlaboro:
    • present - krei se ne ekzistas
    • absent - forigu se ekzistas
  • komando - kian ordonon ruli
  • medio — en kiu medio ruli la komandon (listo de mediaj variabloj kaj iliaj valoroj per =)
  • surhavi — de kiu uzanto ruli la komandon
  • minuto, horo, labortago, monato, monatotago — kiam kuri cron. Se iu el ĉi tiuj atributoj ne estas specifita, ĝia valoro en la crontab estos *.

En Marioneto 6.0 cron kvazaŭ forigita el la skatolo en pupserver, do ne estas dokumentado en la ĝenerala retejo. Sed li estas en la skatolo en marioneto-agento, do ne necesas instali ĝin aparte. Vi povas vidi la dokumentadon por ĝi en la dokumentado por la kvina versio de Puppetsur GitHub.

Pri rimedoj ĝenerale

Postuloj por rimeda unikeco

La plej ofta eraro, kiun ni renkontas, estas Duobla deklaro. Ĉi tiu eraro okazas kiam du aŭ pli da rimedoj de la sama tipo kun la sama nomo aperas en la dosierujo.

Tial mi skribos denove: manifestoj por la sama nodo ne entenu rimedojn de la sama tipo kun la sama titolo!

Kelkfoje necesas instali pakaĵojn kun la sama nomo, sed kun malsamaj pakaĵadministriloj. En ĉi tiu kazo, vi devas uzi la parametron namepor eviti la eraron:

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

Aliaj rimedspecoj havas similajn eblojn por helpi eviti duobligon − name у servo, command у exec, kaj tiel plu.

Metaparametroj

Ĉiu rimeda tipo havas kelkajn specialajn parametrojn, sendepende de sia naturo.

Plena listo de meta-parametroj en la dokumentado de Puppeto.

Mallonga listo:

  • postulas — ĉi tiu parametro indikas, de kiuj rimedoj ĉi tiu rimedo dependas.
  • antaŭ - Ĉi tiu parametro specifas, kiuj rimedoj dependas de ĉi tiu rimedo.
  • aboni — ĉi tiu parametro specifas de kiuj rimedoj ĉi tiu rimedo ricevas sciigojn.
  • sciigi — Ĉi tiu parametro specifas, kiuj rimedoj ricevas sciigojn de ĉi tiu rimedo.

Ĉiuj listigitaj metaparametroj akceptas aŭ ununuran rimedan ligon aŭ aron da ligiloj en kvadrataj krampoj.

Ligiloj al rimedoj

Rimeda ligilo estas simple mencio de la rimedo. Ili estas ĉefe uzataj por indiki dependecojn. Referencado de neekzistanta rimedo kaŭzos kompilan eraron.

La sintakso de la ligo estas jena: rimeda tipo kun majuskla litero (se la tipnomo enhavas duoblajn dupunktojn, tiam ĉiu parto de la nomo inter dupunktoj estas majuskleta), tiam la rimeda nomo inter kvadrataj krampoj (la kazo de la nomo ne ŝanĝiĝas!). Ne estu spacoj; kvadrataj krampoj estas skribitaj tuj post la tipnomo.

Ekzemplo:

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

Dependecoj kaj sciigoj

Dokumentado ĉi tie.

Kiel dirite pli frue, simplaj dependecoj inter rimedoj estas transitivaj. Cetere, atentu aldonante dependecojn - vi povas krei ciklajn dependecojn, kiuj kaŭzos kompilan eraron.

Male al dependecoj, sciigoj ne estas transitivaj. La sekvaj reguloj validas por sciigoj:

  • Se la rimedo ricevas sciigon, ĝi estas ĝisdatigita. La ĝisdatigaj agoj dependas de la rimeda tipo − exec rulas la komandon, servo rekomencas la servon, pakaĵo reinstalas la pakaĵon. Se la rimedo ne havas ĝisdatigon difinitan, tiam nenio okazas.
  • Dum unu kuro de Puppet, la rimedo estas ĝisdatigita ne pli ol unufoje. Ĉi tio eblas ĉar sciigoj inkluzivas dependecojn kaj la dependeca grafeo ne enhavas ciklojn.
  • Se Puppet ŝanĝas la staton de rimedo, la rimedo sendas sciigojn al ĉiuj rimedoj abonitaj al ĝi.
  • Se rimedo estas ĝisdatigita, ĝi sendas sciigojn al ĉiuj rimedoj abonitaj al ĝi.

Pritraktado de nespecifitaj parametroj

Kiel regulo, se iu rimeda parametro ne havas defaŭltan valoron kaj ĉi tiu parametro ne estas specifita en la manifesto, tiam Puppet ne ŝanĝos ĉi tiun posedaĵon por la responda rimedo sur la nodo. Ekzemple, se rimedo de tipo dosiero parametro ne specifita owner, tiam Puppet ne ŝanĝos la posedanton de la responda dosiero.

Enkonduko al klasoj, variabloj kaj difinoj

Supozu, ke ni havas plurajn nodojn, kiuj havas la saman parton de la agordo, sed ankaŭ estas diferencoj - alie ni povus priskribi ĉion en unu bloko. node {}. Kompreneble, vi povas simple kopii identajn partojn de la agordo, sed ĝenerale ĉi tio estas malbona solvo - la agordo kreskas, kaj se vi ŝanĝas la ĝeneralan parton de la agordo, vi devos redakti la samon en multaj lokoj. Samtempe, estas facile erari, kaj ĝenerale, la SEKA (ne ripetu vin) principo estis inventita ial.

Por solvi ĉi tiun problemon ekzistas tia dezajno kiel klaso.

Klasoj

Класс estas nomita bloko de poppet-kodo. Klasoj necesas por reuzi kodon.

Unue la klaso devas esti priskribita. La priskribo mem ne aldonas rimedojn ie ajn. La klaso estas priskribita en manifestoj:

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

Post tio la klaso povas esti uzata:

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

Ekzemplo de la antaŭa tasko - ni movu la instaladon kaj agordon de nginx en klason:

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
}

Variabloj

La klaso de la antaŭa ekzemplo tute ne estas fleksebla ĉar ĝi ĉiam alportas la saman nginx-agordon. Ni faru la vojon al la agorda variablo, tiam ĉi tiu klaso povas esti uzata por instali nginx kun iu ajn agordo.

Ĝi povas esti farita uzante variablojn.

Atentu: variabloj en Puppet estas neŝanĝeblaj!

Krome, variablo nur povas esti alirebla post kiam ĝi estis deklarita, alie la valoro de la variablo estos undef.

Ekzemplo de laboro kun variabloj:

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

Marioneto havas nomspacoj, kaj la variabloj, sekve, havas areo de videbleco: variablo kun la sama nomo povas esti difinita en malsamaj nomspacoj. Kiam oni solvas la valoron de variablo, la variablo estas serĉata en la nuna nomspaco, poste en la enfermanta nomspaco, ktp.

Ekzemploj de nomspaco:

  • tutmonda - variabloj ekster la klasa aŭ noda priskribo iras tien;
  • noda nomspaco en la noda priskribo;
  • klasnomspaco en la klaspriskribo.

Por eviti ambiguecon dum aliro al variablo, vi povas specifi la nomspacon en la variablo nomo:

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

Ni konsentu, ke la vojo al la agordo nginx kuŝas en la variablo $nginx_conf_source. Tiam la klaso aspektos jene:

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
}

Tamen, la donita ekzemplo estas malbona ĉar ekzistas iu "sekreta scio", ke ie ene de la klaso oni uzas variablon kun tia kaj tia nomo. Estas multe pli ĝuste igi ĉi tiun scion ĝenerala - klasoj povas havi parametrojn.

Klasaj parametroj estas variabloj en la klasnomspaco, ili estas specifitaj en la klaskapo kaj povas esti uzataj kiel regulaj variabloj en la klaskorpo. Parametrovaloroj estas specifitaj kiam oni uzas la klason en la manifesto.

La parametro povas esti agordita al defaŭlta valoro. Se parametro ne havas defaŭltan valoron kaj la valoro ne estas agordita kiam uzata, ĝi kaŭzos kompilan eraron.

Ni parametrigu la klason el la supra ekzemplo kaj aldonu du parametrojn: la unua, postulata, estas la vojo al la agordo, kaj la dua, laŭvola, estas la nomo de la pakaĵo kun nginx (en Debian, ekzemple, estas pakaĵoj. 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',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

En Puppet, variabloj estas tajpitaj. Manĝu multaj datumtipoj. Datumtipoj estas kutime uzataj por validigi parametrajn valorojn transdonitajn al klasoj kaj difinoj. Se la pasita parametro ne kongruas kun la specifita tipo, kompila eraro okazos.

La tipo estas skribita tuj antaŭ la parametronomo:

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

Klasoj: inkluzivu klasnomon kontraŭ klaso{'klasnomo':}

Ĉiu klaso estas rimedo de tipo klaso. Kiel kun iu ajn alia speco de rimedo, ne povas ekzisti du okazoj de la sama klaso sur la sama nodo.

Se vi provas aldoni klason al la sama nodo dufoje uzante class { 'classname':} (neniu diferenco, kun malsamaj aŭ identaj parametroj), estos kompila eraro. Sed se vi uzas klason en la rimeda stilo, vi povas tuj eksplicite agordi ĉiujn ĝiajn parametrojn en la manifesto.

Tamen, se vi uzas include, tiam la klaso povas esti aldonita tiom da fojoj kiel dezirate. La fakto estas tio include estas idempotenta funkcio kiu kontrolas ĉu klaso estis aldonita al la dosierujo. Se la klaso ne estas en la dosierujo, ĝi aldonas ĝin, kaj se ĝi jam ekzistas, ĝi faras nenion. Sed en kazo de uzado include Vi ne povas agordi klasajn parametrojn dum klasdeklaro - ĉiuj bezonataj parametroj devas esti agorditaj en ekstera datumfonto - Hiera aŭ ENC. Pri ili ni parolos en la sekva artikolo.

Difinas

Kiel estis dirite en la antaŭa bloko, la sama klaso ne povas ĉeesti sur nodo pli ol unufoje. Tamen, en iuj kazoj vi devas povi uzi la saman blokon de kodo kun malsamaj parametroj sur la sama nodo. Alivorte, estas bezono de rimeda speco propra.

Ekzemple, por instali la PHP-modulon, ni faras la jenon en Avito:

  1. Instalu la pakaĵon kun ĉi tiu modulo.
  2. Ni kreu agordan dosieron por ĉi tiu modulo.
  3. Ni kreas simbolligon al la agordo por php-fpm.
  4. Ni kreas simbolligon al la agordo por php cli.

En tiaj kazoj, dezajno kiel ekz difini (difini, difinita tipo, difinita rimeda tipo). Difini estas simila al klaso, sed estas diferencoj: unue, ĉiu Difino estas rimeda tipo, ne rimedo; due, ĉiu difino havas implican parametron $title, kie la rimeda nomo iras kiam ĝi estas deklarita. Same kiel ĉe klasoj, oni devas unue priskribi difinon, post kio ĝi povas esti uzata.

Simpligita ekzemplo kun modulo por 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' }
}

La plej facila maniero kapti la duplikatan deklaro-eraron estas en Difini. Ĉi tio okazas se difino havas rimedon kun konstanta nomo, kaj estas du aŭ pli da okazoj de ĉi tiu difino sur iu nodo.

Estas facile protekti vin kontraŭ ĉi tio: ĉiuj rimedoj ene de la difino devas havi nomon depende de $title. Alternativo estas idempotenta aldono de rimedoj; en la plej simpla kazo, sufiĉas movi la komunajn rimedojn al ĉiuj okazoj de la difino en apartan klason kaj inkluzivi ĉi tiun klason en la difino - funkcio. include idempotent.

Estas aliaj manieroj atingi idempotencon aldonante rimedojn, nome uzante funkciojn defined и ensure_resources, sed mi rakontos al vi pri tio en la venonta epizodo.

Dependecoj kaj sciigoj por klasoj kaj difinoj

Klasoj kaj difinoj aldonas la sekvajn regulojn por pritrakti dependecojn kaj sciigojn:

  • dependeco de klaso/difini aldonas dependecojn de ĉiuj rimedoj de la klaso/difini;
  • klaso/difina dependeco aldonas dependecojn al ĉiuj klasaj/difinaj rimedoj;
  • klaso/difina sciigo sciigas ĉiujn rimedojn pri la klaso/difini;
  • klaso/difini abono abonas ĉiujn rimedojn de la klaso/difini.

Kondiĉaj deklaroj kaj elektiloj

Dokumentado ĉi tie.

if

Ĉi tie estas simpla:

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

krom se

krom se estas se inverse: la bloko de kodo estos ekzekutita se la esprimo estas falsa.

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

kazo

Ankaŭ ĉi tie estas nenio komplika. Vi povas uzi regulajn valorojn (ŝnuroj, nombroj, ktp.), regulajn esprimojn kaj datumtipojn kiel valorojn.

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

Elektiloj

Elektilo estas lingvokonstruaĵo simila al case, sed anstataŭ ekzekuti blokon de kodo, ĝi resendas valoron.

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

Moduloj

Kiam la agordo estas malgranda, ĝi povas facile esti konservita en unu manifesto. Sed ju pli da agordoj ni priskribas, des pli da klasoj kaj nodoj estas en la manifesto, ĝi kreskas, kaj fariĝas maloportune labori kun.

Krome, ekzistas la problemo de reuzo de kodo - kiam la tuta kodo estas en unu manifesto, estas malfacile kunhavigi ĉi tiun kodon kun aliaj. Por solvi ĉi tiujn du problemojn, Puppet havas enton nomitan moduloj.

Moduloj - ĉi tiuj estas aroj de klasoj, difinoj kaj aliaj Puppetaj estaĵoj metitaj en apartan dosierujon. Alivorte, modulo estas sendependa peco de Pupa logiko. Ekzemple, povas ekzisti modulo por labori kun nginx, kaj ĝi enhavos kion kaj nur kio necesas por labori kun nginx, aŭ povas ekzisti modulo por labori kun PHP, ktp.

Moduloj estas versiigitaj, kaj dependecoj de moduloj unu sur la alia ankaŭ estas subtenataj. Estas malfermita deponejo de moduloj - Pupa Forĝejo.

Sur la marioneta servilo, moduloj troviĝas en la modula subdosierujo de la radika dosierujo. Ene de ĉiu modulo estas norma dosierujo - manifestoj, dosieroj, ŝablonoj, lib, ktp.

Dosiera strukturo en modulo

La radiko de la modulo povas enhavi la sekvajn dosierujojn kun priskribaj nomoj:

  • manifests - ĝi enhavas manifestojn
  • files - ĝi enhavas dosierojn
  • templates - ĝi enhavas ŝablonojn
  • lib — ĝi enhavas Ruby-kodon

Ĉi tio ne estas kompleta listo de dosierujoj kaj dosieroj, sed sufiĉas por ĉi tiu artikolo nuntempe.

Nomoj de rimedoj kaj nomoj de dosieroj en la modulo

Dokumentado ĉi tie.

Rimedoj (klasoj, difinoj) en modulo ne povas esti nomitaj kiel ajn vi volas. Krome, ekzistas rekta korespondado inter la nomo de rimedo kaj la nomo de la dosiero en kiu Puppet serĉos priskribon de tiu rimedo. Se vi malobservas la nomregulojn, tiam Puppet simple ne trovos la rimedpriskribon, kaj vi ricevos kompilan eraron.

La reguloj estas simplaj:

  • Ĉiuj rimedoj en modulo devas esti en la modula nomspaco. Se la modulo estas vokita foo, tiam ĉiuj rimedoj en ĝi estu nomitaj foo::<anything>, aŭ simple foo.
  • La rimedo kun la nomo de la modulo devas esti en la dosiero init.pp.
  • Por aliaj rimedoj, la dosiera nomskemo estas jena:
    • la prefikso kun la modulnomo estas forĵetita
    • ĉiuj duoblaj dupunktoj, se ekzistas, estas anstataŭigitaj per oblikvoj
    • etendo estas aldonita .pp

Mi pruvos per ekzemplo. Ni diru, ke mi skribas modulon nginx. Ĝi enhavas la jenajn rimedojn:

  • klaso nginx priskribita en la manifesto init.pp;
  • klaso nginx::service priskribita en la manifesto service.pp;
  • difini nginx::server priskribita en la manifesto server.pp;
  • difini nginx::server::location priskribita en la manifesto server/location.pp.

Skemoj

Certe vi mem scias, kio estas ŝablonoj; mi ne priskribos ilin detale ĉi tie. Sed mi lasos ĝin por la okazo ligo al Vikipedio.

Kiel uzi ŝablonojn: La signifo de ŝablono povas esti vastigita per funkcio template, kiu estas preterpasita la vojo al la ŝablono. Por rimedoj de tipo dosiero uzata kune kun la parametro content. Ekzemple, tiel:

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

Vidu vojon <modulename>/<filename> implicas dosieron <rootdir>/modules/<modulename>/templates/<filename>.

Krome ekzistas funkcio inline_template — ĝi ricevas la ŝablonan tekston kiel enigaĵon, ne la dosiernomon.

Ene de ŝablonoj, vi povas uzi ĉiujn Puppetajn variablojn en la nuna amplekso.

Marioneto subtenas ŝablonojn en formato ERB kaj EPP:

Mallonge pri ERB

Kontrolaj strukturoj:

  • <%= ВЫРАЖЕНИЕ %> — enigu la valoron de la esprimo
  • <% ВЫРАЖЕНИЕ %> — kalkulu la valoron de esprimo (sen enmeti ĝin). Kondiĉaj deklaroj (se) kaj bukloj (ĉiu) kutime iras ĉi tie.
  • <%# КОММЕНТАРИЙ %>

Esprimoj en ERB estas skribitaj en Ruby (ERB estas fakte Embedded Ruby).

Por aliri variablojn de la manifesto, vi devas aldoni @ al la variablo nomo. Por forigi linion, kiu aperas post kontrolkonstruaĵo, vi devas uzi fermetikedon -%>.

Ekzemplo de uzado de la ŝablono

Ni diru, ke mi skribas modulon por kontroli ZooKeeper. La klaso respondeca por krei la agordon aspektas kiel ĉi tio:

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

Kaj la responda ŝablono zoo.cfg.erb - Do:

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

Faktoj kaj Enkonstruitaj Variabloj

Ofte la specifa parto de la agordo dependas de kio nuntempe okazas sur la nodo. Ekzemple, depende de kio Debiana eldono estas, vi devas instali unu aŭ alian version de la pakaĵo. Vi povas monitori ĉion ĉi permane, reverkante manifestojn se nodoj ŝanĝiĝas. Sed ĉi tio ne estas serioza aliro; aŭtomatigo estas multe pli bona.

Por akiri informojn pri nodoj, Puppet havas mekanismon nomitan faktoj. Faktoj - ĉi tio estas informo pri la nodo, disponebla en manifestoj en formo de ordinaraj variabloj en la tutmonda nomspaco. Ekzemple, gastiga nomo, operaciumo versio, procesoro-arkitekturo, listo de uzantoj, listo de retaj interfacoj kaj iliaj adresoj, kaj multe, multe pli. Faktoj estas haveblaj en manifestoj kaj ŝablonoj kiel regulaj variabloj.

Ekzemplo pri laboro kun faktoj:

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

Formale parolante, fakto havas nomon (ĉeno) kaj valoron (diversaj tipoj estas disponeblaj: ĉenoj, tabeloj, vortaroj). Manĝu aro de enkonstruitaj faktoj. Vi ankaŭ povas skribi vian propran. Fakkolektantoj estas priskribitaj kiel funkcioj en Rubyaŭ kiel ruleblaj dosieroj. Faktoj ankaŭ povas esti prezentitaj en la formo tekstaj dosieroj kun datumoj sur la nodoj.

Dum operacio, la marioneta agento unue kopias ĉiujn disponeblajn faktokolektantojn de la papetservilo al la nodo, post kio ĝi lanĉas ilin kaj sendas la kolektitajn faktojn al la servilo; Post tio, la servilo komencas kompili la katalogon.

Faktoj en formo de ruleblaj dosieroj

Tiaj faktoj estas metitaj en modulojn en la dosierujo facts.d. Kompreneble, la dosieroj devas esti plenumeblaj. Kiam ili ruliĝas, ili devas eligi informojn al norma eligo en aŭ YAML aŭ ŝlosilo=valora formato.

Ne forgesu, ke la faktoj validas por ĉiuj nodoj, kiuj estas kontrolataj de la poppet-servilo, al kiu via modulo estas deplojita. Tial, en la skripto, zorgu kontroli, ke la sistemo havas ĉiujn programojn kaj dosierojn necesajn por ke via fakto funkciu.

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

Rubenaj faktoj

Tiaj faktoj estas metitaj en modulojn en la dosierujo 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

Tekstaj faktoj

Tiaj faktoj estas metitaj sur nodojn en la dosierujo /etc/facter/facts.d en malnova Marioneto aŭ /etc/puppetlabs/facts.d en la nova Marioneto.

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

Alvenante al la Faktoj

Estas du manieroj alproksimiĝi al la faktoj:

  • tra la vortaro $facts: $facts['fqdn'];
  • uzante la faktonomon kiel la variablonomon: $fqdn.

Plej bone estas uzi vortaron $facts, aŭ eĉ pli bone, indiku la tutmondan nomspacon ($::facts).

Jen la koncerna sekcio de la dokumentaro.

Enkonstruitaj Variabloj

Krom la faktoj, ekzistas ankaŭ iuj variabloj, havebla en la tutmonda nomspaco.

  • fidindaj faktoj — variabloj, kiuj estas prenitaj el la atestilo de la kliento (ĉar la atestilo estas kutime elsendita sur popeta servilo, la agento ne povas simple preni kaj ŝanĝi ĝian atestilon, do la variabloj estas "fidindaj"): la nomo de la atestilo, la nomo de la gastiganto kaj domajno, etendaĵoj de la atestilo.
  • servilaj faktoj —variabloj rilataj al informoj pri la servilo—versio, nomo, IP-adreso de servilo, medio.
  • agentaj faktoj — variabloj aldonitaj rekte per marioneto-agento, kaj ne per faktoro — atestilnomo, agenta versio, marioneta versio.
  • majstraj variabloj - Pappetmaster-variabloj (sic!). Estas proksimume la sama kiel en servilaj faktoj, plus agordaj parametrovaloroj disponeblas.
  • variabloj de kompililo — kompililvariabloj kiuj malsamas en ĉiu amplekso: la nomo de la nuna modulo kaj la nomo de la modulo en kiu la nuna objekto estis alirita. Ili povas esti uzataj, ekzemple, por kontroli, ke viaj privataj klasoj ne estas uzataj rekte de aliaj moduloj.

Aldono 1: kiel ruli kaj sencimigi ĉion ĉi?

La artikolo enhavis multajn ekzemplojn de marioneta kodo, sed tute ne diris al ni kiel ruli ĉi tiun kodon. Nu, mi korektas min.

Agento sufiĉas por ruli Puppet, sed por plej multaj kazoj vi ankaŭ bezonos servilon.

Agento

Almenaŭ ekde versio XNUMX, marioneto-agentaj pakoj de oficiala Puppetlabs-deponejo enhavas ĉiujn dependecojn (ruby kaj la respondaj gemoj), do ne estas instalmalfacilaĵoj (mi parolas pri distribuoj bazitaj en Debian - ni ne uzas distribuojn bazitajn en RPM).

En la plej simpla kazo, por uzi la pupa agordon, sufiĉas lanĉi la agenton en senservila reĝimo: kondiĉe ke la marioneta kodo estas kopiita al la nodo, lanĉu 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

Estas pli bone, kompreneble, agordi la servilon kaj ruli agentojn sur la nodoj en demona reĝimo - tiam unufoje ĉiun duonhoron ili aplikos la agordon elŝutitan de la servilo.

Vi povas imiti la puŝan modelon de laboro - iru al la nodo, pri kiu vi interesiĝas kaj komencu sudo puppet agent -t. Ŝlosilo -t (--test) efektive inkluzivas plurajn eblojn, kiuj povas esti ebligitaj individue. Ĉi tiuj opcioj inkluzivas la jenajn:

  • ne rulu en demona reĝimo (defaŭlte la agento komenciĝas en demona reĝimo);
  • malŝaltu post apliki la katalogon (defaŭlte, la agento daŭre laboros kaj aplikos la agordon unufoje ĉiun duonhoron);
  • verki detalan laborprotokolon;
  • montri ŝanĝojn en dosieroj.

La agento havas operacian reĝimon sen ŝanĝoj - vi povas uzi ĝin kiam vi ne certas, ke vi skribis la ĝustan agordon kaj volas kontroli, kion ĝuste la agento ŝanĝos dum operacio. Ĉi tiu reĝimo estas ebligita de la parametro --noop sur la komandlinio: sudo puppet agent -t --noop.

Krome, vi povas ebligi la sencimigan protokolon de la verko - en ĝi, marioneto skribas pri ĉiuj agoj kiujn ĝi faras: pri la rimedo, kiun ĝi nuntempe prilaboras, pri la parametroj de ĉi tiu rimedo, pri kiaj programoj ĝi lanĉas. Kompreneble ĉi tio estas parametro --debug.

Servilo

Mi ne konsideros la plenan agordon de la pappetservilo kaj deploji kodon al ĝi en ĉi tiu artikolo; mi nur diros, ke el la skatolo estas plene funkcia versio de la servilo, kiu ne postulas plian agordon por labori kun malgranda nombro da nodoj (diru, ĝis cent). Pli granda nombro da nodoj postulos agordon - defaŭlte, pupserver lanĉas ne pli ol kvar laboristojn, por pli granda rendimento vi devas pliigi ilian nombron kaj ne forgesu pliigi la memorlimojn, alie la servilo rubos kolektas plejofte.

Koddeplojo - se vi bezonas ĝin rapide kaj facile, tiam rigardu (ĉe r10k)[https://github.com/puppetlabs/r10k], por malgrandaj instalaĵoj ĝi devus esti sufiĉe sufiĉe.

Aldono 2: Kodigaj Gvidlinioj

  1. Metu ĉian logikon en klasojn kaj difinojn.
  2. Konservu klasojn kaj difinojn en moduloj, ne en manifestoj priskribantaj nodojn.
  3. Uzu la faktojn.
  4. Ne faru ifojn bazitajn sur gastigantnomoj.
  5. Bonvolu aldoni parametrojn por klasoj kaj difinoj - tio estas pli bona ol implicita logiko kaŝita en la korpo de la klaso/difino.

Mi klarigos kial mi rekomendas fari ĉi tion en la sekva artikolo.

konkludo

Ni finu kun la enkonduko. En la sekva artikolo mi rakontos al vi pri Hiera, ENC kaj PuppetDB.

Nur registritaj uzantoj povas partopreni la enketon. Ensaluti, bonvolu.

Fakte, estas multe pli da materialo - mi povas skribi artikolojn pri la jenaj temoj, voĉdoni pri tio, pri kio vi interesus legi:

  • 59,1%Altnivelaj marionetaj konstruoj - kelkaj sekvanivelaj merdo: bukloj, mapado kaj aliaj lambda-esprimoj, rimedkolektantoj, eksportitaj rimedoj kaj inter-gastiga komunikado per Puppet, etikedoj, provizantoj, abstraktaj datumtipoj.13
  • 31,8%"Mi estas la administranto de mia patrino" aŭ kiel ni en Avito amikiĝis kun pluraj poppet-serviloj de malsamaj versioj, kaj, principe, la parto pri administrado de la poposervilo.7
  • 81,8%Kiel ni skribas pupkodon: instrumentado, dokumentado, testado, CI/CD.18

22 uzantoj voĉdonis. 9 uzantoj sindetenis.

fonto: www.habr.com