Aller au contenu principal

Introduction à Puppet

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

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
attention

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

Liens