Utiliser Puppet avec un ENC (External Node Classifier)
-
Par défaut, la configuration des scénarios à jouer sur les serveurs clients de Puppet se fait au niveau du fichier “nodes.pp”. Cette solution atteint vite ses limites dès lors que vous avez de nombreux clients, paramètres, classes.
En effet, ce fichier devient vite assez dense rendant sa lecture difficile et la porte ouverte aux erreurs qui peuvent rapidement se transformer en actions non désirées sur vos clients ! De même, connaître d’un coup d’oeil l’ensemble des scénarios joués ainsi que leurs paramètres devient compliqué. Puppet propose d’autres solutions comme par exemple utiliser un LDAP ou un External Node Classifier (ENC), ce que nous allons voir dans cet article.
Je vais reprendre ici la définition d’un ENC que vous trouverez sur la documentation de Puppet tout en rajoutant des exemples précis sur l’utilisation de ressources de type “define” avec l’utilisation de tables de hachage en paramètre.
ENC ? Ce que vous devez savoir
Un ENC n’est rien d’autre qu’un programme appelé par le puppetmasterd au moment où un client se présente pour obtenir son catalogue. Puppet va alors appeler le programme défini avec pour paramètre le hostname (en fqdn). Ce programme peut être écrit dans le langage de votre choix. Dans votre ENC, vous pouvez utiliser le backend de votre choix comme source de donnée. La seule obligation est de fournir les informations en YAML. Il est également possible d’utiliser Puppet Dashboard ou The Foreman comme ENC.
Il y a cependant quelques limites importantes quand on utilise un ENC :
- Avec un ENC, vous ne pouvez que déclarer des classes (classiques et avec paramètre), définir des variables globales et définir un environnement (bien que visiblement toujours mal supporté à ce jour).
- Vous ne pouvez donc pas utiliser, en l’état, vos ressources de type “define” !
- Vous ne pouvez pas déclarer de relations entre vos différentes classes.
Le YAML produit par l’ENC
Je vais reprendre les exemples de Puppet en détaillant un peu. Imaginons 4 classes : common, puppet, dns, ntp.
Dans le “nodes.pp”, cela ressemblerait donc à :
node default { include common include puppet include dns include ntp }
Votre ENC produira lui le résultat suivant :
classes: common: puppet: dns: ntp:
Avec une classe paramétrée
Si vous utilisez une classe paramétrée (Parameterized Classes), on passe de :
class { 'ntp': ntpserver => ['0.pool.ntp.org','1.pool.ntp.org'] }
à :
classes: common: puppet: ntp: ntpserver: - 0.pool.ntp.org - 1.pool.ntp.org
Déclarer des variables globales
Il est possible de déclarer uniquement des variables globales (top scope variables). Dans votre “nodes.pp” ou “site.pp” :
$var1 = 1 $var2 = "test" $var3 = ["a", 1] $var4 = { 'ma_ref1' => { 'cle1' => 'valeur1', 'cle2' => 'valeur2' },\ 'ma_ref2' => { 'cle1' => 'valeur1', 'cle2' => 'valeur2' } }
Votre ENC affichera le YAML suivant :
parameters: var1: 1 var2: test var3: - a - 1 var4: ma_ref1: cle1: valeur1 cle2: valeur2 ma_ref2: cle1: valeur1 cle2: valeur2
Comment faire pour les ressources “define”
Par défaut, vous indiquez uniquement les classes associées à vos clients. Si vous avez dans votre “nodes.pp” une référence à une ressource de type “define”, vous ne pouvez plus l’utiliser directement avec votre ENC. Pour être clair, imaginons un module “users” et son manifest “create” :
# cat /etc/puppet/modules/users/manifests/create.pp define users::create($user, $path) { [...] }
L’utilisation se fait de cette façon dans le “nodes.pp” :
user::create { 'user1': user => 'user1', path => '/home/user1' } user::create { 'user2': user => 'user2', path => '/home/user2' }
Il n’y a pas de solution directe (à ce jour) pour reproduire cette fonctionnalité dans l’ENC. Vous ne pouvez pas définir les variables et appeler user::create comme on appelle une classe. Il faudra utiliser la fonction create_resources. Cette fonction est disponible nativement depuis la version 2.7 de Puppet. Pour les versions précédentes, il faudra passer par un module.
Grace à cette fonction, on va finalement utiliser une classe qui aura pour mission de construire dynamiquement les différents appels à user:: via create_resources.
Exemple :
Mon manifest create.pp reste identique. Il n’y a pas de modification à lui apporter. Il a toujours besoin de la variable $user et $path. On va créer une nouvelle classe manage :
# cat /etc/puppet/modules/user/manifests/manage.pp class user::manage ($user_list) inherits user { create_resources('user::create',$user_list) }
On peut maintenant appeler la classe user::manage dans l’ENC. Il faut déclarer la variable user_list nécessaire à la classe user::manage pour finalement fournir à user::create les variables user et path. Le YAML produit ressemblera à :
classes: user::manage: user_list: user1: user: user1 path: /home/user1 user2: user: user2 path: /home/user2
Un exemple en python
Un petit exemple d’un ENC basique en Python.
#!/usr/bin/env python import sys import re from yaml import dump host = sys.argv[1] # Les classes par defaut classes = [ 'common', 'puppet', 'dns', 'ntp' ] # Si le client Puppet est mon_host # on rajoute la classe user::create if host == "mon_host": classes.append('user::manage') # On transforme la liste des classes en dictionnaire param_classes = {} for cl in classes: param_classes[cl] = {} # Les classes avec parametres param_classes['ntp'] = { 'ntpserver': [ '0.pool.ntp.org', '1.pool.ntp.org' ] } # Dictionnaire des parametres parameters = {} # Des parametres precis pour mon_host et la classe user::manage if host == "mon_host": param_classes['user::manage']['user_list'] = { 'user1': { 'user':'user1','path':'/home/user1'}, 'user2': { 'user':'user2','path':'/home/user2'}, } output = { 'classes' : param_classes, 'parameters': parameters } print dump(output,default_flow_style=False);
On obtient finalement la sortie suivante :
# /usr/local/bin/test_enc.py mon_host classes: common: {} dns: {} ntp: ntpserver: - 0.pool.ntp.org - 1.pool.ntp.org puppet: {} user::manage: user_list: user1: path: /home/user1 user: user1 user2: path: /home/user2 user: user2 parameters: {}
Et pour un serveur différent de mon_host :
# /usr/local/bin/test_enc.py serveur1 classes: common: {} dns: {} ntp: ntpserver: - 0.pool.ntp.org - 1.pool.ntp.org puppet: {} parameters: {}
Vous pouvez également utiliser des classes classiques et des variables globales visibles, donc, au niveau de “parameters” dans votre ENC. Cela fonctionnera de la même façon mais l’inconvénient majeur de cette solution est que votre variable est exposée à toutes les classes. Tant que vous maîtrisez l’ensemble de vos modules et de vos classes, vous pouvez vous assurer de ne pas écraser cette variable. Dès lors que vous utilisez des modules/classes écrites par d’autres personnes, le risque devient beaucoup plus important. C’est une des raisons de l’apparition des Parameterized Classes que je vous encourage, ici, à utiliser.
Activer l’ENC
Il ne vous reste plus qu’à indiquer à Puppet d’utiliser votre ENC. Dans votre fichier de configuration /etc/puppet/puppet.conf, rajoutez :
[master] node_terminus = exec external_nodes = /usr/local/bin/test_enc.py
Relancez ensuite votre Puppet master.
C’est terminé ! Pour aller plus loin, vous pouvez par exemple fournir la liste des classes, des paramètres et les groupes de serveurs via une base de données alimentée par une IHM. L’extraction sera réalisée toujours par votre ENC du langage de votre choix et qui produira le YAML pour Puppet. La gestion de la configuration du serveur client deviendra aussi simple que de choisir au niveau de l’IHM une liste de classes et de paramètres.