Puppet II, le retour de la poupée

GNU/Linux Magazine n° 126 | avril 2010 | Nicolas (nico) Szalay
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Dans le numéro 112 du magazine, nous vous avons fait découvrir le gestionnaire de configuration Puppet destiné à simplifier et embellir la vie du sysadmin. Nous poursuivons ici notre voyage de marionnettiste en explorant de nouvelles contrées et en essayant toujours et encore de tirer le meilleur de ce superbe outil.

1. Mongrel + Nginx

En commençant avec Puppet, on se met à déployer quelques petits trucs de-ci de-là, sans se presser, le temps de se faire la main. Mais par défaut, Puppet tourne avec Webrick, un serveur web tout à fait basique écrit en Ruby, dont la capacité est assez limitée lorsque la charge commence à augmenter. Lorsqu'un nombre critique de clients - donc de machines puppet-isées - est atteint, les requêtes ne sont plus complétées et les erreurs s'empilent. Il est temps de changer de serveur pour quelque chose de plus robuste et plus scalable (cet article fera plaisir à ton décideur).

Actuellement, deux solutions à ce problème sont disponibles. D'un côté, Passenger, alias mod_rails ou mod_rack, qui est généralement utilisé en tant que module apache ; et de l'autre, Mongrel, qui est un serveur (et une bibliothèque) en Ruby particulièrement rapide, qui permet d'avoir de multiples instances lancées. Il se marie très bien avec nginx, le très populaire serveur web et reverse proxy, venu du froid de la toundra russe.

Personnellement, j'ai retenu, de façon arbitraire, le couple Mongrel et nginx. C'est donc ce type d'architecture que nous allons monter, afin de supporter la charge toujours plus intense qui pèse sur notre puppetmaster.

# apt-get install puppetmaster mongrel nginx

On va alors modifier /etc/default/puppetmaster pour lui dire de démarrer non plus un Webrick, mais un Mongrel, et plus précisément 4 instances de Mongrel.

Les lignes

SERVERTYPE=webrick

PUPPETMASTERS=1

PORT=8140

deviennent

SERVERTYPE=mongrel

PUPPETMASTERS=4

PORT=18140

Petite explication pour la dernière ligne : Puppet écoute par défaut sur le port 8140, et c'est sur ce port que les clients essaieront de le joindre. La modification fait écouter les 4 instances de Mongrel sur les ports 18140 à 18143. L'incrémentation est bien entendu liée à la valeur de PUPPETMASTERS précisée juste au-dessus.

Relançons le puppetmaster et examinons les processus qui tournent :

# ps ax | grep puppetmaster

2835 ? Ssl 0:03 ruby /usr/sbin/puppetmasterd --servertype=mongrel –masterport=18140 […]
 2858 ? Ssl 0:04 ruby /usr/sbin/puppetmasterd --servertype=mongrel --masterport=18141 […]
2880 ? Ssl 0:01 ruby /usr/sbin/puppetmasterd --servertype=mongrel --masterport=18142 […]
 2902 ? Ssl 0:03 ruby /usr/sbin/puppetmasterd --servertype=mongrel --masterport=18143 [...]

Dans cette configuration, nous disposons de 4 instances pouvant servir des clients, mais qui ne tournent pas sur le bon port. Il ne tient qu'à nous de faire en sorte que le port 8140 soit la porte d'entrée vers des petits processus fraîchement lancés.

Pour cela, c'est nginx qui va entrer en jeu. Il va écouter sur le port 8140 et dispatcher les requêtes à nos différentes instances Mongrel.

Voici le nginx.conf, les explications juste ensuite :

user root;

worker_processes 5;

error_log /var/log/nginx/error-puppet.log;

pid        /var/run/nginx.pid;

events {

    worker_connections 1024;

}

http {

    default_type application/octet-stream;

access_log /var/log/nginx/access.log;

    sendfile on;

    tcp_nopush     on;

    keepalive_timeout 65;

    tcp_nodelay        on;

    ssl                     on;

    ssl_certificate         /var/lib/puppet/ssl/certs/glmf-master1.lan.pem;

    ssl_certificate_key     /var/lib/puppet/ssl/private_keys/glmf-master1.lan.pem;

   ssl_client_certificate /var/lib/puppet/ssl/ca/ca_crt.pem;

    ssl_ciphers             SSLv2:-LOW:-EXPORT:RC4+RSA;

    ssl_session_cache       shared:SSL:8m;

    ssl_session_timeout     5m;

    upstream puppet-production {

        server 127.0.0.1:18140;

        server 127.0.0.1:18141;

        server 127.0.0.1:18142;

        server 127.0.0.1:18143;

    }

    server {

        listen                  8140;

        ssl_verify_client       on;

        root                    /var/empty;

        access_log              /var/log/nginx/access-main.log;

        location / {

            proxy_pass          http://puppet-production;

            proxy_redirect      off;

            proxy_set_header    Host             $host;

            proxy_set_header    X-Real-IP        $remote_addr;

            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header    X-Client-Verify SUCCESS;

            proxy_set_header    X-SSL-Subject    $ssl_client_s_dn;

            proxy_set_header    X-SSL-Issuer     $ssl_client_i_dn;

            proxy_read_timeout 65;

        }

    }

    server {

        listen                  8141;

        ssl_verify_client       off;

root /var/empty;

access_log /var/log/nginx/access-certs.log;

        location / {

            proxy_pass         http://puppet-production;

            proxy_redirect     off;

            proxy_set_header   Host             $host;

            proxy_set_header   X-Real-IP        $remote_addr;

            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header   X-Client-Verify FAILURE;

            proxy_set_header   X-SSL-Subject    $ssl_client_s_dn;

            proxy_set_header   X-SSL-Issuer     $ssl_client_i_dn;

            proxy_read_timeout 65;

}

    }

}

Puppet est basé sur des certificats SSL, il faut donc dire à nginx où se trouvent tous ces certificats ainsi que les options qui leur sont liées.

La partie « upstream » définit les instances locales dont nous disposons, rien de sorcier. C'est en quelque sorte la destination finale des requêtes qui vont nous parvenir.

La première partie « server » écoute sur le port 8140 et sert de proxy vers la partie « upstream » définie juste au-dessus. C'est par là que quasiment toutes les requêtes passeront, à l'exception de la première connexion d'un hôte. En effet, c'est à ce seul effet qu'une section écoutant sur le port 8141 est créée. Avant la première connexion, le client et le serveur n'ont pas connaissance l'un de l'autre et plus particulièrement au niveau de leurs certificats. C'est pourquoi la directive ssl_verify_client est mise à « off ». Cela permettra à nos deux parties de se faire les politesses d'usage nécessaires au bon fonctionnement de la chaîne.

Il faudra aussi ajouter, dans la section « puppetmasterd » de puppet.conf, la ligne suivante :

ssl_client_header=HTTP_X_SSL_SUBJECT

Si vous n'avez pas de plugins disponibles, modifiez la configuration du client pour désactiver le « pluginsync » et éviter une erreur sur la CLI.

Le premier appel à Puppet ressemblera alors à :

glmf-client1 # puppetd -vt –server=glmf-master1.lan –ca_port=8141

Le manifest sur le serveur est des plus simples pour cette démonstration :

class tmp::file1 {

        file { "/tmp/file1":

ensure => present,

                content => "test numero 1"

        }

}

node "glmf-client1.lan" {

        include tmp::file1

}

Un petit tail -f sur /var/log/nginx/access-main.log permet de voir que les requêtes passent bien à travers le nginx avant d'arriver aux puppetmasters.

A noter que j'utilise la signature automatique des certificats, ce qui me permet de ne pas avoir à faire de puppetca –sign à chaque nouveau client. Certains n'aiment pas, mais mon master n'étant pas accessible publiquement, cette pratique n'est guère gênante. Si vous ne l'utilisez pas, vous devrez signer le certificat avec la commande puppetca après la première demande.

Nous avons désormais un puppetmaster capable de recevoir plus de demandes, mais dans le monde exigeant d'aujourd'hui où les SLA sont souvent aussi élevées que des bonus de traders, on ne peut se permettre de perdre son master. Deux puppetmasters, c'est mieux qu'un seul et nous allons tout de suite lui trouver un petit frère.

1.1 Multiplication des masters puppet

Pour que les serveurs aient la même configuration, nous allons pour cet article aller au plus simple et utiliser NFS pour le partage des fichiers des masters. Dans un environnement de production exigeant des SLA fortes, il est plus conseillé de recourir à des systèmes de fichiers clusterisés, comme ocfs ou glusterfs, qui ne constitueront pas un « single point of failure ».

Sur la machine glmf-infra1, nous installons et paramétrons le nécessaire :

# apt-get install nfs-kernel-server
# mkdir -p /opt/puppet/etc
# mkdir -p /opt/puppet/lib
# cat > /etc/exports
/opt/puppet/etc glmf-master*.lan(rw,no_root_squash)
/opt/puppet/lib glmf-master*.lan(rw,no_root_squash)
^D
# /etc/init.d/nfs-kernel-server restart

Les répertoires partagés seront donc respectivement montés dans /etc/puppet et dans /var/lib/puppet. Il s'agit alors de « migrer » les données présentes sur le master dans le NFS, puis de monter ces deux répertoires sur chacun des masters.

Sur glmf-master1 :

# mkdir /mnt/etc
# mkdir /mnt/lib
# mount -t nfs glmf-infra1:/opt/puppet/etc /mnt/etc
# mount -t nfs glmf-infra1:/opt/puppet/lib /mnt/lib
# cd /etc/puppet
# cp -a * /mnt/etc
# cd /var/lib/puppet
# cp -a * /mnt/lib
# umount /mnt/etc
# umount /mnt/lib
# mount -t nfs glmf-infra1:/opt/puppet/etc /etc/puppet
# mount -t nfs glmf-infra1:/opt/puppet/lib /var/lib/puppet

On aura donc sur les deux masters les entrées suivantes dans le fichier /etc/fstab :

glmf-infra1:/opt/puppet/lib    /var/lib/puppet   nfs     noatime,nodev,rw,tcp,nolock     0       0

glmf-infra1:/opt/puppet/etc   /etc/puppet        nfs     noatime,nodev,rw,tcp,nolock     0       0

Par défaut, Mongrel n'écoute que sur l'adresse locale, nous allons donc lui dire de tendre l'oreille vers le monde extérieur par le biais de /etc/default/puppetmaster.

# Startup options

DAEMON_OPTS="—bindaddress=0.0.0.0"

Nos deux puppetmasters ont maintenant la même configuration et répondent tous les deux au nom « glmf-master1 » grâce à la directive certname.

Nous devons aussi modifier la configuration de nginx pour qu'il prenne en compte les deux puppetmasters et qu'il répartisse les requêtes entre eux. On transforme donc la partie suivante :

    upstream puppet-production {

        server 127.0.0.1:18140;

        server 127.0.0.1:18141;

        server 127.0.0.1:18142;

        server 127.0.0.1:18143;

    }

en

    upstream puppet-production {

server 192.168.43.40:18140;

        server 192.168.43.40:18141;

        server 192.168.43.40:18142;

        server 192.168.43.40:18143;

        server 192.168.43.41:18140;

        server 192.168.43.41:18141;

        server 192.168.43.41:18142;

        server 192.168.43.41:18143;

    }

Et on redémarre le démon. Pour vérifier que tout fonctionne comme prévu, il nous suffit de lancer plusieurs requêtes vers le master afin d'être certains de toucher les deux lors de la répartition des requêtes. On suit donc le /var/log/daemon.log des 2 masters :

sur glmf-master1 :

glmf-master1 puppetmasterd[13068]: Compiled catalog for glmf-client1.lan in 1.17 seconds

sur glmf-master2 :

glmf-master2 puppetmasterd[9366]: Compiled catalog for glmf-client1.lan in 0.01 seconds

et le client, pour qui tout est transparent :

glmf-client1:~# puppetd -vt –server=glmf-master1.lan
info: Retrieving plugin
info: Caching catalog for glmf-client1.lan
info: Applying configuration version '1265274040'
notice: Finished catalog run in 0.01 seconds

Nous avons donc deux masters qui servent un nginx positionné en frontal, ce qui nous permet d'absorber un grand nombre de requêtes. Néanmoins, ce type d'architecture n'est pas souhaitable pour un environnement de production en l'état car, comme je l'ai précisé plus haut, le montage NFS est un point « bloquant » en cas de panne. De plus, il faudrait doubler le nginx qui se trouve en frontal à l'aide, par exemple, d'un heartbeat tout ce qu'il y a de plus classique avec une simple IP virtuelle. Ceci est laissé comme exercice au lecteur ;-)

2. Ressources exportées & configurations stockées

Avec la version 0.25, une nouvelle fonctionnalité est apparue, il s'agit des storedconfigs qu'on pourrait traduire par « configurations stockées ». Ce mécanisme permet la collecte de données sur les clients Puppet, en vue de leur réutilisation ultérieure. On peut alors générer des configurations à partir de ces données, mais aussi les redéployer. En effet, ces données peuvent être des fichiers ! Avant d'entrer plus dans le détail, commençons par paramétrer tout ce petit monde.

2.1 Paramétrage

J'ai retenu le backend MySQL pour utiliser les storedconfigs. Il est possible d'utiliser aussi SQLite et PostgreSQL.

On installe donc MySQL Server :

glmf-infra1# apt-get install mysql-server-5.0

Il faut maintenant créer la base ainsi qu'un utilisateur pour Puppet :

# mysql -u root -p
[tip tip tip password]
mysql> create database puppet;
mysql> grant all privileges on puppet.* to puppet@'%' identified by 'l3f1nn0i5';

Puisque nous ne sommes pas en local, il faut également penser à commenter la ligne dans /etc/mysql/my.cnf,qui contient l'instruction suivante :

bind-address           = 127.0.0.1

Et sur les masters, on ajoute à la section [puppetmasterd] les lignes suivantes :

storeconfigs=true

dbadapter=mysql

dbname=puppet

dbuser=puppet

dbpassword= l3f1nn0i5

dbserver=glmf-infra1.lan

Et alors, au premier client qui se connecte, on obtient dans les logs du puppetmaster qui aura pris la requête en charge :

glmf-master2 puppetmasterd[9829]: Initialized database in 0.15 seconds

2.2 Utilisation dans les manifests

En guise d'introduction, nous allons exporter un fichier contenant des infos concernant le nœud affecté, que facter nous fournit sous la forme de variables, dans un fichier. Les clients 1 et 2 de notre architecture vont exporter le fichier tandis que le troisième va collecter ces fichiers.

# La classe qui exporte des données

class er::exportation {

        @@file { "/tmp/export_$fqdn":

content => "Je suis exporté par $fqdn, qui est un systeme $operatingsystem et est disponible à l'IP $ipaddress\n",

                tag => "test1"

        }

}

# La classe qui sert à collecter les fichiers

class er::importation {

        File <<| tag == "test1" |>>

}

# La configuration des nodes

node "glmf-client1.lan" {

        include tmp::file1

        include er::exportation

}

node "glmf-client2.lan" {

        include tmp::file1

        include er::exportation

}

node "glmf-client3.lan" {

        include tmp::file1

        include er::importation

}

Aucun message particulier n'est affiché par les nœuds qui exportent des données, tandis qu'on voit bien sur le nœud qui importe la création des fichiers :

glmf-client3:~# puppetd -vt –server=glmf-master1.lan
info: Retrieving plugin
info: Caching catalog for glmf-client3.lan
info: Applying configuration version '1265303876'
info: Filebucket[/var/lib/puppet/clientbucket]: Adding /tmp/export_glmf-client2.lan(9bd80d5d8a285490dec76903e3c74f8d)
[.....]
info: Filebucket[/var/lib/puppet/clientbucket]: Adding /tmp/export_glmf-client1.lan(b57d720f4d1360db654ba99c7c34c479)
[..…]
notice: Finished catalog run in 0.02 seconds

3. Puppet & VCS : un duo efficace

3.1 Travailler à plusieurs & tracer les changements

Lorsque l'on travaille seul, on est tenté de se dire : « je sais ce que je fais, personne ne viendra mettre le bazar dans mes manifests ». Dans le cadre d'une équipe d'admins, cette réflexion n'est tout de suite plus valable. C'est pourquoi la plupart des puppet-eurs utilisent un VCS en combinaison avec leur outil de gestion de configuration préféré. Que ce soit CVS, Subversion ou le très hype git - pour lequel vous trouverez d'excellentes introductions dans les archives de GLMF que vous stockez ou sur unixgarden.com - le principe reste le même que lorsque vous codez sur un projet en commun : tracer les changements, vérifier les conflitset et au besoin, les gérer. Cela permet aussi d'avoir un historique des modifications afin d'identifier l'origine d'un problème et au besoin de revenir rapidement à un état « connu » et fonctionnel. Dans le cadre de mon travail, c'est Subversion le VCS qui a été choisi et c'est donc sur ce dernier que je vais me baser pour les exemples qui vont suivre.

3.2 All your files are belong to us

Afin de garder trace de tout changement dans Puppet, que ce soit au niveau des manifests, des fichiers servis ou des templates, il est impératif que toutes les modifications soient faites au niveau du dépôt et jamais dans sa copie en production.

Nous allons donc faire un checkout de notre dépôt puppet dans /etc/puppet/ et pouvoir ainsi le mettre à jour par un simple update :

svn co svn://infra-glmf/puppet-article /etc/puppet/
[,,,]
A etc/puppet/fileserver.conf
A etc/puppet/autosign.conf
A etc/puppet/templates

Note

Cette méthode est celle que j'emploie, mais je suis loin d'être un gourou de la gestion de version. Certains d'entre vous auront sûrement une méthode différente et je serais heureux qu'ils me la communiquent.

3.3 Scripts de {pre|post} commit

Même les sysadmins sont humains, du moins en grande partie, et ils font eux aussi des erreurs, de frappe le plus souvent. Il faut donc pallier aux problèmes liés aux erreurs et éviter de mettre en production une version qui ne passe pas le stade de la compilation des manifests. Ceci est possible et facilement réalisable grâce aux scripts de pre-commit qui vont vérifier que les fichiers commités sont au moins syntaxiquement corrects. Les scripts qui remplissent ces fonctions dans Subversion sont appelés des « hooks ». Ils se situent donc dans le répertoire du même nom. Ici, c'est le script de pre-commit, donc avant l'envoi des modifications dans le dépôt, qui nous intéresse.

Voici le script qui se trouve dans <racineSVN>/puppet-article/hooks/pre-commit. Il est adapté de celui du wiki Puppet. A noter qu'un petit bug existe en version 0.24.x et a été corrigé dans les versions 0.25.

#!/usr/local/bin/bash

# SVN pre-commit hook to check Puppet syntax for .pp files

PATH="/usr/bin:/bin:/usr/local/bin"

REPOS="$1"

TXN="$2"

tmpfile=`mktemp -t tmp_puppet`

export HOME=/

SVNLOOK=/usr/local/bin/svnlook

PUPPET=/usr/local/bin/puppet

$SVNLOOK changed -t "$TXN" "$REPOS" | awk '{print $2}' | grep '\.pp$' | while read line

do

  $SVNLOOK cat -t "$TXN" "$REPOS" "$line" > $tmpfile

  

  $PUPPET --color=false --confdir=/tmp --vardir=/tmp --parseonly --ignoreimport $tmpfile

  if [ $? -ne 0 ]

  then

    echo "Puppet syntax error in $line."

    exit 1

   fi

done

rm -f $tmpfile

Exemple de commit réussi :

$ svn commit -m "test pre commit"
Envoi modules/tests/manifests/init.pp
Transmission des données .

Exemple de commit rejeté :

$ svn commit -m "test pre commit"
Envoi modules/tests/manifests/init.pp
Transmission des données .svn: Échec de la propagation (commit), détails :
svn: Commit blocked by pre-commit hook (exit code 1) with output:

Il est également possible de mettre en place un script de post-commit, donc une fois que les modifications sont entrées de manière effective dans le dépôt. Son usage le plus commun est l'envoi par mail de la sortie d'un diff entre l'ancienne et la nouvelle version d'un fichier. Cette pratique permet de tenir au courant chacun des sysadmins qui travaille sur le dépôt de la modification d'un fichier.

Le script de post-commit, quant à lui, est plus simple :

#!/bin/sh

REPOS="$1"

REV="$2"

/<path_to_svndir>/puppet/hooks/commit-email.pl --from subversion@macorp.net --diff y "$REPOS" "$REV" puppeters@macorp.net

3.4 Update automatique VS mise en production contrôlée

Un autre point important de l'utilisation d'un VCS pour la gestion de Puppet est la façon de mettre en production les modifications. Certains, que nous ne nommerons pas, font un update régulier de leur dépôt en cron. Ainsi, les modifications une fois commitées arrivent directement en production. D'autres choisissent de faire l'update à la main et ainsi contrôler le moment précis où les modifications seront effectives. J'aurai personnellement tendance à conseiller la seconde solution, parce que des modifications non voulues ou incorrectes sur des dizaines (ou même plus) de machines peuvent avoir des conséquences relativement fâcheuses.

4. Ajouter des facts & des fonctions

Un des points forts de Puppet est son extensibilité. Il est possible de créer des extensions pour presque tout ce qui le compose : des facts, des fonctions, des types, des providers. Le seul prérequis est la connaissance du langage Ruby. De plus, de nombreux exemples sont disponibles et l'aide ne manque pas, notamment via le biais du canal IRC #puppet sur le réseau Freenode.

Quelques prérequis :

- le système de pluginsync doit être activé, c'est de cette façon que sont distribuées les fonctions et les facts ;

- les éléments personnalisés sont placés dans le module custom, sous les chemins suivants :

- <moduledir>/custom/plugins/facter (pour les facts personnalisés) ;

- <moduledir>/custom/plugins/puppet/parser/functions (pour les fonctions personnalisées).

4.1 Ecrire un fact personnalisé

On a parfois besoin de connaître des infos que facter ne propose pas d'emblée et qui sont spécifiques à notre architecture. Pour cela, rien de bien compliqué, quelques lignes de Ruby suffisent la plupart du temps. Comme petit exercice de démonstration, je vais utiliser un fact personnalisé que j'utilise dans mon environnement de production. J'utilise des machines Alix (de chez PC Engines) sous OpenBSD en guise de petits serveurs DNS/DHCP pour de petits réseaux, et elles ont quelques particularités de configuration. Afin de les appliquer de façon sélective :

Facter.add("alix") do

 confine :operatingsystem => :openbsd

 

 result = "classic"

 setcode do

  if Facter.value("hardwareisa") =~ /Geode\(TM\)/i then

   result = "alix"

  end

  # Returns the value

result

 end

end

Petite analyse : il est possible de restreindre la « portée d'éxécution » d'un fact avec la directive confine. Ici, on ne le déclenchera que si la valeur du fact operatingsystem est égale à openbsd. Sur les machines qui ne sont pas des Alix, elle renverra la valeur classic et dans le cas contraire, elle renverra alix. Le test porte sur la valeur d'un autre fact. A noter que ceci fonctionne parce que les Alix sont mes seules machines à base de Geode.

La création d'un fact personnalisé est bien structurée, il faut respecter le fait de renvoyer la valeur voulue pour le fact dans le bloc setcode. De même, c'est la première valeur renvoyée qui prime.

Il est possible de tester un fact personnalisé de façon autonome une fois déployé avec le switch -p de facter.

machinenormale $ uname -s
OpenBSD
machinenormale # facter -p alix
classic

ou :

alix-dhcp-1 $ uname -s
OpenBSD
alix-dhcp-1 # facter -p alix
alix

4.2 Ecrire une fonction personnalisée

De même que pour les facts, la nécessité peut se faire ressentir au niveau des fonctions originales de Puppet. Celles disponibles ne font pas ce dont nous avons besoin ? Qu'à cela ne tienne, écrivons la notre. Supposons que nous avons besoin de passer une variable tout en casse minuscule et de lui ajouter un chiffre aléatoire au bout.

module Puppet::Parser::Functions

 newfunction(:rnd_lowercase, :type => :rvalue) do |args|

  args[0].downcase+rand(1000).to_s

end

end

elle sera donc appelée dans un manifest de la façon suivante, par exemple :

class tmp::rnd_lowercase {

        file { "/tmp/rnd_file":

                ensure => present,

                content => rnd_lowercase("bla")

}

}

A noter une particularité, que certains considèreront comme un défaut voire un bug : lorsque l'on développe des fonctions supplémentaires pour Puppet, ces dernières sont chargées par le master au moment d'être utilisées et restent ensuite en mémoire. Ainsi, quand on utilise plusieurs puppetmasters et que les fonctions sont en cours de développement, il est possible que 2 masters n'aient pas la même version de la fonction en mémoire, ce qui peut rendre le déboguage quelque peu casse-tête avec des résultats qui ressemblent à « un coup ça marche, un coup ça ne marche pas ». Il faut donc redémarrer les masters entre chaque modification des fonctions. De toute façon, vous avez une architecture de test dans des machines virtuelles, non ?

5. Séparer ses données de sa logique

Une question qui revient souvent est « comment séparer mes données de mes manifests ? ». En effet, il est peu pratique, et pas bon niveau sécurité, d'avoir une clé privée SSH, un mot de passe ou toute autre donnée métier un tant soit peu sensible dans un manifest. Heureusement, il existe une solution simple et flexible, disponible sous la forme d'une fonction nommée extlookup(). Elle a été écrite par R. I. Pienaar et a été intégrée ensuite au répertoire « ext » de Puppet au vu de son succès. Pour la déployer, il faut la placer dans le dossier des fonctions personnalisées, tout simplement.

Cette fonction recherche ses informations dans des fichiers CSV selon un ordre que nous pouvons définir à l'aide d'une variable. Avec ce système, on obtient une excellente souplesse dans la récupération des données. Un exemple vaut mille mots :

$extlookup_datadir = "/etc/puppet/extdata/"

$extlookup_precedence = ["%{fqdn}", "location_%{site}", "domain_%{domain}", "common"]

# On veut récupérer le nom du serveur de log pour le domaine de notre domaine

$logserver=extlookup("logserver")

Et pour cela, le fichier /etc/puppet/extdata/domain_lan.csv doit contenir la ligne :

logserver,logophage.lan

Si on veut utiliser un serveur de logs particulier pour la machine glmf-client1, on ajoute dans le répertoire extdata le fichier glmf-client1.lan.csv avec une valeur différente pour le champ logserver. De même, lorsque nos machines sont géographiquement dispersées, il est fréquent et souvent intéressant de définir une variable précisant le site. Personnellement, je l'appelle site et m'en sers pour faire pointer des services vers un serveur qui se trouve être au plus prêt.

La fonction extlookup() évalue la variable extlookup_precedence dans l'ordre et renvoie la première valeur trouvée pour le champ demandé, et une erreur si le champ n'est défini dans aucun des fichiers qui la composent et se trouvent dans le dossier pointé par extlookup_datadir. Ainsi, dans l'exemple ci-dessus, on commence par faire la recherche sur le FQDN de la machine (variable facter fqdn), puis par rapport à la valeur de la variable site, ensuite par rapport au domaine de la machine (variable facter domain) et enfin, un fichier censé contenir des valeurs « par défaut » pour toutes les machines de l'infrastructure.

Conclusion

Puppet est un logiciel qui grandit vite, s'adapte rapidement aux demandes des utilisateurs et se révèle être très flexible. Nous venons de voir qu'il est possible de déployer des serveurs supplémentaires facilement et de façon tout à fait transparente pour les clients. Il est aussi facilement extensible, les connaissances requises en Ruby sont très basiques et n'importe quel sysadmin peut les acquérir dans les livres d'introduction au langage.

A bientôt pour de nouvelles aventures !