Puppet

De UnixManiax
Aller à : navigation, rechercher


Présentation

Puppet est un logiciel libre qui assure la gestion de configuration d'un parc de serveurs de manière centralisée. Il fonctionne sur le mode clients/serveur. Les clients peuvent être des serveurs Unix (GNU/Linux, FreeBSD, OpenBSD, Solaris, AIX, HP-UX et MacOS X) ou Windows.

Le principe est le suivant : sur le serveur (puppetmaster), on défini un certain nombre de configurations qu'on va affecter à tel ou tel client. Une vérification va être lancée pour vérifier que les configurations affectées aux clients sont bien en place. Si ce n'est pas le cas, les clients vont récupérer les configurations qui les concernent et les appliquer. Cette vérification peut être automatique (toutes les x secondes) ou manuelle (depuis le client ou depuis le puppetmaster). Le client ne peut pas choisir les configurations qu'il va récupérer, tout est configuré sur le puppetmaster.

Une configuration peut être beaucoup de choses, par exemple : installer un serveur apache, installer un ou plusieurs package, ajouter/remplacer/supprimer des fichiers, démarrer/arrêter un service, créer un compte utilisateur, vérifier les droits d'un fichier, etc. Ainsi, on peut automatiser rapidement la création d'un compte utilisateur sur plusieurs centaines de serveurs, mettre en place un groupe de serveurs web ou s'assurer qu'un service est bien démarré sur l'ensemble d'un parc.

Puppet utilise un langage qui décrit l'action à faire, mais pas les commandes à lancer pour le faire. Par exemple, on va lui dire : "redémarre le service apache". C'est ensuite l'agent (propre à chaque système d'exploitation) qui va se débrouiller pour relancer le service. Ainsi, on n'a pas à se soucier de savoir si on est sur Windows ou sur Solaris (sauf cas particuliers). Ça permet d'avoir des configurations simples et rapides à mettre en œuvre.


Comparaison avec les concurrents

Puppet a deux principaux concurrents : Chef et CFEngine. Je n'ai pas utilisé Chef et CFEngine, donc cette comparaison ne se base que sur ce que j'ai trouvé comme informations sur les sites web des trois produits ainsi que sur les informations que j'ai pu trouver sur le net.

Commençons par CFEngine, le plus ancien des trois. Puppet et Chef sont écrits en ruby, alors que CFEngine est écrit en C. Du coup, CFEngine est beaucoup plus rapide et consomme beaucoup moins de CPU et de RAM surtout quand le nombre d'agents augmente. Cela dit, on trouve des exemples de parcs de plusieurs milliers d'agents puppet qui fonctionnent sans encombre. CFEngine semble aussi être plus puissant, par exemple il peut modifier le contenu d'un fichier, ce que Puppet ne peut pas faire. Par contre, sa syntaxe est plus complexe et plus longue à appréhender, surtout pour un non développeur. Les trois peuvent dialoguer avec des clients windows, mais CFengine, de par sa construction, nécessite d'avoir des scripts différents pour Windows, car il est plus proche du système d'exploitation et il faut presque passer les commandes au lieu d'un simple "relance le service". CFEngine semble donc plus puissant mais plus complexe.

Comme puppet, Chef est écrit en ruby. Il est est beaucoup plus proche de puppet que CFEngine au niveau de sa syntaxe et de sa consommation mémoire et cpu. Leur philosophie est proche (on dit ce qu'on veut faire sans entrer dans les détails, par exemple "relance le service"), mais puppet pousse encore plus loin cette philosophie. Ainsi, les fichiers chef sont de vrais fichiers ruby, alors que ceux de puppet sont une adaptation simplifiée de ruby. Donc chef permettrait (d'après les pro-chef qui connaissent le langage ruby) de pouvoir traiter plus facilement des cas complexes, et puppet serait un peu plus accessible avec une syntaxe légèrement plus simple. Autre différence, puppet est plus ancien et serait plus mûr que chef. Il aurait aussi une plus grande communauté d'utilisateurs et de développeurs, et aurait plus de documentation. Mais ça peut changer.

Bref, on pourrait résumer en disant que CFEngine est plus performant et puissant, mais plus complexe à administrer, en particulier si on gère des plateformes hétérogènes. A l'opposé, puppet est beaucoup plus facile d'accès, surtout quand on n'est pas un as du développement. Chef se situerait entre les deux, en étant beaucoup plus proche de puppet. Mais à ce que j'ai pu lire, les communautés des trois produits sont actives et les trois semblent bons. Il n'y a pas de maillon faible ou de maillon fort juste des produits légèrement différents, et c'est surtout votre besoin qui va définir votre choix.

Pour finir, il faut savoir que les trois ont une version payante et une version gratuite (bien sûr limitée). Et un argument important pour les sociétés qui ne sont pas prêtes à dépenser de l'argent pour ces outils : puppet est le seul des trois qui peut avoir un nombre illimité de clients Windows dans sa version gratuite. Bien sûr, si votre parc est uniquement composé de clients unix (chanceux !! ;-) ), vous vous moquez totalement de cette information... Dans mon cas, c'est ce qui a fait mon choix.


Installation

Serveur

Installer le package :

apt-get install puppetmaster

Dans /etc/puppet/puppet.conf, on peut ajouter des noms DNS en ajoutant une ligne "dns_alt_names=", puis les différentes valeurs séparées par des virgules. Il faut faire cette manipulation dès le début, car ces valeurs vont faire partie du certificat du serveur.

Normalement, le service puppetmaster est démarré (il démarre à l'installation du package). Vérifier que c'est bon :

service puppetmaster status

Sinon, le démarrer :

service puppetmaster start

Remarque : sur Redhat/CentOS il n'y a pas de package puppetmaster, il faut installer puppet-server. Mais le service s'appelle bien puppetmaster.

Client

  • Installer le package :
apt-get install puppet
  • Éditer /etc/puppet/puppet.conf. Ajouter les lignes suivantes dans le bloc [agent] (il faut parfois le créer).
[agent]
server = serveur.lan
report = true
certname = ubuntu-virt

Service ou pas service ?


Il faut maintenant savoir si on veut que le client fonctionne en mode service ou pas. Cela dépend de ce que vous voulez faire. Le mode service implique que l'agent puppet fonctionne en tâche de fond sur le client. Si on ne choisi pas le mode service, alors la récupération des configurations ne pourra se faire qu'à l'initiative du client, manuellement (puppet agent -t). Cette solution me parait peu intéressante, car il faut obligatoirement se connecter sur le client pour descendre une configuration or, à mon sens, un des intérêts de puppet est justement de pouvoir tout faire de manière centralisée. Mais il faut savoir que cette possibilité existe.


Si on choisi le mode service, on a encore deux façons de voir les choses. Soit on demande au service d'aller tout seul chercher les configurations à appliquer toutes les "x" secondes (par défaut 30 minutes), tout est donc entièrement automatisé, soit on déclenche manuellement depuis le puppetmaster (puppet kick nom-client). Cette dernière option permet de contrôler précisément à quel moment on déclenche la synchronisation. Attention toutefois avec cette dernière méthode : on doit préciser le nom du client et, si on a de nombreux clients, ça peut être long, à moins d'avoir une liste à jour et de faire une boucle, mais on perd un peu le côté automatique, sauf... Sauf si nos serveurs (puppetmaster et clients) sont connectés à un LDAP. Dans ce cas, on peut mettre à jour tout le monde d'un coup (puppet kick --all) ou mettre à jour seulement une classe de serveurs (puppet kick --class nom_classe).


Si vous choisissez le mode service, il faut ajouter les étapes suivantes à l'installation :

  • Éditer /etc/default/puppet et remplacer START=no par START=yes si vous voulez que le service démarre automatiquement au boot.
  • Uniquement si vous voulez un mode service non automatique : Éditer /etc/default/puppet et ajouter --no-client à la variable DAEMON_OPTS :
DAEMON_OPTS="--no-client"
  • Dans la section [agent] de /etc/puppet/puppet.conf, ajouer la ligne
listen = true
  • Toujours dans la section [agent] de /etc/puppet/puppet.conf, on peut ajouer la ligne suivante, qui indique le temps en secondes entre deux déclanchements :
runinterval = 300  # 5 minutes (default is 30 minutes)
  • Démarrer le service :
service puppet start

Remarque : avant de vous déconnecter du client, lisez la partie suivante sur l'échange des clés. La méthode n°2 contient quelques modifs facultatives qu'on peut intégrer tout de suite. Ça peut nous gagner beaucoup de temps si on a de nombreux clients à configurer.

Échanger les clés

L'échange des clés cryptées est nécessaire pour que le client et le serveur puissent dialoguer. On a deux solutions pour faire cet échange. La première est un tout petit peu plus simple ; on la retrouve dans la plupart des tutoriels. La deuxième permet de tout faire depuis le serveur maitre, mais à condition d'avoir ajouté ce qu'il faut dans la configuration du client.

La deuxième méthode me parait plus intéressante, car les quelques petites modifications qu'on va mettre en place sur le client, vont permettre, par la suite, de pouvoir contrôler l'agent du client directement depuis le serveur. Mais si vous choisissez ou avez déjà fait la première méthode, il suffira simplement de modifier la config du client après, ce qui ne pose aucun soucis.

Avant d'aller plus loin

Avant de commencer, si vous avez un firewall, vérifiez que le serveur peut atteindre le port 8139/tcp des clients et que les clients peuvent atteindre le port 8140/tcp du serveur.

Méthode n°1

Le client est à l'initiative de la demande.

Sur le client :

puppet agent --test

Sur le serveur :

  • Le serveur voit la demande avec la commande suivante, qui liste les demandes en attente :
puppet cert list
 "client1.lan" (XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX)
  • Le serveur valide l'échange de clés avec le client :
puppet cert sign client1.lan
  • On peut également valider toutes les demandes d'un coup :
puppet cert sign --all

Méthode n°2

On procède à l'échange de clés depuis le serveur.

Prérequis sur le client :

  • dans /etc/puppet/puppet.conf, dans le block [agent], ajouter la ligne :
listen=true
  • créer un fichier /etc/puppet/auth.conf avec les lignes suivantes :
path /run
method save
allow server.lan

La ligne allow doit contenir soit l'adresse ip du serveur, soit son nom réseau complet (son FQDN, son hostname seul ne fonctionnera pas).

Pour que ces modifications soient prises en compte, il faut redémarrer le service :

service puppet restart


Sur le serveur :

puppet cert sign client1.lan

What else?

Pour voir tous les clients validés, taper :

puppet cert list --all
+ "client1.lan"     (XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX)
+ "client2.lan"     (XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX)
+ "client3.lan"     (XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX)


Arborescence / fichiers de configuration

Sur le client

Nous avons déjà vu tous les fichiers intéressant sur les clients. On va juste les rappeler.

  • /etc/puppet/ : répertoire principal des fichiers de conf ;
  • /etc/puppet/puppet.conf : le fichier de configuration principal, où on déclare le nom du master ;
  • /etc/puppet/auth.conf : pour autoriser le master à lancer des actions sur le client ;
  • /etc/default/puppet : à modifier seulement si l'agent est en mode service et si on veut qu'il démarre automatiquement au boot.

Sur le serveur

Tous les fichiers intéressants sont dans le répertoire principal : /etc/puppet/. Voyons les fichiers intéressants à l'intérieur.

  • auth.conf : fichier dans lequel on va pouvoir limiter les droits d'accès à certaines ressources (certificats, partage de fichiers, etc). Par défaut, quasiment rien n'est protégé.
  • fileserver.conf : fichier dans lequel on va définir un partage de fichiers pour les agents. A noter qu'on peut également définir des partages de fichiers dans les modules et que dans ce cas on n'a pas besoin de passer par ce fichier.
  • puppet.conf : configuration générale de puppet (nom du master, emplacment des répertoires, etc).
  • manifests/ : répertoire dans lequel on va placer les manifests (fichiers *.pp qui sont les fichiers qui contiennent du langage puppet).
    • site.pp : c'est LE fichier principal pour tout ce qui concerne le dialogue avec les agents. D'ailleurs, c'est le seul qui va être lu. On peut tout mettre ici, mais ça serait très lourd à lire et à maintenir, donc on va au contraire s'efforcer d'en mettre le moins possible. On va mettre tout dans d'autres fichiers *.pp qu'on va charger depuis site.pp à l'aide de la commande import pour avoir l'architecture la plus claire, propre et maintenable possible.
    • nodes.pp : ce fichier est facultatif et doit donc être appelé depuis site.pp (avec une ligne import "node"). On le retrouve dans tous les tutoriels et les documentations ou il sert à déclarer les noeuds, c'est à dire les serveurs qui possèdent des agents.
  • modules/<nom_module>/ : C'est ici qu'on va mettre tous les modules qu'on va appliquer aux clients. Par exemple, un module pour installer un serveur apache, configurer samba, etc. On ne mettra rien directement dans ce répertoire, on va utiliser les deux sous-répertoires ci-dessous.
    • modules/<nom_module>/manifests/ : c'est là qu'on va mettre le(s) fichier(s) *.pp pour le module.
    • modules/<nom_module>/files/ : ici on va mettre les éventuels fichiers à déposer sur les clients. Ce répertoire est facultatif.

Précisions sur les modules :

  • on peut les mettre dans un autre répertoire. Mais dans ce cas il faut indiquer à puppet ou chercher, en ajouter une variable "modulepath = ..." dans puppet.conf
  • le nom du répertoire du module est obligatoirement le même que le nom du module
  • le sous-répertoire "manifests" est obligatoire et doit contenir un fichier init.pp (on peut avoir d'autres fichiers *.pp)
  • ce fichier init.pp doit obligatoirement contenir une classe qui porte le nom du module

Le langage

Pour ceux qui connaissent, le langage est proche du ruby. Je ne vais pas entrer dans les détails, je pense que la meilleure façon d'apprendre est de pratiquer pour bien intégrer au fur et à mesure et bien comprendre ce qu'on fait. Je vous invite donc à lire le lien qui suit, qui montre des exemples intéressants, ou à passer directement à la suite pour mettre en pratique.

Ce site montre un exemple de manifest et détaille sa structure avec des liens pour avoir des détails sur chaque partie : http://docs.puppetlabs.com/puppet/3/reference/lang_visual_index.html.


Mise en pratique

1er exemple : déposer un fichier

Maintenant que notre puppetmaster communique avec nos clients et qu'on connait les principaux fichiers, on va pouvoir passer à la pratique ! :-)

Sur les clients, nous n'avons plus rien à faire, sauf à chaque essai, vérifier que ça a bien fonctionné. Et sauf pour ceux qui n'ont pas activé le service et qui devront appliquer les configurations avec l'une des deux commandes suivantes (qui sont strictement identiques) :

puppet agent -t
puppet agent --test

Voyons maintenant du côté du master. Commençons par le fichier /etc/puppet/manifests/site.pp. Pour rappel, c'est le fichier d'entrée en ce qui concerne la mise en place de configurations sur les clients. Notre but est qu'il reste le plus concis possible. Voici un exemple.

# site.pp
# Fichier d'entrée en ce qui concerne la mise en place de configurations sur les clients.

# Les deux lignes suivantes servent a la configuration du filebucket.
# Le filebucket est un service permettant d’avoir une copie de sauvegarde des fichiers modifiés sur les clients.
filebucket { 'main': server => 'mint-virt.lan' }
File { backup => 'main' }

import "node.pp"

La valeur server => 'mint-virt.lan' contient le fqdn du serveur puppet (son nom complet sur le réseau). Le mien s'appelle mint-virt.lan, car c'est une machine virtuelle sous LinuxMint. La ligne import "node.pp" sert à importer le fichier node.pp qu'on va voir maintenant. A noter qu'on peut mettre uniquement import "node", le .pp étant sous-entendu.

Nous allons maintenant créer un fichier /etc/puppet/manifests/node.pp qui va contenir des informations sur nos nœuds (le clients).

node 'ubuntu-virt.lan' {
   include test
}

La première ligne contient le fqdn du nœud. Le mien étant une machine virtuelle Ubuntu, je l'ai appelé ubuntu-virt.

La ligne include test sert à inclure la classe test que nous allons tout de suite créer dans /etc/puppet/modules/.

Créons les répertoires dont on a besoin :

mkdir -p /etc/puppet/modules/test/manifests
mkdir /etc/puppet/modules/test/files

Dans le répertoire manifests, on va créer un ou plusieurs fichiers *.pp qui vont décrire les actions à faire. Dans files, on va mettre les fichiers qui devront être déposés sur les nœuds.

Pour notre premier exemple, on va déposer un fichier sur notre nœud. Commençons par créer un fichier tout bête :

echo "Bisous mon lapinou." > /etc/puppet/modules/test/files/puppet_test.txt

Maintenant on va créer un fichier /etc/puppet/modules/test/manifests/init.pp qui décrit les actions :

class test {
    file { "/tmp/puppet_test.txt":
        owner => root,
        group => root,
        mode => 644,
        source => "puppet:///test/puppet_test.txt"
    }
}

Le contenu est très parlant ; c'est une des grandes forces de puppet. On explique que, sur le client, on va créer un fichier /tmp/puppet_test.txt appartenant à root:root avec les droits 644. La ligne source indique ou prendre le fichier. Le chemin indiqué et relatif par rapport à /etc/puppet/modules.

L'action alors de comparer le fichier source au fichier cible sur le nœud (comparaison MD5). Si la cible n'existe pas ou si elle est différente de la source, alors le fichier source va remplacer la cible. Ensuite, puppet va comparer le propriétaire, le groupe et les droits, et modifier la cible seulement si c'est nécessaire. L'ordre des ligne n'est pas important. Il n'est pas obligatoire de toutes les mettre (owner, group, mode), mais si on ne le met pas, il appartiendra au compte qui fait tourner le service (à priori root:root) et aura les droits par défaut (dépend de l'umask de votre serveur). Il est donc clairement conseillé de préciser toutes les infos.

La configuration est maintenant terminée. Elle est prise à chaud par puppet, inutile de relancer le service puppetmaster pour la prendre en compte.

Et maintenant, il n'y a plus qu'à... Si votre client a un service puppet qui tourne, alors, sur le master, tapez :

puppet kick nom-client

Sinon, connectez-vous sur le client et taper :

puppet agent -t

Si tout s'est bien passé, vous devez avoir sur le client un fichier /tmp/puppet_test.txt avec les droits définis plus haut et le contenu qu'on lui a mit. Si ce n'est pas le cas, voyez ce que dit le message d'erreur. Difficile de faire une liste exhaustive des erreurs possibles. A savoir qu'en général un "puppet kick" sur le serveur n'affiche pas d'erreur, même si ça se passe mal, alors qu'un "puppet agent -t" sur le client va nous afficher des informations. Donc pour le débogage il peut être intéressant de se placer sur le client.

2ème exemple : (re)démarrer un service

Nous allons maintenant vérifier qu'un service est lancé et, si ce n'est pas le cas, le relancer. Prenons le service ssh sur notre client ubuntu. On va donc créer un module qu'on va nommer sshd.

Commençons pas créer le répertoire du module et son sous-répertoire manifests :

mkdir -p /etc/puppet/modules/sshd/manifests/

Dedans, on va créer le fichier init.pp suivant :

class sshd {
    service { 'sshd':
        name      => ssh,
        ensure    => running,
    }
}

Sur la deuxième ligne, juste après le crochet ouvrant, on met le titre entre simples cottes. Ce titre est quelconque mais doit être unique par type de ressource. C'est à dire qu'il n'y a qu'un seul service qui peut s'appeler sshd, mais on peut avoir un service sshd et un fichier sshd, ça ne gênera pas puppet.

La ligne "name" contient le nom du service à démarrer sur le nœud.

Pour plus d'information sur la structure d'une déclaration, allez voir la doc officielle : http://docs.puppetlabs.com/puppet/3/reference/lang_resources.html.

Puis on va éditer /etc/puppet/manifests/node.pp pour d'inclure la classe sshd pour notre nœud ubuntu-virt :

node 'ubuntu-virt.lan' {
    include test
    include sshd
}

Et voilà, il n'y a plus qu'à vérifier que ça marche.

Mais j'entends déjà les petits malins dire : "C'est bien beau tout ça, mais chez moi j'ai des serveurs Redhat et des Solaris. Et sur Redhat, le service ssh s'appelle "sshd" alors que sur Solaris il s'appelle "ssh", comme sur Debian et Ubuntu. Alors comment on fait gros malin ??".

Et bien pas de problème, les concepteurs de puppet on pensé à ce cas de figure. Nous allons voir ça tout de suite dans notre exemple 3.


3ème exemple : agir en fonction de l'OS

On va se servir des variables de l'agent puppet. Une d'entre elle va nous dire sur quelle famille d'OS on se trouve, et en fonction du cas, on va donner telle ou telle valeur à notre service. Petite parenthèse, les variables des agents sont données par l'utilitaire facter. Il suffit de taper la commande facter sur un client pour voir la liste des variables qu'on peut utiliser.

Voici notre nouveau fichier init.pp :

class sshd {

   case $operatingsystem {
       centos, redhat:          { $service_name = 'sshd' }
       debian, ubuntu, solaris: { $service_name = 'ssh'  }
       default:                 { $service_name = 'ssh'  }
   }
   service { 'sshd':
       name      => $service_name,
       ensure    => running,
   }

}

Cet exemple est relativement simple. On fait un test sur la valeur de la variable $operatingsystem. Si elle vaut "centos" ou "redhat", alors on créé une variable interne à notre classe $service_name et on lui affecte la valeur "sshd". Si elle vaut "debian", "ubuntu" ou "solaris", alors on créé une variable interne à notre classe $service_name et on lui affecte la valeur "ssh". J'ai également ajouté une ligne (facultative) qui sera lue si la variable $operatingsystem ne correspond à aucune des valeurs proposées. A noter que l'action étant la même que pour "debian, ubuntu, solaris", on aurait pu se contenter de mettre une ligne pour "centos, redhat" et une ligne "default", mais j'ai mis comme ça pour rendre l'exemple plus parlant.

Bon, j'entends encore les petits malins du fond : "Bah oui, mais si mon service sshd n'est pas installé, comment on fait ?". Réponse tout de suite.


4ème exemple : installer le package de notre service

Voici la nouvelle version de notre classe :

class sshd {
    case $operatingsystem {
        centos, redhat:          { $service_name = 'sshd' }
        debian, ubuntu, solaris: { $service_name = 'ssh'  }
        default:                 { $service_name = 'ssh'  }
    }
    package { 'openssh-server':
        ensure => installed,
    }
    service { 'sshd':
        name      => $service_name,
        ensure    => running,
        require => Package['openssh-server'],
    }
}

On a ajouté une section "package" qui s'assure que le package "openssh-server" est bien installé sur le client. Si ne n'est pas le cas, il l'installe. Remarquez au passage la simplicité du langage puppet pour cette action.

On a également ajouté une ligne dans la section service :

require => Package['openssh-server'],

Cette ligne indique que pour pouvoir redémarrer le service, il y a un prérequis. Ce prérequis est précisé, il faut que ce qui est contenu dans la section "package 'openssh-server'" soit valide, c'est à dire que le package soit installé.


Pour ceux qui veulent aller plus loin

Nous venons de mettre en place une notion de relation entre les éléments de la classe. Il existe quatre types de relation : before, require, notify et subscribe. Je vous invite à aller voir la doc officielle sur ce sujet, qui explique les quatre avec des exemples pour bien les comprendre : http://docs.puppetlabs.com/puppet/2.7/reference/lang_relationships.html.

Pour revenir sur le "case", il existe également d'autres structures conditionnelles, notamment des "if" et des tests avec des expressions régulières (eh oui, même sous puppet on n'échappe pas aux tant redoutées expressions régulières ^_^). La doc officielle sur ce sujet est ici : http://docs.puppetlabs.com/puppet/2.7/reference/lang_conditional.html.

Quant aux packages, les petits malins diront que suivant les OS, le nom du package n'est pas le même. Une fois de plus ils nous cassent les pieds, mais leur remarque est pertinente. La réponse est ici : http://docs.puppetlabs.com/references/latest/type.html#package.

Les petits malins (pff...) nous ferons remarquer qu'on ne peut pas modifier un fichier (on peut juste le remplacer), et que dans certains cas, un fichier doit être personnalisé pour chaque nœud. Créer un fichier par nœud serait faisable, mais laborieux. Il est probable que l'utilisation des templates réponde à leur besoin. Une doc par ici : http://docs.puppetlabs.com/learning/templates.html. Je vous présente également un exemple un peu plus loin.

Vous trouverez encore des tonnes d'informations intéressantes, des exemples, des cas beaucoup plus pointus, etc. dans la documentation officielle qui est vraiment très complète. Je vous invite donc vivement à la parcourir, ainsi que les autres liens que je vous indique tout à la fin de cet article. Bonne lecture. Enjoy. ;-)

Encore un exemple : exécuter une commande quelconque

Bon, puisque vous insistez, je vais vous en donner encore un peu. On va exécuter une commande quelconque sur le client. Je pense qu'il vaut mieux essayer de s'en passer quand on peut (notamment parce que c'est peu portable d'un OS à l'autre), mais ça peut sauver des vies. Si si !

On va reprendre le premier exemple qui déposait un fichier test. Voici la nouvelle version de la classe :

class test {
    file { "/tmp/puppet_test.txt":
        owner => root,
        group => root,
        mode => 644,
        source => "puppet:///test/puppet_test.txt"
    }
    exec { "test_command":
        command => "/bin/echo \"J\'ai donne les cles du camion a Zezette. \nTon ip : $ipaddress.\" >> /tmp/puppet_test.txt",
        require => File["/tmp/puppet_test.txt"]
    }
}

On a ajouté un bloc "exec" qui contient son nom, sa ligne de commande et une dépendance. Comme la commande consiste à ajouter du texte au fichier, il faut s'assurer que le fichier a été précédemment ; c'est ce qu'on fait à la ligne "require".

La commande doit obligatoirement appeler les binaires avec leur chemin complet, ou alors il faut préciser le path sur une autre ligne. On peut également préciser ou exécuter la commande, le nombre d'essais si elle échoue, le timeout, ajouter une condition, etc. Toutes les possibilités sont indiquées ici : http://docs.puppetlabs.com/references/latest/type.html#exec.

Comme la commande doit être entourée de guillemets ou d'apostrophes, il va falloir jouer avec les anti-slashes pour échapper certains caractères. J'ai aussi ajouté une variable qui ne pourra être interprétée que si elle est entourée de guillemets, pas d'apostrophes. Mais si vous êtes habitués au shells unix, vous ne serez pas dépaysés.


Utiliser les templates

Les templates sont similaires aux Files ; ils vont déposer un fichier sur le client, mais en le personnalisant en utilisant des variables et/ou du code ruby. Les templates fonctionnent obligatoirement dans des modules. Dans le répertoire du module, il faut créer un sous-répertoire templates, et dedans on va mettre les templates, qui sont des fichiers *.erb. Par exemple : /etc/puppet/modules/mon_module/templates/fichier.txt.erb.

Mon besoin était de personnaliser le fichier de conf /etc/cups/cupsd.conf pour n'autoriser que les serveurs du réseau du client à se connecter. La ligne que j'avais était :

Allow 192.168.*

pour autoriser seulement les machines du réseau 192.168.* à se connecter. Cette ligne se retrouve plusieurs fois dans le fichier. Mon problème était que certains clients avaient un réseau en 192.168.*, et d'autres en 10.0.*. L'idée est donc de récupérer la variable $network_eth0 de l'agent, et de remplacer les deux derniers groupes de chiffres par .*.

On va donc créer un fichier /etc/puppet/modules/test/templates/cupsd.conf.erb qui, pour l'instant, est le fichier cupsd.conf de base, sans personnalisation. On va ensuite insérer du code ERB (le code des templates) aux endroits désirés. Le code ERB se met entre des balises <% et %>. Pour que la sortie du code soit affichée dans le fichier, on va commencer par une balise <%=. Pour un commentaire, ça sera une balise <%#. Quant aux variables, le code ERB est capable d'appeler toutes les variables de la classe (donc celles du master), mais également celles de l'agent (donc du client). Par contre, il faut les précéder d'un @ au lieu d'un $.

Let's go.

Pour rappel, voici la syntaxe pour un File /etc/puppet/modules/test/files/cupsd.conf :

file { "/etc/cups/cupsd.conf":
    owner  => root,
    group  => root,
    mode   => 644,
    source => "puppet:///test/cupsd.conf",
}


Et maintenant celle d'un template /etc/puppet/modules/test/templates/cupsd.conf.erb :

file { "/etc/cups/cupsd.conf":
    owner   => root,
    group   => root,
    mode    => 644,
    content => template("test/cupsd.conf.erb"),
}

On remarque qu'il n'y a qu'une seule ligne qui change : la ligne source devient content. Malheureusement, la syntaxe n'est pas la même. On utilise la fonction template() au lieu de la source puppet:///. D'ailleurs, les développeurs du projet s'excusent de ce manque d'homogénéité dans la doc officielle sur les templates. Mais peu importe, il suffit de le savoir, et cette syntaxe se comprend facilement.

Passons maintenant au fichier ERB. Voici à quoi il ressemble :

[...]
<% @network = @network_eth0.gsub(/.([0-9]*).([0-9]*$)/, ".*") %>
# Restrict access to the server...
<Location />
  Order deny,allow
  Deny From All
  Allow From 127.0.0.1
  Allow from <%= @network %>
</Location>

# Restrict access to the admin pages...
<Location /admin>
  Order deny,allow
  Deny From All
  Allow From 127.0.0.1
  Allow from <%= @network %>
</Location>

# Restrict access to configuration files...
<Location /admin/conf>
  AuthType Default
  Require user @SYSTEM
  Order deny,allow
  Deny From All
  Allow From 127.0.0.1
  Allow from <%= @network %>
</Location>
[...]

Explications. Sur la première ligne, on définit une variable @network. Le contenu de cette variable est la variable @network_eth0 de l'agent qu'on modifie avec la fonction gsub. Il s'agit d'une fonction du langage ruby qui nous sert ici à remplacer les deux derniers groupes de chiffres de l'adresse ip, par un .*, à l'aide d'une expression régulière. Ça peut paraitre un peu barbare à certains, mais il faut savoir qu'on peut exécuter n'importe quel code ruby ici, et il fallait bien choisir un exemple. D'ailleurs, on n'est même pas obligé d'exécuter du code, on peut se contenter d'afficher la variable. Bref, revenons à nodre première ligne. Le code est exécuté (on a affecté une valeur à notre variable), mais pour l'instant rien n'est affiché, car on a une balise <%.

Maintenant qu'on a notre chaine de caractère, on l'utilise ou on veut. Ici c'est dans chacune des sections du fichier, on on retrouve cette ligne :

Allow from <%= @network %>

Dans le fichier final, sur le client, on aura la ligne modifiée qui sera adaptée au réseau du client. Par exemple :

Allow from 192.168.*

Et voilà. Cette méthode n'est pas très compliquée et n'a de limite que vos compétences en ruby. ;-)

Les variables utilisables pour les nœuds

Les agents qui tournent sur les nœuds possèdent un certain nombre de variables qui vont nous être utiles dans l'écriture de nos manifests. On en a déjà vu dans la partie "Mise en pratique", par exemple $operatingsystem. C'est l'outil facter qui collecte et renvoie ces variables. Il est installé avec les packages puppet et puppetmaster. Pour voir toutes les variables existantes, il suffit de taper la commande facter sans argument. Voici un exemple de sortie sur ma machine virtuelle Ubuntu 13.04.

# facter
architecture => amd64
augeasversion => 1.0.0
boardmanufacturer => Oracle Corporation
boardproductname => VirtualBox
boardserialnumber => 0
domain => lan
facterversion => 1.6.9
fqdn => ubuntu-virt.lan
hardwareisa => x86_64
hardwaremodel => x86_64
hostname => ubuntu-virt
id => root
interfaces => eth0,lo
ipaddress => 192.168.1.5
ipaddress_eth0 => 192.168.1.5
ipaddress_lo => 127.0.0.1
is_virtual => true
kernel => Linux
kernelmajversion => 3.8
kernelrelease => 3.8.0-30-generic
kernelversion => 3.8.0
lsbdistcodename => raring
lsbdistdescription => Ubuntu 13.04
lsbdistid => Ubuntu
lsbdistrelease => 13.04
lsbmajdistrelease => 13
macaddress => XX:XX:XX:XX:XX:XX
macaddress_eth0 => XX:XX:XX:XX:XX:XX
manufacturer => innotek GmbH
memoryfree => 585.91 MB
memorysize => 995.02 MB
memorytotal => 995.02 MB
netmask => 255.255.255.0
netmask_eth0 => 255.255.255.0
netmask_lo => 255.0.0.0
network_eth0 => 192.168.1.0
network_lo => 127.0.0.0
operatingsystem => Ubuntu
operatingsystemrelease => 13.04
osfamily => Debian
path => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
physicalprocessorcount => 1
processor0 => AMD Phenom(tm) II X4 965 Processor
processor1 => AMD Phenom(tm) II X4 965 Processor
processorcount => 2
productname => VirtualBox
ps => ps -ef
puppetversion => 2.7.18
rubysitedir => /usr/local/lib/site_ruby/1.8
rubyversion => 1.8.7
selinux => false
serialnumber => 0
sshdsakey => [...]
sshecdsakey => [...]
sshrsakey => [...]
swapfree => 1.46 GB
swapsize => 1.48 GB
timezone => CEST
type => Other
uniqueid => xxxxxxxx
uptime => 14:09 hours
uptime_days => 0
uptime_hours => 14
uptime_seconds => 50947
virtual => virtualbox


Obtenir de l'aide

La page man de puppet ne contient rien. Mais on obtient l'équivalent du man avec la commande :

puppet help

Et on peut avoir une aide plus détaillée sur une sous-commande avec, par exemple :

puppet help kick

Liens