Puppet : mes premiers modules ou le passage de la boucle ssh à la déclaration
Mes scripts bash, une liste d’hôtes, un CSV de variables et une boucle ssh qui pousse des configs rendues par template m’ont rendu de bons services depuis 2007, mais ils décrivent des pas et c’est justement ce que je cherchais à quitter en écrivant mes premiers modules Puppet pour le parc que j’administre au travail. Puppet, c’est un vrai DSL, un vrai modèle agent/master (la 0.25 en ce moment) et une promesse que mes scripts n’ont jamais tenue: déclarer l’état désiré, pas décrire les pas. Ssh, ntp, postfix, munin, zabbix, varnish, rsyslog, ces modules couvrent le socle commun et je les enrichis au fil des besoins.
L’anatomie d’un module
Le pattern que j’ai fini par adopter partout, tel qu’il vit dans ssh/manifests/init.pp:
class ssh {
$packagename = $::operatingsystem ? {
/(?i)(ubuntu|debian)/ => 'openssh-server',
/(?i)(redhat|centos)/ => 'openssh',
default => undef
}
}
class ssh::install {
package { $ssh::packagename: ensure => installed }
}
class ssh::authkeys {
file { '/root/.ssh/authorized_keys':
ensure => file,
replace => false,
owner => 'root', group => 'root', mode => '0644',
content => template("ssh/authorized_keys.erb"),
require => Class["ssh::install"],
notify => Class["ssh::service"],
}
}
class ssh::service {
service { $ssh::servicename:
ensure => running,
hasstatus => true, hasrestart => true, enable => true,
require => Class["ssh::authkeys"],
}
}
Le triptyque install/config/service, le sélecteur sur $::operatingsystem pour survivre à Debian et RedHat dans le même parc, les require/notify qui font du graphe de dépendances le vrai programme et les templates ERB pour les confs.
Le replace => false sur authorized_keys est le détail qui a de la valeur: on pose la clef au premier passage, on ne réécrase jamais ce que l’admin local a ajouté ensuite.
Ce que Puppet apporte
Trois choses que la boucle ssh ne m’a jamais données.
L’idempotence, d’abord, puisque relancer un run ne change rien si l’état est déjà bon, là où mes scripts rejouaient tout à chaque passage.
La convergence, ensuite, parce que l’agent rattrape la dérive au lieu de se contenter de l’installation initiale.
Le graphe, enfin, où les require expliquent l’ordre et le catalogue explique le pourquoi.
Il y a un prix, bien sûr: un agent sur chaque nœud, un master, des certificats à signer et Ruby qui grogne sur les vieilles machines, mais l’échange vaut largement le coup.
[edit: 2026] Le repo oldpuppet
a survécu, mais il n’a été poussé sur GitHub qu’en avril 2016, en un commit par module (“Adding puppet’s apt module”…), avec un README qui fixe le ton: “My first puppet’s modules. Not really proud of this too, but it’s maybe usefull for someone.”
Il en reste 19 modules et avec eux les péchés de l’époque, qui le rendent utile comme archive.
Dans ntp/manifests/init.pp, toute la classe config est commentée (le ntp.conf se gérait ailleurs) et le service s’arrête sur un hôte exempté en dur, if $::fqdn != 'exceptedhost.domain.tld', l’exception codée dans le manifest, la maison du mal Puppet.
Les références en majuscules, Class["ssh::install"], relèvent d’une syntaxe que les versions suivantes ont fait passer du warning à l’erreur.
Et la gestion de version de ces modules, c’était le puppetmaster lui-même, ce qui me fait sourire aujourd’hui.
Pour la suite, j’ai joué un peu avec Ansible dès 2012, mais Puppet est resté le gros de mon travail jusqu’en 2014 et je n’ai adopté Ansible professionnellement que vers 2016; le bricolage bash+CSV de 2007, lui, a continué d’évoluer de son côté, ce que je raconte dans l’ancêtre d’Ansible
.