Introducció a Titella

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.

Introducció a Titella

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:

Aquí teniu un exemple de com és el manifest:

# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова 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à *.

A Puppet 6.0 cron com si tret de la caixa a puppetserver, de manera que no hi ha documentació al lloc general. Però ell està a la caixa a l'agent de titelles, de manera que no cal instal·lar-lo per separat. Podeu veure'n la documentació a la documentació de la cinquena versió de PuppetO a GitHub.

Sobre els recursos en general

Requisits per a la singularitat dels recursos

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:

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

Altres tipus de recursos tenen opcions similars per ajudar a evitar la duplicació − name у servei, command у exec, etcètera.

Metaparàmetres

Cada tipus de recurs té uns paràmetres especials, independentment de la seva naturalesa.

Llista completa de metaparàmetres a la documentació de Titella.

Llista curta:

  • 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.

Exemple:

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

Dependències i notificacions

Documentació aquí.

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:

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
}

Variables

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ó.

Es pot fer utilitzant variables.

Atenció: les variables de Puppet són immutables!

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:

  1. Instal·leu el paquet amb aquest mòdul.
  2. Creem un fitxer de configuració per a aquest mòdul.
  3. Creem un enllaç simbòlic a la configuració de php-fpm.
  4. 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.

Selectors i declaracions condicionals

Documentació aquí.

if

Aquí és senzill:

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

llevat que

tret que sigui un si al revés: el bloc de codi s'executarà si l'expressió és falsa.

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

cas

Aquí tampoc no hi ha res complicat. Podeu utilitzar valors regulars (cadenes, números, etc.), expressions regulars i tipus de dades com a valors.

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

Selectors

Un selector és una construcció de llenguatge semblant a case, però en lloc d'executar un bloc de codi, retorna un valor.

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

Mòduls

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.

Noms dels recursos i noms dels fitxers del mòdul

Documentació aquí.

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í:

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

Veure camí <modulename>/<filename> implica fitxer <rootdir>/modules/<modulename>/templates/<filename>.

A més, hi ha una funció inline_template — rep el text de la plantilla com a entrada, no el nom del fitxer.

Dins de les plantilles, podeu utilitzar totes les variables de Puppet de l'àmbit actual.

Puppet admet plantilles en format ERB i EPP:

Breument sobre ERB

Estructures de control:

  • <%= ВЫРАЖЕНИЕ %> — 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ò:

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

I la plantilla corresponent zoo.cfg.erb - Tan:

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

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

Arribar als fets

Hi ha dues maneres d'abordar els fets:

  • a través del diccionari $facts: $facts['fqdn'];
  • utilitzant el nom del fet com a nom de la variable: $fqdn.

El millor és fer servir un diccionari $facts, o encara millor, indica l'espai de noms global ($::facts).

Aquí teniu la secció corresponent de la documentació.

Variables incorporades

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ó

  1. Col·loqueu tota la lògica en classes i definicions.
  2. Mantingueu les classes i les definicions en mòduls, no en manifests que descriguin nodes.
  3. Utilitzeu els fets.
  4. No feu ifs basats en noms d'amfitrió.
  5. 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

Han votat 22 usuaris. 9 usuaris es van abstenir.

Font: www.habr.com