Gestion de la configuration 101: rédaction de recettes de chef

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 unprevious guide, nous avons parlé des principaux avantages de la mise en œuvre d'une stratégie de gestion de la configuration pour votre infrastructure serveur, du fonctionnement des outils de gestion de la configuration et de ce que ces outils ont généralement en commun.

Cette partie de la série vous guidera tout au long du processus d’automatisation du provisionnement de serveur à l’aide de Chef, un puissant outil de gestion de la configuration qui exploite le langage de programmation Ruby pour automatiser l’administration et le provisionnement de l’infrastructure. Nous nous concentrerons sur la terminologie linguistique, la syntaxe et les fonctionnalités nécessaires pour créer un exemple simplifié permettant d'automatiser entièrement 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 deapt

  2. Installer Apache

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

  4. Placez un fichierindex.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 Chef, puis par un aperçu des principales fonctionnalités linguistiques pouvant être utilisées pour rédiger des recettes. À la fin de ce guide, nous partagerons l’exemple complet afin que vous puissiez l’essayer vous-même.

[.note] #Note: ce guide est destiné à vous familiariser avec le langage Chef et à écrire des recettes pour automatiser l'approvisionnement de votre serveur. Pour une vue plus introductive de Chef, y compris les étapes nécessaires pour installer et démarrer avec cet outil, veuillez vous référer àChef’s official documentation.
#

Commencer

Avant de pouvoir passer à une vision plus pratique de Chef, il est important de se familiariser avec la terminologie et les concepts importants introduits par cet outil.

Termes Chef

  • Chef Server: un serveur central qui stocke les informations et gère l'approvisionnement des nœuds

  • Chef Node: un serveur individuel géré par un serveur Chef

  • Chef Workstation: une machine contrôleur sur laquelle les approvisionnements sont créés et téléchargés sur Chef Server

  • Recipe: un fichier contenant un ensemble d'instructions (ressources) à exécuter. Une recette doit être contenue dans unCookbook

  • Resource: une partie de code qui déclare un élément du système et quelle action doit être exécutée. Par exemple, pour installer un package, nous déclarons une ressourcepackage avec l'actioninstall

  • Cookbook: une collection de recettes et d'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

  • Attributes: détails sur un nœud spécifique. Les attributs peuvent être automatiques (voir la définition suivante) et peuvent également être définis dans des recettes.

  • Automatic Attributes: variables globales contenant des informations sur le système, comme les interfaces réseau et le système d'exploitation (appeléesfacts dans d'autres outils). Ces attributs automatiques sont collectés par un outil appeléOhai

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

Format de recette

Les recettes du chef sont écrites en Ruby. Une recette est fondamentalement un ensemble de définitions de ressources qui créeront un ensemble d'instructions pas à pas à exécuter par les nœuds. Ces définitions de ressources peuvent être combinées avec du code Ruby pour plus de flexibilité et de modularité.

Vous trouverez ci-dessous un exemple simple de recette qui exécuteraapt-get update et installeravim par la suite:

execute "apt-get update" do
 command "apt-get update"
end

apt_package "vim" do
 action :install
end

Écrire des recettes

Travailler avec des variables

Les variables locales peuvent être définies dans les recettes en tant que variables locales Ruby normales. L'exemple ci-dessous montre comment créer une variable locale qui sera utilisée ultérieurement dans une définition de ressource:

package  = "vim"

apt_package package do
 action :install
end

Ces variables, cependant, ont une portée limitée, n'étant valables que dans le fichier où elles ont été définies. Si vous souhaitez créer une variable et la rendre disponible globalement, afin de pouvoir l'utiliser à partir de n'importe lequel de vos livres de recettes ou recettes, vous devez définir uncustom attribute.

Utilisation d'attributs

Les attributs représentent des détails sur un nœud. Chef possède des attributs automatiques, qui sont les attributs collectés par un outil appelé Ohai et contenant des informations sur le système (telles que la plate-forme, le nom d'hôte et l'adresse IP par défaut), mais vous permettent également de définir vos propres attributs personnalisés.

Les attributs ont différents niveaux de priorité, définis par le type d'attribut que vous créez. Les attributsdefault sont le choix le plus courant, car ils peuvent toujours être écrasés par d'autres types d'attributs lorsque vous le souhaitez.

L'exemple suivant montre à quoi ressemblerait l'exemple précédent avec un attribut de nœuddefault au lieu d'une variable locale:

node.default['main']['package'] = "vim"

apt_package node['main']['package'] do
 action :install
end

Il y a deux détails à observer dans cet exemple:

La pratique recommandée lors de la définition des variables de noeud consiste à les organiser sous forme de hachage à l'aide du livre de recettes actuel utilisé comme clé. Dans ce cas, nous avons utilisémain, car nous avons un livre de recettes avec le même nom. Cela évite toute confusion si vous travaillez avec plusieurs livres de recettes qui pourraient avoir des attributs nommés similaires.
Notez que nous avons utilisénode.default lors de la définition de l'attribut, mais lors de l'accès à sa valeur plus tard, nous avons utilisénode directement. L'utilisation denode.default définit que nous créons un attribut de typedefault. Cet attribut peut avoir sa valeur écrasée par un autre type avec une priorité plus élevée, comme les attributsnormal ouoverride.

La priorité des attributs peut être légèrement déroutante au début, mais vous vous y habituerez après une certaine pratique. Pour illustrer ce comportement, prenons l'exemple suivant:

node.normal['main']['package']  = "vim"

node.override['main']['package'] = "git"

node.default['main']['package'] = "curl"

apt_package node['main']['package'] do
 action :install
end

Savez-vous quel paquet sera installé dans ce cas? Si vous avez devinégit, vous avez bien deviné. Quel que soit l'ordre dans lequel les attributs ont été définis, la priorité la plus élevée du typeoverride rendra les+node['main']['package'] be evaluated to+`git.

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.

Chef prend en charge toutes les structures de boucles Ruby pour créer des boucles dans les recettes. Pour une utilisation simple,each est un choix courant:

['vim', 'git', 'curl'].each do |package|
 apt_package package do
   action :install
 end
end

Au lieu d'utiliser un tableau en ligne, vous pouvez également créer une variable ou un attribut pour définir les paramètres que vous souhaitez utiliser dans la boucle. Cela gardera les choses plus organisées et plus faciles à lire. Ci-dessous, le même exemple utilisant maintenant une variable locale pour définir les paquetages à installer:

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

packages.each do |package|
 apt_package package do
   action :install
 end
end

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.

Chef prend en charge toutes les conditions préalables Ruby pour la création d'instructions conditionnelles dans les recettes. De plus, tous les types de ressources prennent en charge deux propriétés spéciales qui évalueront une expression avant de décider si la tâche doit être exécutée ou non:if_only etnot_if.

L'exemple ci-dessous vérifiera l'existence dephp avant d'essayer d'installer l'extensionphp-pear. Il utilisera la commandewhich pour vérifier si un exécutablephp est actuellement installé sur ce système. Si la commandewhich php renvoie false, cette tâche ne sera pas exécutée:

apt_package "php-pear" do
 action :install
 only_if "which php"
end

Si nous voulons faire le contraire, en exécutant une commande à tout momentexcept lorsqu'une condition est évaluée comme vraie, nous utilisonsnot_if à la place. Cet exemple installeraphp5 sauf si le système est CentOS:

apt_package "php5" do
 action :install
 not_if { node['platform'] == 'centos' }
end

Pour effectuer des évaluations plus complexes, si vous souhaitez exécuter plusieurs tâches dans des conditions spécifiques, vous pouvez utiliser l’une des conditions standard de Ruby. L'exemple suivant n'exécuteraapt-get update que lorsque le système est soit Debianor Ubuntu:

if node['platform'] == 'debian' || node['platform'] == 'ubuntu'
 execute "apt-get update" do
   command "apt-get update"
 end
end

L'attributnode['platform'] est un attribut automatique de Chef. Le dernier exemple consistait uniquement à démontrer une construction conditionnelle plus complexe, mais il pouvait être remplacé par un simple test utilisant l'attribut automatiquenode['platform_family'], qui renverrait «debian» pour les systèmes Debian et Ubuntu.

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.

Chef utilise des modèles Embedded Ruby (ERB), qui sont identiques à ceux utilisés par Puppet. Ils supportent les conditionnels, les boucles et autres fonctionnalités Ruby.

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


    ServerAdmin webmaster@localhost
    DocumentRoot <%= @doc_root %>

    >
        AllowOverride All
        Require all granted
    

Afin d'appliquer le modèle, nous devons créer une ressourcetemplate. Voici comment appliquer ce modèle pour remplacer l'hôte virtuel Apache par défaut:

template "/etc/apache2/sites-available/000-default.conf" do
 source "vhost.erb"
 variables({ :doc_root => node['main']['doc_root'] })
 action :create
end

Chef s’appuie sur quelques hypothèses lorsqu’il s’agit de fichiers locaux pour renforcer l’organisation et la modularité. Dans ce cas, Chef chercherait un fichier de modèlevhost.erb dans un dossiertemplates qui devrait se trouver dans le même livre de recettes où se trouve cette recette.

Contrairement aux autres outils de gestion de la configuration que nous avons vus jusqu'à présent, Chef dispose d'un champ d'application plus strict pour les variables. Cela signifie que vous devrez fournir explicitement toutes les variables que vous prévoyez d'utiliser dans un modèle, lors de la définition de la ressourcetemplate. Dans cet exemple, nous avons utilisé la méthodevariables pour transmettre l'attributdoc_root dont nous avons besoin au modèle d'hôte virtuel.

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.

Dans Chef, les ressources de service doivent être déclarées avant d'essayer de les notifier, sinon vous obtiendrez une erreur.

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 qu'Apache est redémarré après un changement d'hôte virtuel, vous devez d'abord créer une ressourceservice pour le service Apache. Voici comment une telle ressource est définie dans Chef:

service "apache2" do
  action [ :enable, :start ]
end

Maintenant, lors de la définition de la ressourcetemplate, vous devez inclure une optionnotify afin de déclencher un redémarrage:

template "/etc/apache2/sites-available/000-default.conf" do
 source "vhost.erb"
 variables({ :doc_root => node['main']['doc_root'] })
 action :create
 notifies :restart, resources(:service => "apache2")
end

Exemple de recette

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 modèle pour la configuration d'Apache et un fichier HTML à servir par le serveur Web, peut être trouvéon Github. Le dossier contient également un Vagrantfile qui vous permet de tester le manifeste dans une configuration simplifiée, à l'aide d'une machine virtuelle gérée parVagrant.

Vous trouverez ci-dessous la recette complète:

node.default['main']['doc_root'] = "/vagrant/web"

execute "apt-get update" do
 command "apt-get update"
end

apt_package "apache2" do
 action :install
end

service "apache2" do
 action [ :enable, :start ]
end

directory node['main']['doc_root'] do
 owner 'www-data'
 group 'www-data'
 mode '0644'
 action :create
end

cookbook_file "#{node['main']['doc_root']}/index.html" do
 source 'index.html'
 owner 'www-data'
 group 'www-data'
 action :create
end

template "/etc/apache2/sites-available/000-default.conf" do
 source "vhost.erb"
 variables({ :doc_root => node['main']['doc_root'] })
 action :create
 notifies :restart, resources(:service => "apache2")
end

Recette Expliquée

ligne 1

La recette commence par une définitionattribute,node['main']['doc_root']. Nous aurions pu utiliser une variable locale simple ici, cependant, dans la plupart des scénarios de cas d'utilisation, les recettes doivent définir des variables globales qui seront utilisées à partir de recettes incluses ou d'autres fichiers. Pour ces situations, il est nécessaire de créer un attribut au lieu d'une variable locale, cette dernière ayant une portée limitée.

lignes 3-5

Cette ressourceexecute exécute unapt-get update.

lignes 7-10

Cette ressourceapt_package installe le packageapache2.

lignes 12-15

Cette ressourceservice active et démarre le serviceapache2. Plus tard, nous devrons notifier cette ressource pour le redémarrage du service. Il est important que la définition de service vienne avant toute ressource qui tente de notifier un service, sinon vous obtiendrez une erreur.

lignes 17-22

Cette ressourcedirectory utilise la valeur définie par l'attribut personnalisénode['main']['doc_root'] pour créer un répertoire qui servira de notredocument root.

lignes 24-29

Une ressourcecookbook_file est utilisée pour copier un fichier local sur un serveur distant. Cette ressource copiera notre fichierindex.html et le placera dans la racine du document que nous avons créée dans une tâche précédente.

lignes 31-36

Enfin, cette ressourcetemplate applique notre modèle d'hôte virtuel Apache et notifie le serviceapache2 pour un redémarrage.

Conclusion

Chef est un puissant outil de gestion de la configuration qui exploite le langage Ruby pour automatiser le déploiement et le provisionnement des serveurs. Il vous donne la liberté d'utiliser les fonctionnalités de langage standard pour une flexibilité maximale, tout en offrant des DSL personnalisés pour certaines ressources.