Introduction à Puppet avec Vagrant
Publié le : 12 octobre 2022 | Mis à jour le : 27 juin 2023Table des matières
Je n’ai jamais utilisé puppet
, mais voilà dans le cadre de mon travail, je
dois transposer ce qui était fait avec puppet
en utilisant Ansible. Mais voilà
il faut comprendre comment fonctionne puppet
pour y arriver. C’est dans ce
cadre que j’écris ce tutoriel qui est une simple introduction à puppet
.
Introduction
Puppet
est un outil de gestion de configuration qui fonctionne
principalement en mode client/serveur. En effet, il est également possible
d’appliquer le code directement sur une machine cliente sans faire appel au
serveur. Pour rappel un outil de gestion de configuration a pour but :
- de déployer des configurations sur les serveurs ;
- d’automatiser le déploiement et la configuration de logiciels ;
- de s’assurer que les configurations voulues sont celles qui sont appliquées ;
Le serveur est appelé le master
et les clients des nodes
. Sur les nodes sont
installées un agent qui se charge d’obtenir les configurations déclarées sur le
master. Le master regroupe les classes en ce qu’on appelle un catalogue et qui
sera appliqué sur le node par l’agent. Dans le cas où la configuration décrite
n’est pas celle présente sur le node, l’agent appliquera les changements
nécessaires pour la respecter.
Attention : Pour ceux qui utilisent Ansible
et qui découvrent Puppet:
Puppet n’exécute pas les instructions dans l’ordre de déclaration, mais
selon un ordre déterminé à partir des dépendances entre classes.
Le langage de Puppet
Les manifests
Le code Puppet utilise des fichiers .pp
qui sont écrits dans un langage
déclaratif basé sur du Ruby. Ces fichiers sont appelés des manifests
. Dans ces
manifests on utilise des ressources de base qui peuvent être de type compte
user, group, file, package, service, exec et cron. Ces ressources présentent un
haut niveau d’abstraction. Par exemple, il n’est pas nécessaire d’indiquer le
système de package à utiliser sur la distribution cible du node. Pour identifier
le système hôte, l’agent puppet utilise Facter
qui décrit le système sous la
forme d’un fichier JSON.
Les classes
Une classe est une collection de ressources liées qui, une fois la classe définie, peuvent être déclarées comme une seule unité. Par exemple, une classe peut contenir toutes les ressources (telles que des fichiers, des paramètres, des modules et des scripts) nécessaires pour configurer le serveur Web Apache sur un hôte. Les classes peuvent également déclarer d’autres classes.
Les modules
Un module est un ensemble de classes, de types de ressources, de fichiers, de fonctions et de modèles, regroupés pour atteindre un objectif particulier. Par exemple, un module peut configurer une instance de serveur Web Apache. Des modules écrits par la communauté Puppet sont disponibles sur la PuppetForge.
Apprendre Puppet avec Vagrant
Comme pour Ansible
, Salt
et Chef
, Vagrant
met à disposition un
provisionner
prenant en charge les manifests Puppet
.
Voici un exemple de fichier Vagrantfile l’utilisant :
# -*- mode: ruby -*-
# vi: set ft=ruby :
$script=<<EOF
wget http://apt.puppet.com/puppet7-release-jammy.deb
sudo dpkg -i puppet7-release-jammy.deb
sudo apt update && sudo apt install puppet-agent
EOF
Vagrant.configure("2") do |config|
config.vm.box = "generic/ubuntu2204"
config.vm.network :forwarded_port, guest: 80, host: 8080
config.vm.provision "shell", inline: $script
config.vm.provision :puppet do |puppet|
puppet.module_path = "puppet/modules"
puppet.manifests_path = "puppet/manifests"
puppet.manifest_file = "init.pp"
end
end
Vous remarquez que j’installe juste le puppet-agent
et j’indique le chemin du
manifest
à utiliser. D’ailleurs écrivons le :
Ecriture d’un simple manifest
Nous allons tout simplement demander l’installation du package apache. Il suffit
donc de créer le fichier init.pp
dans le répertoire puppet/manifests
:
package { 'apache2':
ensure => installed,
}
notify { 'apache installed':
message => 'apache a été installé',
}
Nous utilisons deux simples ressources une de type package et une autre de type notify. Vous pouvez retrouver tous les arguments de ces ressources en suivant leur lien respectif. Une fois dessus vous serez capable d’utiliser n’importe quelle autre type de ressource Puppet.
Utilisation de modules de la puppetForge
Maintenant que l’on sait écrire de simples manifests voyons comment récupérer et utiliser des modules Puppet sur la puppetForge.
Pour télécharger les modules, il faut au préalable installer le puppet-agent :
sudo apt install puppet-agent
Ensuite on crée le répertoire et on y télécharge le module désiré :
mkdir -p puppet/modules
puppet module install puppetlabs-apache --version 8.2.1 --modulepath=puppet/modules
## Quels modules avons nous ?
puppet module list --modulepath=./
/home/vagrant/Projets/personal/vagrant-puppet/puppet/modules
├── puppet-nginx (v4.0.0)
├── puppetlabs-apache (v8.2.1)
├── puppetlabs-concat (v7.3.0)
└── puppetlabs-stdlib (v8.4.0)
Notre module est installé localement. Je vais l’utiliser dans mon manifest.
Remplacer le contenu du fichier init.pp
avec ces lignes (on remplace package
par le module) :
class { 'apache':
default_vhost => false,
}
notify { 'apache installed':
message => 'apache a été installé',
}
On relance notre box Vagrant. Comme pour Chef il faut utiliser la commande reload pour recharger les modifications faites localement :
vagrant reload --provision
Faites attention à bien choisir vos modules. Privilégiez ceux ayant les labels Puppet Supported et Puppet Approved.
Maintenant que nous savons utiliser un module de la forge voyons comment en écrire.
Ecriture d’un simple module
Dans un premier temps installons le PDK pour créer la structure de notre module:
wget --content-disposition 'https://pm.puppet.com/cgi-bin/pdk_download.cgi?dist=ubuntu&rel=20.04&arch=amd64&ver=latest'
sudo dpkg -i pdk_2.5.0.0-1focal_amd64.deb
Maintenant dans le répertoire puppet/modules créons le squelette de notre module test:
mkdir -p puppet/modules
puppet/modules
pdk new module apache2
Nous avons la structure, créons notre première classe.
cd apache2
pdk new class apache2
---------------Files added--------------
/home/vagrant/Projets/personal/vagrant-puppet/puppet/modules/apache2/spec/classes/apache2_spec.rb
/home/vagrant/Projets/personal/vagrant-puppet/puppet/modules/apache2/manifests/init.pp
Pour cela éditer le fichier puppet/modules/apache2/manifests/init.pp
. Prenons
comme exemple l’installation du serveur web apache2 :
# @summary A short summary of the purpose of this class
#
# A description of what this class does
#
# @example
# include apache2
class apache2 {
# Installation de apache
package { 'apache2':
ensure => 'present',
} ->
# On démarre le service apache
service { 'apache2':
ensure => 'running',
}
}
Pour valider votre code, nous allons utiliser la commande validate de pdk 👍 :
pdk validate
pdk (INFO): Using Ruby 2.7.6
pdk (INFO): Using Puppet 7.16.0
pdk (INFO): Running all available validators...
pdk (INFO): Validator 'puppet-epp' skipped for '/home/vagrant/Projets/personal/vagrant-puppet/puppet/modules/apache2'. No files matching '["**/*.epp"]' found to validate.
pdk (INFO): Validator 'task-metadata-lint' skipped for '/home/vagrant/Projets/personal/vagrant-puppet/puppet/modules/apache2'. No files matching '["tasks/*.json"]' found to validate.
┌ [✔] Running metadata validators ...
├── [✔] Checking metadata syntax (metadata.json tasks/*.json).
└── [✔] Checking module metadata style (metadata.json).
┌ [✔] Running puppet validators ...
├── [✔] Checking Puppet manifest syntax (**/*.pp).
└── [✔] Checking Puppet manifest style (**/*.pp).
┌ [✔] Running ruby validators ...
└── [✔] Checking Ruby code style (**/**.rb).
┌ [✔] Running tasks validators ...
├── [✔] Checking task names (tasks/**/*).
└── [✔] Checking task metadata style (tasks/*.json).
┌ [✔] Running yaml validators ...
└── [✔] Checking YAML syntax (**/*.yaml **/*.yml).
Pour vous aider à écrire votre code Puppet correctement, vous pouvez installer l’extension vscode officielle. Elle est plutôt complète on y retrouve : linting, indentation, intellisense, debug …
Éditer maintenant le fichier puppet/manifests/init.pp et remplacez les lignes par :
class { 'apache2':
}
Utilisation des facts
Nous allons simplement ajouter un test vérifiant si le systeme d’exploitation est bien celui attendu. Pour retrouver la structure des facts puppet il suffit de lancer la commande facter. Pour limiter le retour à certains paramètres il suffit d’ajouter le nom derrière :
facter
aio_agent_version => 7.20.0
augeas => {
version => "1.13.0"
}
...
os => {
architecture => "amd64",
distro => {
codename => "jammy",
description => "Ubuntu 22.04.1 LTS",
id => "Ubuntu",
release => {
full => "22.04",
major => "22.04"
}
},
family => "Debian",
hardware => "x86_64",
name => "Ubuntu",
release => {
full => "22.04",
major => "22.04"
},
...
facter os.family
Debian
Ajoutons donc ce test dans notre manifest:
# @summary A short summary of the purpose of this class
#
# A description of what this class does
#
# @example
# include apache2
class apache2 {
if $facts['os']['family'] != 'Debian' {
notice("This os ${facts['os']['name']} is not supported")
}
else {
# Installation de apache
package { 'apache2':
ensure => 'present',
} ->
# On démarre le service apache
service { 'apache2':
ensure => 'running',
}
}
}
Changez la distribution de base de votre Vagrantfile par une fedora et regarder le résultat :
# -*- mode: ruby -*-
# vi: set ft=ruby :
$script=<<EOF
wget http://yum.puppet.com/puppet7-release-fedora-36.noarch.rpm
sudo yum install puppet7-release-fedora-36.noarch.rpm -y
sudo yum update -y && sudo yum install puppet-agent -y
EOF
Vagrant.configure("2") do |config|
config.vm.box = "generic/fedora36"
config.vm.network :forwarded_port, guest: 80, host: 8080
config.vm.provision "shell", inline: $script
config.vm.provision :puppet do |puppet|
puppet.module_path = "puppet/modules"
puppet.manifests_path = "puppet/manifests"
puppet.manifest_file = "init.pp"
end
end
On détruit et on re-provisionne la machine :
vagrant destroy -f
vagrant up
...
==> default: Notice: Scope(Class[Apache2]): This os Fedora is not supported
==> default: Notice: Compiled catalog for fedora36.localdomain in environment production in 0.04 seconds
==> default: Notice: Applied catalog in 0.06 seconds
Ca fonctionne.
Utilisation de variables et paramètres
Nous remplacer le fichier index.html par notre propre contenu. Pour cela nous allons déclarer un paramètre content dans notre class apache2. Ce paramètre sera ensuite utilisé dans la ressource file :
# @summary A short summary of the purpose of this class
#
# A description of what this class does
#
# @example
# include apache2
class apache2 ( String $content ) {
if $facts['os']['family'] != 'Debian' {
notice("This os ${facts['os']['name']} is not supported")
}
else {
# Installation de apache
package { 'apache2':
ensure => 'present',
} ->
# On démarre le service apache
service { 'apache2':
ensure => 'running',
}
file { '/var/www/html/index.html':
ensure => 'file',
content => $content,
path => '/var/www/html/index.html',
}
}
}
Pour déclarer les variables par défaut, nous allons l’ajouter dans le fichier
common.yaml
présent dans le répertoire data
de notre module :
---
apache2::content: Ma première variable
Une autre manière aurait eté de l’ajouter dans la déclaration de la classe :
class apache2 ( String $content = 'Mon texte par défaut') {
On relance, on récupère l’adresse IP de notre VM et un petit curl pour vérifier que le contenu est bien celui attendu :
vagrant destroy -f
vagrant up
...
vagrant ssh-config
Host default
HostName 192.168.121.16
User vagrant
Port 22
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /home/vagrant/Projets/personal/vagrant-puppet/.vagrant/machines/default/libvirt/private_key
IdentitiesOnly yes
LogLevel FATAL
curl http://192.168.121.16
Ma première variable
Maintenant pour surcharger le paramètre dans l’instance de notre classe il suffit d’ajouter la variable content dans l’appel de la classe :
class { 'apache2':
content=> 'Ma variable est défini dans la classe',
}
On relance et on repasse le curl :
curl http://192.168.121.16
Ma variable est défini dans la classe
On relance la validation de notre module :
pdk validate
...
└── [✔] Checking YAML syntax (**/*.yaml **/*.yml).
pdk (WARNING): puppet-lint: missing documentation for class parameter apache2::content (manifests/init.pp:7:24)
Aie. Il faut documenter la variable. Modifions le code de notre manifest :
# @summary A short summary of the purpose of this class
#
# A description of what this class does
#
# @param content
# Valid option: une chaine de caractère
class apache2 ( String $content ) {
if $facts['os']['family'] != 'Debian' {
notice("This os ${facts['os']['name']} is not supported")
}
C’est réglé.
Plus d’infos sur les paramètres
Utilisation de template
Nous aimerions plutôt que le contenu de notre fichier utilise un template Puppet
(EPP) dans lequel on va injecter notre variable content
. Il faut dans un
premier temps créer le fichier index.epp
dans le répertoire templates
de
notre module :
Hello mon premier Template hébergé sur <%= $facts[hostname] %><br>
<%= $content %>
<% if $facts[hostname] =~ 'ubuntu2204' { -%>
Bonne machine
<% } -%>
La syntaxe utilisée pour les templates (pas simple) :
- <%= VAR %> affiche le contenu d’une variable
- <% EXPRESSION %> une instruction interprété
- <%# COMMENT %> un commentaire
- <%- ou -%> supprime le saut de ligne avant ou après
On regarde le résultat :
curl http://192.168.121.16
Hello mon premier Template hébergé sur ubuntu2204<br>
Ma variable est défini dans la classe
Bonne machine
Plus d’infos sur les templates Puppet
Plus loin avec Puppet
Maintenant que vous savez écrire de simples manifests, je vous propose de vous montrer comment mettre en place un environnement de développement Puppet sur votre machine. Cela consiste à installer dans des VM un serveur mâitre et des nodes clients. Le code de puppet peut ensuite être appliqué directement sur les nodes.