Puppet és un sistema de gestió de configuració. S'utilitza per portar els hosts a l'estat desitjat i mantenir aquest estat.
Fa més de cinc anys que treballo amb Puppet. Aquest text és essencialment una recopilació traduïda i reordenada de punts clau de la documentació oficial, que permetrà als principiants entendre ràpidament l'essència de Puppet.
Informació bàsica
El sistema operatiu de Puppet és client-servidor, tot i que també admet el funcionament sense servidor amb una funcionalitat limitada.
S'utilitza un model d'operació pull: per defecte, un cop cada mitja hora, els clients es posen en contacte amb el servidor per obtenir una configuració i l'apliquen. Si heu treballat amb Ansible, utilitzen un model push diferent: l'administrador inicia el procés d'aplicació de la configuració, els mateixos clients no aplicaran res.
Durant la comunicació de xarxa, s'utilitza el xifratge TLS bidireccional: el servidor i el client tenen les seves pròpies claus privades i els certificats corresponents. Normalment el servidor emet certificats per als clients, però en principi és possible utilitzar una CA externa.
Introducció als manifestos
En terminologia de titelles al servidor de titelles connectar nodes (nodes). S'escriu la configuració dels nodes en manifestos en un llenguatge de programació especial: Puppet DSL.
Puppet DSL és un llenguatge declaratiu. Descriu l'estat desitjat del node en forma de declaracions de recursos individuals, per exemple:
El fitxer existeix i té contingut específic.
El paquet està instal·lat.
El servei ha començat.
Els recursos es poden interconnectar:
Hi ha dependències, afecten l'ordre en què s'utilitzen els recursos.
Per exemple, "primer instal·leu el paquet, després editeu el fitxer de configuració i inicieu el servei".
Hi ha notificacions: si un recurs ha canviat, envia notificacions als recursos subscrits.
Per exemple, si el fitxer de configuració canvia, podeu reiniciar automàticament el servei.
A més, el Puppet DSL té funcions i variables, així com declaracions i selectors condicionals. També s'admeten diversos mecanismes de plantilla: EPP i ERB.
Puppet està escrit en Ruby, de manera que molts dels constructes i termes s'han extret d'allà. Ruby us permet ampliar Puppet: afegiu lògica complexa, nous tipus de recursos i funcions.
Mentre Puppet s'executa, els manifests de cada node específic del servidor es compilen en un directori. Каталог és una llista de recursos i les seves relacions després de calcular el valor de les funcions, les variables i l'ampliació de les declaracions condicionals.
Sintaxi i estil de codi
Aquí teniu seccions de la documentació oficial que us ajudaran a entendre la sintaxi si els exemples proporcionats no són suficients:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}
El sagnat i els salts de línia no són una part obligatòria del manifest, però hi ha una recomanació guia d'estil. Resum:
Sagnats de dos espais, no s'utilitzen tabulacions.
Les claus estan separades per un espai; els dos punts no estan separats per un espai.
Comes després de cada paràmetre, inclòs l'últim. Cada paràmetre es troba en una línia separada. Es fa una excepció per al cas sense paràmetres i un paràmetre: podeu escriure en una línia i sense coma (és a dir, resource { 'title': } и resource { 'title': param => value }).
Les fletxes dels paràmetres han d'estar al mateix nivell.
Al davant s'escriuen fletxes de relació de recursos.
Ubicació dels fitxers al servidor pappet
Per a més explicació, introduiré el concepte de "directori arrel". El directori arrel és el directori que conté la configuració de Puppet per a un node específic.
El directori arrel varia segons la versió de Puppet i els entorns utilitzats. Els entorns són conjunts independents de configuració que s'emmagatzemen en directoris separats. S'utilitza habitualment en combinació amb git, en aquest cas els entorns es creen a partir de branques de git. En conseqüència, cada node es troba en un entorn o un altre. Això es pot configurar al mateix node, o a ENC, del qual parlaré en el proper article.
A la tercera versió ("titella antiga") el directori base era /etc/puppet. L'ús d'entorns és opcional; per exemple, no els fem servir amb l'antic Puppet. Si s'utilitzen entorns, normalment s'emmagatzemen /etc/puppet/environments, el directori arrel serà el directori de l'entorn. Si no s'utilitzen entorns, el directori arrel serà el directori base.
A partir de la quarta versió ("nou Puppet"), l'ús d'entorns es va fer obligatori i el directori base es va traslladar a /etc/puppetlabs/code. En conseqüència, s'emmagatzemen els entorns /etc/puppetlabs/code/environments, el directori arrel és el directori de l'entorn.
Hi ha d'haver un subdirectori al directori arrel manifests, que conté un o més manifests que descriuen els nodes. A més, hi hauria d'haver un subdirectori modules, que conté els mòduls. Us explicaré quins mòduls són una mica més endavant. A més, l'antic Puppet també pot tenir un subdirectori files, que conté diversos fitxers que copiem als nodes. Al nou Puppet, tots els fitxers es col·loquen en mòduls.
Els fitxers de manifest tenen l'extensió .pp.
Un parell d'exemples de combat
Descripció del node i recurs que hi ha
Al node server1.testdomain s'ha de crear un fitxer /etc/issue amb contingut Debian GNU/Linux n l. El fitxer ha de ser propietat d'un usuari i grup root, els drets d'accés han de ser 644.
Escrivim un 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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
}
}
Relacions entre recursos d'un node
Al node server2.testdomain nginx s'ha d'executar, treballant amb una configuració preparada prèviament.
Anem a descompondre el problema:
Cal instal·lar el paquet nginx.
És necessari que els fitxers de configuració es copien del servidor.
El servei ha d'estar en funcionament nginx.
Si s'actualitza la configuració, s'ha de reiniciar el servei.
Escrivim un 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 получает уведомление,
# соответствующий сервис перезапускается.
}
Perquè això funcioni, necessiteu aproximadament la següent ubicació del fitxer al servidor de titelles:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.conf
Tipus de recursos
Podeu trobar una llista completa dels tipus de recursos compatibles aquí a la documentació, aquí descriuré cinc tipus bàsics, que en la meva pràctica són suficients per resoldre la majoria de problemes.
file
Gestiona fitxers, directoris, enllaços simbòlics, el seu contingut i drets d'accés.
Paràmetres:
nom del recurs — camí al fitxer (opcional)
camí — camí al fitxer (si no s'especifica al nom)
assegurar - tipus d'arxiu:
absent - esborrar un fitxer
present — hi ha d'haver un fitxer de qualsevol tipus (si no n'hi ha, es crearà un fitxer normal)
file - arxiu normal
directory - directori
link - enllaç simbòlic
contingut — contingut del fitxer (només apte per a fitxers normals, no es pot utilitzar juntament amb font o objectiu)
font — un enllaç a la ruta des del qual voleu copiar el contingut del fitxer (no es pot utilitzar juntament amb contingut o objectiu). Es pot especificar com a URI amb un esquema puppet: (aleshores s'utilitzaran fitxers del servidor de titelles), i amb l'esquema http: (Espero que quedi clar què passarà en aquest cas), i fins i tot amb el diagrama file: o com a camí absolut sense esquema (aleshores s'utilitzarà el fitxer de l'FS local del node)
objectiu — on hauria d'apuntar l'enllaç simbòlic (no es pot utilitzar juntament amb contingut o font)
propietari — l'usuari que hauria de ser propietari del fitxer
grup — el grup al qual ha de pertànyer el fitxer
manera - permisos de fitxer (com a cadena)
recurs - permet el processament recursiu del directori
purga - permet suprimir fitxers que no es descriuen a Puppet
força - permet suprimir directoris que no es descriuen a Puppet
paquet
Instal·la i elimina paquets. Capaç de gestionar les notificacions: torna a instal·lar el paquet si s'especifica el paràmetre reinstall_on_refresh.
Paràmetres:
nom del recurs - nom del paquet (opcional)
nom — nom del paquet (si no s'especifica al nom)
proveïdor — Gestor de paquets per utilitzar
assegurar — estat desitjat del paquet:
present, installed - qualsevol versió instal·lada
latest - darrera versió instal·lada
absent - suprimit (apt-get remove)
purged — esborrat juntament amb els fitxers de configuració (apt-get purge)
held - La versió del paquet està bloquejada (apt-mark hold)
любая другая строка — La versió especificada està instal·lada
reinstall_on_refresh - si true, després de rebre la notificació, el paquet es reinstal·larà. Útil per a distribucions basades en font, on pot ser necessari reconstruir paquets quan es canvien els paràmetres de compilació. Per defecte false.
servei
Gestiona els serveis. Capaç de processar notificacions: reinicia el servei.
Paràmetres:
nom del recurs — servei a gestionar (opcional)
nom — el servei que cal gestionar (si no s'especifica al nom)
assegurar — estat desitjat del servei:
running - llançat
stopped - aturat
permetre — controla la capacitat d'iniciar el servei:
true — L'execució automàtica està habilitat (systemctl enable)
mask - disfressat (systemctl mask)
false — L'execució automàtica està desactivada (systemctl disable)
reprendre - ordre per reiniciar el servei
estat — ordre per comprovar l'estat del servei
ha reiniciat — indiqueu si el servei initscript admet el reinici. Si false i s'especifica el paràmetre reprendre — S'utilitza el valor d'aquest paràmetre. Si false i paràmetre reprendre no especificat: el servei s'atura i comença a reiniciar-se (però systemd utilitza l'ordre systemctl restart).
hasstatus — indiqueu si el servei initscript admet l'ordre status. Si false, llavors s'utilitza el valor del paràmetre estat. Per defecte true.
exec
Executa ordres externes. Si no especifiqueu paràmetres crea, només si, llevat que o refrescant, l'ordre s'executarà cada cop que s'executi Puppet. Capaç de processar notificacions: executa una ordre.
Paràmetres:
nom del recurs — ordre a executar (opcional)
comanda — l'ordre que s'ha d'executar (si no s'especifica al nom)
camí — camins on buscar el fitxer executable
només si — si l'ordre especificada en aquest paràmetre s'ha completat amb un codi de retorn zero, s'executarà l'ordre principal
llevat que — si l'ordre especificada en aquest paràmetre s'ha completat amb un codi de retorn diferent de zero, s'executarà l'ordre principal
crea — si el fitxer especificat en aquest paràmetre no existeix, s'executarà l'ordre principal
refrescant - si true, l'ordre només s'executarà quan aquest executiu rebi una notificació d'altres recursos
cwd — directori des del qual executar l'ordre
user — l'usuari des del qual executar l'ordre
proveïdor - com executar l'ordre:
posix — Simplement es crea un procés fill, assegureu-vos d'especificar-lo camí
petxina - l'ordre es llança a l'intèrpret d'ordres /bin/sh, pot no estar especificat camí, podeu utilitzar globbing, canonades i altres funcions de shell. Normalment es detecta automàticament si hi ha caràcters especials (|, ;, &&, || etc).
cron
Controla els cronjobs.
Paràmetres:
nom del recurs - només una mena d'identificador
assegurar - Estat de la corona:
present - crear si no existeix
absent - esborrar si existeix
comanda - quina comanda executar
medi ambient — en quin entorn executar l'ordre (llista de variables d'entorn i els seus valors mitjançant =)
user — des de quin usuari executar l'ordre
minut, hora, entre setmana, mes, dia del mes - quan executar cron. Si no s'especifica cap d'aquests atributs, el seu valor a la crontab serà *.
L'error més comú que ens trobem és Declaració duplicada. Aquest error es produeix quan dos o més recursos del mateix tipus amb el mateix nom apareixen al directori.
Per tant, tornaré a escriure: els manifests per al mateix node no han de contenir recursos del mateix tipus amb el mateix títol!
De vegades és necessari instal·lar paquets amb el mateix nom, però amb gestors de paquets diferents. En aquest cas, cal utilitzar el paràmetre nameper evitar l'error:
exigir — aquest paràmetre indica de quins recursos depèn aquest recurs.
abans - Aquest paràmetre especifica quins recursos depenen d'aquest recurs.
subscriure — aquest paràmetre especifica de quins recursos aquest recurs rep notificacions.
notificar — Aquest paràmetre especifica quins recursos reben notificacions d'aquest recurs.
Tots els metaparàmetres enumerats accepten un enllaç de recurs únic o una matriu d'enllaços entre claudàtors.
Enllaços a recursos
Un enllaç de recurs és simplement una menció del recurs. S'utilitzen principalment per indicar dependències. Fer referència a un recurs inexistent provocarà un error de compilació.
La sintaxi de l'enllaç és la següent: tipus de recurs amb una lletra majúscula (si el nom del tipus conté dos punts dobles, llavors cada part del nom entre els dos punts s'escriu en majúscula), després el nom del recurs entre claudàtors (el cas del nom no canvia!). No hi hauria d'haver espais; els claudàtors s'escriuen immediatament després del nom del tipus.
Com s'ha dit anteriorment, les dependències simples entre recursos són transitives. Per cert, aneu amb compte a l'hora d'afegir dependències: podeu crear dependències cícliques, que provocaran un error de compilació.
A diferència de les dependències, les notificacions no són transitives. Per a les notificacions s'apliquen les regles següents:
Si el recurs rep una notificació, s'actualitza. Les accions d'actualització depenen del tipus de recurs − exec executa l'ordre, servei reinicia el servei, paquet reinstal·la el paquet. Si el recurs no té una acció d'actualització definida, no passa res.
Durant una execució de Puppet, el recurs no s'actualitza més d'una vegada. Això és possible perquè les notificacions inclouen dependències i el gràfic de dependències no conté cicles.
Si Puppet canvia l'estat d'un recurs, el recurs envia notificacions a tots els recursos subscrits a ell.
Si s'actualitza un recurs, envia notificacions a tots els recursos subscrits a ell.
Maneig de paràmetres no especificats
Per regla general, si algun paràmetre de recurs no té un valor per defecte i aquest paràmetre no s'especifica al manifest, Puppet no canviarà aquesta propietat per al recurs corresponent al node. Per exemple, si un recurs de tipus file paràmetre no especificat owner, aleshores Puppet no canviarà el propietari del fitxer corresponent.
Introducció a les classes, variables i definicions
Suposem que tenim diversos nodes que tenen la mateixa part de la configuració, però també hi ha diferències; en cas contrari, podríem descriure-ho tot en un bloc. node {}. Per descomptat, només podeu copiar parts idèntiques de la configuració, però en general aquesta és una mala solució: la configuració creix i, si canvieu la part general de la configuració, haureu d'editar el mateix en molts llocs. Al mateix temps, és fàcil equivocar-se i, en general, el principi DRY (no et repeteixis) es va inventar per una raó.
Per resoldre aquest problema hi ha un disseny com classe.
Classes
Classe és un bloc anomenat de codi poppet. Es necessiten classes per reutilitzar el codi.
Primer cal descriure la classe. La descripció en si no afegeix cap recurs enlloc. La classe es descriu als manifests:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}
Després d'això, la classe es pot utilitzar:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше
Un exemple de la tasca anterior: movem la instal·lació i configuració de nginx a una classe:
La classe de l'exemple anterior no és gens flexible perquè sempre aporta la mateixa configuració nginx. Fem el camí a la variable de configuració, llavors aquesta classe es pot utilitzar per instal·lar nginx amb qualsevol configuració.
A més, només es pot accedir a una variable després d'haver estat declarada, en cas contrari el valor de la variable serà undef.
Exemple de treball amb variables:
# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"
El titella té espais de noms, i les variables, en conseqüència, tenen zona de visibilitat: una variable amb el mateix nom es pot definir en diferents espais de noms. Quan es resol el valor d'una variable, la variable es cerca a l'espai de noms actual, després a l'espai de noms adjunt, i així successivament.
Exemples d'espais de noms:
global: les variables fora de la descripció de classe o node hi van;
espai de noms del node a la descripció del node;
espai de noms de classe a la descripció de la classe.
Per evitar ambigüitats en accedir a una variable, podeu especificar l'espai de noms al nom de la variable:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var
Estem d'acord que el camí a la configuració nginx es troba a la variable $nginx_conf_source. Aleshores la classe quedarà així:
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
}
Tanmateix, l'exemple donat és dolent perquè hi ha un "coneixement secret" que en algun lloc de la classe s'utilitza una variable amb tal o tal nom. És molt més correcte generalitzar aquest coneixement: les classes poden tenir paràmetres.
Paràmetres de classe són variables a l'espai de noms de classe, s'especifiquen a la capçalera de la classe i es poden utilitzar com a variables normals al cos de la classe. Els valors dels paràmetres s'especifiquen quan s'utilitza la classe al manifest.
El paràmetre es pot configurar amb un valor predeterminat. Si un paràmetre no té un valor per defecte i el valor no s'estableix quan s'utilitza, provocarà un error de compilació.
Parametritzem la classe de l'exemple anterior i afegim dos paràmetres: el primer, obligatori, és el camí a la configuració, i el segon, opcional, és el nom del paquet amb nginx (a Debian, per exemple, hi ha paquets). 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', # задаём параметры класса точно так же, как параметры для других ресурсов
}
}
A Puppet, s'escriuen variables. Menja molts tipus de dades. Els tipus de dades s'utilitzen normalment per validar els valors dels paràmetres passats a les classes i definicions. Si el paràmetre passat no coincideix amb el tipus especificat, es produirà un error de compilació.
El tipus s'escriu immediatament abans del nom del paràmetre:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}
Classes: inclou classname vs class{'classname':}
Cada classe és un recurs de tipus class. Com amb qualsevol altre tipus de recurs, no hi pot haver dues instàncies de la mateixa classe al mateix node.
Si intenteu afegir una classe al mateix node dues vegades utilitzant class { 'classname':} (sense diferència, amb paràmetres diferents o idèntics), hi haurà un error de compilació. Però si utilitzeu una classe a l'estil de recurs, podeu establir immediatament tots els seus paràmetres de manera explícita al manifest.
Tanmateix, si feu servir include, llavors la classe es pot afegir tantes vegades com es vulgui. El fet és que include és una funció idempotent que verifica si s'ha afegit una classe al directori. Si la classe no és al directori, l'afegeix, i si ja existeix, no fa res. Però en cas d'utilitzar include No podeu establir paràmetres de classe durant la declaració de classe: tots els paràmetres necessaris s'han d'establir en una font de dades externa: Hiera o ENC. En parlarem en el proper article.
Defineix
Com es va dir al bloc anterior, la mateixa classe no pot estar present en un node més d'una vegada. Tanmateix, en alguns casos cal poder utilitzar el mateix bloc de codi amb diferents paràmetres al mateix node. En altres paraules, hi ha una necessitat d'un tipus de recurs propi.
Per exemple, per instal·lar el mòdul PHP, fem el següent a Avito:
Instal·leu el paquet amb aquest mòdul.
Creem un fitxer de configuració per a aquest mòdul.
Creem un enllaç simbòlic a la configuració de php-fpm.
Creem un enllaç simbòlic a la configuració de php cli.
En aquests casos, un disseny com ara definir (definir, tipus definit, tipus de recurs definit). Un Define és semblant a una classe, però hi ha diferències: primer, cada Define és un tipus de recurs, no un recurs; en segon lloc, cada definició té un paràmetre implícit $title, on va el nom del recurs quan es declara. Igual que en el cas de les classes, primer s'ha de descriure una definició, després de la qual es pot utilitzar.
Un exemple simplificat amb un mòdul per a 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 manera més senzilla de detectar l'error de declaració duplicat és a Definir. Això passa si una definició té un recurs amb un nom constant i hi ha dues o més instàncies d'aquesta definició en algun node.
És fàcil protegir-se d'això: tots els recursos dins de la definició han de tenir un nom en funció de $title. Una alternativa és l'addició idempotent de recursos; en el cas més simple, n'hi ha prou amb moure els recursos comuns a totes les instàncies de la definició a una classe separada i incloure aquesta classe a la funció definició. include idempotent.
Hi ha altres maneres d'aconseguir la idempotència en afegir recursos, és a dir, utilitzant funcions defined и ensure_resources, però us ho explicaré en el proper episodi.
Dependències i notificacions per a classes i definicions
Les classes i les definicions afegeixen les regles següents per gestionar les dependències i les notificacions:
dependència d'una classe/definició afegeix dependències a tots els recursos de la classe/definició;
una dependència de classe/definició afegeix dependències a tots els recursos de classe/definició;
la notificació class/define notifica tots els recursos de la classe/define;
La subscripció class/define se subscriu a tots els recursos de la classe/define.
Quan la configuració és petita, es pot mantenir fàcilment en un manifest. Però com més configuracions descrivim, més classes i nodes hi ha al manifest, creix i es fa incòmode treballar-hi.
A més, hi ha el problema de la reutilització del codi: quan tot el codi està en un manifest, és difícil compartir aquest codi amb altres persones. Per resoldre aquests dos problemes, Puppet té una entitat anomenada mòduls.
Mòduls - Aquests són conjunts de classes, definicions i altres entitats Puppet col·locades en un directori separat. En altres paraules, un mòdul és una peça independent de la lògica de Puppet. Per exemple, pot haver-hi un mòdul per treballar amb nginx, i contindrà què i només el que es necessita per treballar amb nginx, o pot haver-hi un mòdul per treballar amb PHP, etc.
Els mòduls es versionen i també s'admeten les dependències dels mòduls entre si. Hi ha un repositori obert de mòduls - Forja de titelles.
Al servidor de titelles, els mòduls es troben al subdirectori mòduls del directori arrel. Dins de cada mòdul hi ha un esquema de directoris estàndard: manifests, fitxers, plantilles, lib, etc.
Estructura de fitxers en un mòdul
L'arrel del mòdul pot contenir els directoris següents amb noms descriptius:
manifests - conté manifests
files - conté fitxers
templates - conté plantilles
lib — conté codi Ruby
Aquesta no és una llista completa de directoris i fitxers, però és suficient per a aquest article de moment.
Els recursos (classes, definicions) d'un mòdul no es poden anomenar com vulgueu. A més, hi ha una correspondència directa entre el nom d'un recurs i el nom del fitxer en el qual Puppet buscarà una descripció d'aquest recurs. Si incompleu les regles de denominació, Puppet simplement no trobarà la descripció del recurs i obtindreu un error de compilació.
Les regles són senzilles:
Tots els recursos d'un mòdul han d'estar a l'espai de noms del mòdul. Si es crida el mòdul foo, aleshores s'han de nomenar tots els recursos que hi ha foo::<anything>, o simplement foo.
El recurs amb el nom del mòdul ha d'estar al fitxer init.pp.
Per a altres recursos, l'esquema de noms dels fitxers és el següent:
es descarta el prefix amb el nom del mòdul
tots els dos punts dobles, si n'hi ha, es substitueixen per barres inclinades
s'afegeix una extensió .pp
Ho demostraré amb un exemple. Suposem que estic escrivint un mòdul nginx. Conté els recursos següents:
classe nginx descrit al manifest init.pp;
classe nginx::service descrit al manifest service.pp;
definir nginx::server descrit al manifest server.pp;
definir nginx::server::location descrit al manifest server/location.pp.
plantilles
Segur que tu mateix saps què són les plantilles; no les descriuré amb detall aquí. Però ho deixaré per si de cas enllaç a la Viquipèdia.
Com utilitzar les plantilles: el significat d'una plantilla es pot ampliar mitjançant una funció template, que es passa el camí a la plantilla. Per recursos de tipus file utilitzat juntament amb el paràmetre content. Per exemple, així:
<%= ВЫРАЖЕНИЕ %> — inseriu el valor de l'expressió
<% ВЫРАЖЕНИЕ %> — calcular el valor d'una expressió (sense inserir-la). Les declaracions condicionals (if) i els bucles (cadascun) solen anar aquí.
<%# КОММЕНТАРИЙ %>
Les expressions en ERB s'escriuen en Ruby (en realitat ERB és Embedded Ruby).
Per accedir a les variables del manifest, cal afegir @ al nom de la variable. Per eliminar un salt de línia que apareix després d'una construcció de control, heu d'utilitzar una etiqueta de tancament -%>.
Exemple d'ús de la plantilla
Suposem que estic escrivint un mòdul per controlar ZooKeeper. La classe responsable de crear la configuració té un aspecte semblant a això:
<% 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 -%>
Fets i variables incorporades
Sovint, la part específica de la configuració depèn del que està passant actualment al node. Per exemple, depenent de la versió de Debian, cal que instal·leu una o una altra versió del paquet. Podeu controlar tot això manualment, reescrivint els manifests si canvien els nodes. Però aquest no és un enfocament seriós; l'automatització és molt millor.
Per obtenir informació sobre els nodes, Puppet disposa d'un mecanisme anomenat fets. fets - Aquesta és informació sobre el node, disponible als manifests en forma de variables ordinàries a l'espai de noms global. Per exemple, nom d'amfitrió, versió del sistema operatiu, arquitectura del processador, llista d'usuaris, llista d'interfícies de xarxa i les seves adreces, i molt, molt més. Els fets estan disponibles en manifests i plantilles com a variables regulars.
Un exemple de treball amb fets:
notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог
Formalment parlant, un fet té un nom (cadena) i un valor (hi ha diferents tipus disponibles: cadenes, matrius, diccionaris). Menja conjunt de fets integrats. També pots escriure el teu. Es descriuen els col·leccionistes de fets funcions com a Rubyja sigui com fitxers executables. Els fets també es poden presentar en el formulari fitxers de text amb dades als nodes.
Durant l'operació, l'agent titella copia primer tots els col·lectors de fets disponibles del servidor pappet al node, després els llança i envia els fets recollits al servidor; Després d'això, el servidor comença a compilar el catàleg.
Fets en forma de fitxers executables
Aquests fets es col·loquen en mòduls del directori facts.d. Per descomptat, els fitxers han de ser executables. Quan s'executen, han de generar informació a la sortida estàndard en format YAML o clau=valor.
No oblideu que els fets s'apliquen a tots els nodes que estan controlats pel servidor poppet al qual està desplegat el vostre mòdul. Per tant, a l'script, tingueu cura de comprovar que el sistema disposa de tots els programes i fitxers necessaris perquè el vostre fet funcioni.
#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'
Fets Ruby
Aquests fets es col·loquen en mòduls del directori 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
Fets del text
Aquests fets es col·loquen als nodes del directori /etc/facter/facts.d en vell Titella o /etc/puppetlabs/facts.d en el nou Titella.
A més dels fets, també n'hi ha algunes variables, disponible a l'espai de noms global.
fets de confiança — variables que s'agafen del certificat del client (com que el certificat s'emet normalment en un servidor poppet, l'agent no pot prendre i canviar el seu certificat, de manera que les variables són "de confiança"): el nom del certificat, el nom del amfitrió i domini, extensions del certificat.
fets del servidor —variables relacionades amb la informació sobre el servidor: versió, nom, adreça IP del servidor, entorn.
fets de l'agent — variables afegides directament per l'agent-titella, i no pel factor — nom del certificat, versió de l'agent, versió del titella.
variables mestres - Variables Pappetmaster (sic!). És aproximadament el mateix que a fets del servidor, més els valors dels paràmetres de configuració estan disponibles.
variables del compilador — variables del compilador que difereixen en cada àmbit: el nom del mòdul actual i el nom del mòdul en què s'ha accedit a l'objecte actual. Es poden utilitzar, per exemple, per comprovar que les vostres classes particulars no s'estan utilitzant directament des d'altres mòduls.
Addició 1: com executar i depurar tot això?
L'article contenia molts exemples de codi de titelles, però no ens va dir gens com executar aquest codi. Bé, m'estic corregint.
Un agent és suficient per executar Puppet, però en la majoria dels casos també necessitareu un servidor.
Agent
Almenys des de la versió XNUMX, els paquets de puppet-agent de repositori oficial de Puppetlabs conté totes les dependències (ruby i les gemmes corresponents), de manera que no hi ha dificultats d'instal·lació (estic parlant de distribucions basades en Debian, no fem servir distribucions basades en RPM).
En el cas més senzill, per utilitzar la configuració del titella, n'hi ha prou amb llançar l'agent en mode sense servidor: sempre que es copie el codi del titella al node, 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
És millor, per descomptat, configurar el servidor i executar agents als nodes en mode dimoni; després, una vegada cada mitja hora, aplicaran la configuració baixada del servidor.
Podeu imitar el model de treball push: aneu al node que us interessa i comenceu sudo puppet agent -t. clau -t (--test) de fet inclou diverses opcions que es poden activar individualment. Aquestes opcions inclouen les següents:
no s'executa en mode dimoni (per defecte, l'agent s'inicia en mode dimoni);
tancar després d'aplicar el catàleg (per defecte, l'agent continuarà treballant i aplicant la configuració un cop cada mitja hora);
escriure un registre de treball detallat;
mostrar els canvis als fitxers.
L'agent té un mode de funcionament sense canvis: podeu utilitzar-lo quan no esteu segur d'haver escrit la configuració correcta i voleu comprovar què canviarà exactament l'agent durant el funcionament. Aquest mode està activat pel paràmetre --noop a la línia d'ordres: sudo puppet agent -t --noop.
A més, podeu habilitar el registre de depuració de l'obra: en ell, puppet escriu sobre totes les accions que realitza: sobre el recurs que està processant actualment, sobre els paràmetres d'aquest recurs, sobre quins programes llança. Per descomptat, aquest és un paràmetre --debug.
Servidor
En aquest article no tindré en compte la configuració completa del servidor pappet ni el desplegament de codi; només diré que de la caixa hi ha una versió completament funcional del servidor que no requereix una configuració addicional per funcionar amb un petit nombre de nodes (per exemple, fins a un centenar). Un nombre més gran de nodes requerirà ajustaments: per defecte, puppetserver llança no més de quatre treballadors, per obtenir un major rendiment, cal augmentar-ne el nombre i no oblideu augmentar els límits de memòria, en cas contrari, el servidor recollirà escombraries la majoria del temps.
Desplegament de codi: si el necessiteu de manera ràpida i senzilla, mireu (a r10k)[https://github.com/puppetlabs/r10k], per a instal·lacions petites hauria de ser suficient.
Addendum 2: Directrius de codificació
Col·loqueu tota la lògica en classes i definicions.
Mantingueu les classes i les definicions en mòduls, no en manifests que descriguin nodes.
Utilitzeu els fets.
No feu ifs basats en noms d'amfitrió.
No dubteu a afegir paràmetres per a classes i definicions: això és millor que la lògica implícita oculta al cos de la classe/definició.
Explicaré per què us recomano fer-ho en el següent article.
Conclusió
Acabem amb la introducció. En el proper article us parlaré de Hiera, ENC i PuppetDB.
Només els usuaris registrats poden participar en l'enquesta. Inicia sessiósi us plau.
De fet, hi ha molt més material: puc escriure articles sobre els següents temes, votar sobre què us interessaria llegir:
59,1%Construccions de titelles avançades: algunes merdes de nivell següent: bucles, mapes i altres expressions lambda, col·lectors de recursos, recursos exportats i comunicació entre hostes mitjançant Puppet, etiquetes, proveïdors, tipus de dades abstractes.13
31,8%“Sóc l'administrador de la meva mare” o com a Avito vam fer amistat amb diversos servidors poppet de diferents versions i, en principi, la part sobre l'administració del servidor poppet.7
81,8%Com escrivim el codi de titelles: instrumentació, documentació, proves, CI/CD.18