Gestion de la configuration 101: Écrire des manifestes de marionnettes

introduction

En résumé, la gestion de la configuration du serveur (également appelée IT Automation) est une solution permettant de transformer l’administration de votre infrastructure en base de code, décrivant tous les processus nécessaires au déploiement d’un serveur dans un ensemble de scripts de provisioning pouvant être versionnés et réutilisés facilement. Cela peut considérablement améliorer l’intégrité de toute infrastructure de serveur au fil du temps.

Dans un https://www.digitalocean.com/community/tutorials/an-introduction-to-configuration-management (guide précédent), nous avons présenté les principaux avantages de la mise en œuvre d’une stratégie de gestion de la configuration pour votre infrastructure de serveur. les outils fonctionnent et ce qu’ils ont généralement en commun.

Cette partie de la série vous guidera à travers le processus d’automatisation du provisionnement de serveur à l’aide de Puppet, un outil de gestion de configuration populaire capable de gérer une infrastructure complexe de manière transparente, en utilisant un serveur maître pour orchestrer la configuration des nœuds. Nous nous concentrerons sur la terminologie linguistique, la syntaxe et les fonctionnalités nécessaires pour créer un exemple simplifié permettant d’automatiser complètement le déploiement d’un serveur Web Ubuntu 18.04 à l’aide d’Apache.

Voici la liste des étapes à automatiser pour atteindre notre objectif:

  1. Mettre à jour le cache + apt +

  2. Installer Apache

  3. Créer un répertoire racine de document personnalisé

  4. Placez un fichier + index.html dans la racine du document personnalisé

  5. Appliquer un modèle pour configurer notre hôte virtuel personnalisé

  6. Redémarrer Apache

Nous commencerons par examiner la terminologie utilisée par Puppet, puis par un aperçu des principales fonctionnalités linguistiques pouvant être utilisées pour écrire des manifestes. À la fin de ce guide, nous partagerons l’exemple complet afin que vous puissiez l’essayer vous-même.

Commencer

Avant que nous puissions passer à une vue plus pratique de Puppet, il est important que nous nous familiarisions avec la terminologie et les concepts importants introduits par cet outil.

Conditions de la marionnette

  • * Puppet Master *: le serveur maître qui contrôle la configuration sur les nœuds

  • * Nœud d’agent de marionnettes *: un nœud contrôlé par un maître de marionnettes

  • * Manifest *: un fichier contenant un ensemble d’instructions à exécuter

  • * Ressource *: une partie du code qui déclare un élément du système et comment son état doit être changé. Par exemple, pour installer un package, nous devons définir une ressource package et nous assurer que son état est défini sur «installé».

  • * Module *: une collection de manifestes et autres fichiers associés organisés de manière prédéfinie pour faciliter le partage et la réutilisation de parties d’un provisioning

  • * Classe *: comme avec les langages de programmation habituels, les classes sont utilisées dans Puppet pour mieux organiser le provisioning et faciliter la réutilisation de parties du code.

  • * Facts *: variables globales contenant des informations sur le système, telles que les interfaces réseau et le système d’exploitation

  • * Services *: utilisés pour déclencher des changements d’état du service, comme le redémarrage ou l’arrêt d’un service

Les provisionnements de marionnettes sont écrits à l’aide d’un DSL personnalisé (langage spécifique au domaine) basé sur Ruby.

Ressources

Avec Puppet, les tâches ou les étapes sont définies en déclarant * ressources *. Les ressources peuvent représenter des packages, des fichiers, des services, des utilisateurs et des commandes. Ils peuvent avoir un état, ce qui déclenchera un changement de système si l’état d’une ressource déclarée est différent de celui actuellement présent sur le système. Par exemple, une ressource package définie sur + installed + dans votre manifeste déclenchera l’installation d’un package sur le système si celui-ci n’avait pas déjà été installé.

Voici à quoi ressemble une ressource package:

package { 'nginx':
   ensure  => 'installed'
}

Vous pouvez exécuter n’importe quelle commande arbitraire en déclarant une ressource + exec +, comme ceci:

exec { 'apt-get update':
   command => '/usr/bin/apt-get update'
}

Notez que la partie + apt-get update sur la première ligne n’est pas la déclaration de commande réelle, mais un identifiant pour cette ressource unique. Nous avons souvent besoin de référencer d’autres ressources à partir d’une ressource, et nous utilisons leur identifiant pour cela. Dans ce cas, l’identifiant est + apt-get update, mais il pourrait s’agir d’une autre chaîne.

Dépendance des ressources

Lors de la rédaction de manifestes, il est important de garder à l’esprit que Puppet n’évalue pas les ressources dans le même ordre qu’elles ont été définies. C’est une source de confusion courante pour ceux qui commencent à utiliser Puppet. Les ressources doivent définir explicitement la dépendance les unes par rapport aux autres, sinon rien ne garantit quelle ressource sera évaluée et par conséquent exécutée en premier.

Par exemple, supposons que vous souhaitiez exécuter une commande, mais vous devez vous assurer qu’une dépendance est préalablement installée:

package { 'python-software-properties':
   ensure => 'installed'
}

exec { 'add-repository':
   command => '/usr/bin/add-apt-repository ppa:ondrej/php5 -y'
   require => Package['python-software-properties']
}

L’option + require + reçoit en paramètre une référence à une autre ressource. Dans ce cas, nous faisons référence à la ressource package identifiée comme + python-software-properties. + Il est important de noter que bien que nous utilisions + exec +, + package +, et autres pour déclarer des ressources (en minuscules), lorsque nous faisons référence à des ressources définies précédemment, nous utilisons + Exec +, '+ Package + `, etc. sur (en majuscule).

Maintenant, disons que vous devez vous assurer qu’une tâche est exécutée * avant * une autre. Dans un cas comme celui-ci, nous pouvons utiliser l’option + before + à la place:

package { 'curl':
   ensure => 'installed'
   before => Exec['install script']
}

exec { 'install script':
   command => '/usr/bin/curl http://example.com/some-script.sh'

Format du manifeste

Les manifestes sont essentiellement une collection de déclarations de ressources, utilisant l’extension + .pp +. Vous trouverez ci-dessous un exemple de livre de jeu simple qui effectue deux tâches: met à jour le cache + apt + et installe + vim + par la suite:

exec { 'apt-get update':
   command => '/usr/bin/apt-get update'
}

package { 'vim':
   ensure => 'installed'
   require => Exec['apt-get update']
}

Avant la fin de ce guide, nous verrons un exemple plus concret d’un manifeste, expliqué en détail. La section suivante vous donnera un aperçu des éléments et fonctionnalités les plus importants pouvant être utilisés pour écrire les manifestes de marionnettes.

Manifestes d’écriture

Travailler avec des variables

Les variables peuvent être définies n’importe où dans un manifeste. Les types de variables les plus courants sont les chaînes et les tableaux de chaînes, mais d’autres types sont également pris en charge, tels que les booléens et les hachages.

L’exemple ci-dessous définit une variable de chaîne qui sera utilisée ultérieurement dans une ressource:

$package = "vim"

package { $package:
  ensure => "installed"
}

Utiliser des boucles

Les boucles sont généralement utilisées pour répéter une tâche en utilisant différentes valeurs d’entrée. Par exemple, au lieu de créer 10 tâches pour installer 10 packages différents, vous pouvez créer une tâche unique et utiliser une boucle pour répéter la tâche avec tous les packages que vous souhaitez installer.

Le moyen le plus simple de répéter une tâche avec des valeurs différentes dans Puppet consiste à utiliser des tableaux, comme dans l’exemple ci-dessous:

$packages = ['vim', 'git', 'curl']

package { $packages:
  ensure => "installed"
}

À partir de la version 4, Puppet prend en charge d’autres méthodes permettant de parcourir les tâches. L’exemple ci-dessous fait la même chose que l’exemple précédent, mais cette fois en utilisant l’itérateur each each. Cette option vous donne plus de flexibilité pour parcourir en boucle les définitions de ressources:

$packages.each |String $package| {
 package { $package:
   ensure => "installed"
 }
}

Utilisation de conditions

Les conditions peuvent être utilisées pour déterminer de manière dynamique si un bloc de code doit être exécuté ou non, en fonction d’une variable ou d’une sortie d’une commande, par exemple.

Puppet supporte la plupart des structures conditionnelles que vous pouvez trouver avec les langages de programmation traditionnels, telles que les instructions + if / else + et + case +. En outre, certaines ressources telles que + exec + prendront en charge des attributs qui fonctionnent comme une condition, mais acceptent uniquement une sortie de commande en tant que condition.

Supposons que vous souhaitiez exécuter une commande basée sur un fact. Dans ce cas, pour tester la valeur d’une variable, vous devez utiliser l’une des structures conditionnelles prises en charge, comme + if / else +:

if $osfamily != 'Debian' {
warning('This manifest is not supported on this OS.')
}
else {
notify { 'Good to go!': }
}

Une autre situation courante consiste à conditionner l’exécution d’une commande en fonction du résultat d’une autre commande. Dans de tels cas, vous pouvez utiliser + onlyif + ou + à moins que +, comme dans l’exemple ci-dessous. Cette commande ne sera exécutée que lorsque la sortie de + / bin / php + aura abouti, c’est-à-dire que la commande se ferme avec le statut * 0 *:

exec { "Test":
command => "/bin/echo PHP is installed here > /tmp/test.txt",
onlyif => "/bin/which php"
}

De même, + moins + + exécutera la commande tous les temps, sauf lorsque la commande sous + moins que + se termine correctement:

exec { "Test":
command => "/bin/echo PHP is NOT installed here > /tmp/test.txt",
unless => "/bin/which php"
}

Travailler avec des modèles

Les modèles sont généralement utilisés pour configurer les fichiers de configuration, ce qui permet d’utiliser des variables et d’autres fonctionnalités destinées à rendre ces fichiers plus polyvalents et réutilisables. Puppet prend en charge deux formats différents pour les modèles: Embedded Puppet (EPP) et Embedded Ruby (ERB). Le format EPP, cependant, ne fonctionne qu’avec les versions récentes de Puppet (à partir de la version 4.0).

Vous trouverez ci-dessous un exemple de modèle ERB permettant de configurer un hôte virtuel Apache, à l’aide d’une variable permettant de configurer la racine du document pour cet hôte:

<VirtualHost *:80>
   ServerAdmin webmaster@localhost
   DocumentRoot <%= @doc_root %>

   <Directory <%= @doc_root %>>
       AllowOverride All
       Require all granted
   </Directory>
</VirtualHost>

Pour appliquer le modèle, nous devons créer une ressource + file qui restitue le contenu du modèle avec la méthode` + template a`. Voici comment appliquer ce modèle pour remplacer l’hôte virtuel Apache par défaut:

file { "/etc/apache2/sites-available/000-default.conf":
   ensure => "present",
   content => template("apache/vhost.erb")
}

Puppet fait quelques hypothèses lorsqu’il s’agit de fichiers locaux afin de renforcer l’organisation et la modularité. Dans ce cas, Puppet rechercherait un fichier de modèle + vhost.erb + dans un dossier + apache / templates +, dans votre répertoire de modules.

Définir et déclencher des services

Les ressources de service sont utilisées pour s’assurer que les services sont initialisés et activés. Ils sont également utilisés pour déclencher des redémarrages de service.

Prenons en considération notre exemple d’utilisation du modèle précédent, dans lequel nous avons configuré un hôte virtuel Apache. Si vous voulez vous assurer que Apache est redémarré après un changement d’hôte virtuel, vous devez d’abord créer une ressource service pour le service Apache. Voici comment une telle ressource est définie dans Puppet:

service { 'apache2':
   ensure => running,
   enable => true
}

Maintenant, lors de la définition de la ressource, vous devez inclure une option + notify + afin de déclencher un redémarrage:

file { "/etc/apache2/sites-available/000-default.conf":
   ensure => "present",
   content => template("vhost.erb"),
   notify => Service['apache2']
}

Exemple de manifeste

Voyons maintenant un manifeste qui automatisera l’installation d’un serveur Web Apache sur un système Ubuntu 14.04, comme indiqué dans l’introduction de ce guide.

L’exemple complet, y compris le fichier de modèle pour la configuration d’Apache et un fichier HTML destiné au serveur Web, est disponible à l’adresse on Github. Le dossier contient également un fichier Vagrant qui vous permet de tester le manifeste dans une installation simplifiée, à l’aide d’une machine virtuelle gérée par https://vagrantup.com [Vagrant].

Vous trouverez ci-dessous le manifeste complet:

default.pp

$doc_root = "/var/www/example"

exec { 'apt-get update':
command => '/usr/bin/apt-get update'
}

package { 'apache2':
ensure  => "installed",
require => Exec['apt-get update']
}

file { $doc_root:
ensure => "directory",
owner => "www-data",
group => "www-data",
mode => 644
}

file { "$doc_root/index.html":
  ensure => "present",
  source => "puppet:///modules/main/index.html",
  require => File[$doc_root]
}

file { "/etc/apache2/sites-available/000-default.conf":
  ensure => "present",
  content => template("main/vhost.erb"),
  notify => Service['apache2'],
  require => Package['apache2']
}

service { 'apache2':
  ensure => running,
  enable => true
}

Manifeste Expliquée

ligne 1

Le manifeste commence par une définition de variable, + $ doc_root +. Cette variable est utilisée ultérieurement dans une déclaration de ressource.

lignes 3-5

Cette ressource * exec * exécute une commande + apt-get update.

lignes 7-10

Cette ressource * package * installe le package + apache2 +, en définissant que la ressource + apt-get update + est une exigence, ce qui signifie qu’elle ne sera exécutée qu’une fois la ressource requise évaluée.

lignes 12-17

Nous utilisons ici une ressource * fichier * pour créer un nouveau répertoire qui servira de racine de document. La ressource + fichier + peut être utilisée pour créer des répertoires et des fichiers, mais également pour appliquer des modèles et copier des fichiers locaux sur le serveur distant. Cette tâche peut être exécutée à n’importe quel moment de la mise en service, il n’a donc pas été nécessaire de définir un + require + ici.

lignes 19-23

Nous utilisons ici une autre ressource * file *, cette fois pour copier notre fichier * index.html * local à la racine du document à l’intérieur du serveur. Nous utilisons le paramètre + source + pour indiquer à Puppet où trouver le fichier d’origine. Cette nomenclature est basée sur la manière dont Puppet gère les fichiers locaux. Si vous consultez le Github exemple de référentiel, vous verrez comment la structure de répertoires doit être créée. afin de laisser Puppet trouver cette ressource. Le répertoire racine du document doit être créé avant l’exécution de cette ressource, c’est pourquoi nous avons inclus une option + require + faisant référence à la ressource précédente.

lignes 25-30

Une nouvelle ressource * fichier * est utilisée pour appliquer le modèle Apache et informer le service de redémarrage. Pour cet exemple, notre provisioning est organisé dans un module appelé * main *, c’est pourquoi le modèle source est * main / vhost.erb *. Nous utilisons une instruction + require + pour nous assurer que la ressource modèle n’est exécutée qu’une fois le package + apache2 + installé, sinon la structure de répertoires utilisée par Apache pourrait ne pas être présente.

lignes 32-35

Enfin, la ressource * service * déclare le service + apache2 +, que nous notifions pour un redémarrage à partir de la ressource qui applique le modèle d’hôte virtuel.

Conclusion

Puppet est un puissant outil de gestion de la configuration qui utilise un DSL personnalisé expressif pour gérer les ressources du serveur et automatiser les tâches. Sa langue offre des ressources avancées pouvant donner une flexibilité supplémentaire à vos configurations de provisioning; Il est important de se rappeler que les ressources ne sont pas évaluées dans le même ordre de définition. C’est pourquoi vous devez être prudent lors de la définition des dépendances entre les ressources afin d’établir la bonne chaîne d’exécution.

Dans le next guide de cette série, nous allons jeter un coup d’œil à Chef, un autre puissant outil de gestion de la configuration qui exploite le langage de programmation Ruby pour automatiser l’administration et l’approvisionnement des infrastructures.