1. Présentation
Mcollective [1], pour « Marionnette Collective », est un logiciel écrit par R.I. Pienaar. Le but est de faciliter la gestion d'un parc comportant de nombreuses machines depuis un point central. Il peut se comparer à des outils comme Fabric [2] (cf. l'excellent article d'iMil récemment dans GLMF), ou Capistrano [3], car il permet de lancer de nombreuses actions en parallèle sur des machines distantes, mais il s'en différencie par un point notable : il ne repose pas sur SSH. En effet, le programme s'appuie sur un middleware (intergiciel si ma mémoire de langage avec du sirop au caramel ne me trompe pas) et dispose de fonctionnalités qui font de vous non plus une loutre, mais LA loutre ultime.
Pourquoi cela ? Parce qu'une bonne partie du travail répétitif et lassant est pris en charge directement dans le programme. Avec les deux logiciels cités précédemment, vous devez savoir quelles machines sont là, quelle configuration elles embarquent. Bref, vous devez tenir une liste à jour. Avec Mcollective, la découverte des clients est automatique : les machines viennent s'enregistrer sur un serveur, et lors d'une requête, les messages sont dispatchés vers tous les hôtes via le middleware.
Mcollective utilise un daemon qui tourne sur chaque machine. Ce dernier utilise des agents afin d'effectuer les différentes actions que l'on attend de lui : gérer des packages, des services ou envoyer des messages. Chaque agent s'inscrit à un « topic » du middleware et attend les messages qui lui correspondent.
2. Installation et configuration
Le projet fournit des packages au format RPM, Deb et des ebuilds pour Gentoo. Les fichiers pour créer un port OpenBSD sont également disponibles. Si votre plateforme cible n'est pas dans celles citées, pas de souci : un makefile générique est disponible. Le package étiqueté -common contient la base du logiciel. Le package normal est à déployer sur les machines à commander, et le package -client est celui à utiliser pour contrôler les différents nœuds du réseau, celui de l'admin donc.
À noter que vous aurez besoin du gem stomp pour la communication avec le middleware, installez-le donc d'un petit
# gem install stomp
La plateforme de référence pour cet article sera la distribution Debian.
Ensuite, une simple petite invocation de dpkg fera l'affaire :
# dpkg -i mcollective_1.0.0-2_all.deb mcollective-common_1.0.0-2_all.deb
En guise de middleware, nous allons utiliser un serveur stomp. Il en existe plusieurs et celui utilisé pour le développement est ActiveMQ. Il possède de nombreuses fonctionnalités, mais se révèle assez lourd à mettre en place, et de plus, il n'est pas officiellement packagé dans Debian. Le but de cet article n'étant pas de monter une usine à gaz en Java, nous allons installer le package stompserver, qui fera largement aussi bien pour notre mise en jambes.
# apt-get install stompserver
Et nous le configurerons comme suit :
---
:daemon: true
:working_dir: /var/lib/stompserver
:logdir: /var/log/stompserver
:pidfile: /var/run/stompserver/pid
:storage: /var/lib/stompserver/storage
:etcdir: /etc/stompserver
:queue: memory
:auth: false
:debug: false
:group: stompserver
:user: stompserver
:host: 127.0.0.1
:port: 61613
Pour que le serveur stomp écoute sur autre chose que son adresse de loopback, remplacez la valeur 127.0.0.1 par l'adresse IP de votre machine. Une fois le middleware en place, nous pouvons configurer la partie serveur (le daemon donc) de Mcollective.
topicprefix = /topic/mcollective
libdir = /usr/share/mcollective/plugins
logfile = /var/log/mcollective.log
loglevel = info
daemonize = 1
# Plugins
securityprovider = psk
plugin.psk = unset
connector = stomp
plugin.stomp.host = stomp.mycorp.net
plugin.stomp.port = 6163
plugin.stomp.user = mcollective
plugin.stomp.password = zomgsecret
# Facts
factsource = yaml
plugin.yaml = /etc/mcollective/facts.yaml
Examinons certaines des directives composant le fichier :
- topicprefix : c'est une indication liée au middleware, c'est pour ainsi dire le « lieu d'inscription » du daemon. Cela permet de faire cohabiter différents ensembles de daemons Mcollective sur un même middleware.
- libdir : c'est l'endroit où se trouveront les plugins.
- connector : le connecteur utilisé pour la communication avec le middleware, il est interchangeable facilement pour utiliser un autre protocole. Les 4 lignes suivantes sont des directives de configuration pour les variables du plugin « stomp ».
- factsource : c'est l'indication pour le daemon du plugin qui sert à importer les facts. Ici, c'est un simple fichier yaml dans lequel j'ai redirigé la sortie de la commande suivante :
# facter -py
C'est par souci de performance que je fais ceci : en effet, invoquer facter est relativement coûteux en ressources, et sur de petites machines, cela pénalise les performances de Mcollective. De même, il est possible de donner plusieurs fichiers yaml en entrée en séparant les différentes sources par le caractère :.
plugin.yaml = /etc/mcollective/puppet.yaml:/etc/mcollective/mycorp.yaml
On peut donc avoir les valeurs suivantes dans /etc/mcollective/mycorp.yaml, par exemple :
country: fr
company: acme
datacenter: garage
Attention, le fichier doit être un fichier YAML valide, sans quoi Mcollective ne le chargera pas.
Maintenant que tout est configuré, nous pouvons lancer notre daemon :
# /etc/init.d/mcollective start
Il nous faut aussi un client pour manipuler les machines cibles. Installons le package -client sur notre machine d'admin. De nouveau l'ami dpkg :
# dpkg -i mcollective-client_1.0.0-2_all.deb
Le client utilise le fichier /etc/mcollective/client.cfg pour savoir comment se connecter au serveur middleware. Il est également possible d'utiliser un fichier de configuration par utilisateur en créant le fichier ~/.mcollective. Pensez à renseigner les noms d'hôtes ainsi que les logins et password dans le client pour les faire correspondre à ceux contenus dans le fichier server.cfg.
Lançons notre première commande Mcollective afin de voir quelles sont les machines connectées sur le middleware :
$ mc-find-hosts -v
mordor
khepry
---- stomp call summary ----
Nodes: 2
Start Time: Fri Sep 17 16:20:22 +0200 2010
Discovery Time: 0.00ms
Agent Time: 5005.94ms
Total Time: 5005.94ms
Un des point forts de Mcollective est le fait de pouvoir utiliser des classifieurs afin de filtrer les machines sur lesquelles les actions doivent être lancées. Mcollective utilise par défaut le fichier /etc/mcollective/facts.yaml, comme vu plus haut, mais est aussi capable d'utiliser les informations venues d'un plugin. Le plus utilisé est sûrement facter, qui est également le fournisseur d'infos de Puppet. Il est possible d'utiliser ohai, l'équivalent de facter pour chef, un autre projet open source de configuration management. Plus les informations remontées par ces outils sont nombreuses, plus il est simple de filtrer précisément les machines sur lesquelles on veut agir.
Filtrons alors sur un fact. Ici, c'est sur le fact « country » que je veux filtrer :
$ mc-fnd-hosts –wf country=fr
mordor
$ mc-find-hosts –wf country=de
khepry
On peut aussi filtrer sur des classes (Puppet), sur les noms d'hôtes (via une expression rationnelle) ou combiner ces critères afin de ne sélectionner, par exemple, que les serveurs web de la compagnie « acme »
mc-find-hosts –wf corp="acme" –wc "apache2::server"
3. Cas pratique : utilisation d'agents
Le daemon seul est peu utile : ce sont les agents qui le rendent si puissant. Nous allons donc en déployer plusieurs afin de voir comment fonctionne Mcollective en conditions réelles. À noter que ces agents réutilisent des parties de Muppet pour effectuer certaines actions, mais que Puppet n'est absolument pas nécessaire à la bonne marche de Mcollective. Vous pouvez les télécharger sur le site indiqué en [4].
Ces agents seront les suivants :
- service ;
- package ;
- puppetd.
Il faut les copier sur les machines cibles dans le répertoire des plugins et plus exactement dans notre cas : /usr/share/mcollective/plugins/mcollective/agent. Le daemon va automatiquement charger les plugins au redémarrage, relançons-le donc :
# /etc/init.d/mcollective restart
Ces 3 plugins ont un point commun : plutôt que de réinventer la route, l'auteur a voulu réutiliser un travail déjà fait et comme je l'ai dit plus haut, ils réutilisent une partie du code de Puppet, à savoir les providers. Les providers sont les éléments qui rendent Puppet « omnipotent » et qui lui permettent de réaliser, par exemple, l'installation d'un package de façon indifférente sur une Debian ou sur un OpenBSD. Les agents sont donc aussi capables que l'est Puppet dans toutes les actions qu'il peut mener.
Le mode de découverte des capacités de Mcollective fonctionne de la façon suivante : s'il possède l'agent appelé et qu'il correspond aux éventuels critères de recherche, alors il va lancer l'action demandée avec les arguments qui lui sont fournis. La plupart des agents sont des agents dits « SimpleRPC » (nous verrons un peu plus loin ce que ceci représente) et peuvent être invoqués de façon générique :
$ mc-rpc --agent service --action status --arg service=apache2 -v --with-class webserver::pool
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
khepry : OK
{"status"=>"running"}
---- service#status call stats ----
Nodes: 1 / 1
Pass / Fail: 1 / 0
Start Time: Sun Jan 30 18:14:02 +0100 2011
Discovery Time: 2003.16ms
Agent Time: 265.37ms
Total Time: 2268.52ms
Il est à noter que je n'ai pas déployé l'agent « service » sur mordor, il n'a donc pas répondu à la requête, tandis que khepry a répondu, puisqu'il correspondait aux critères. Maintenant, ajoutons un filtre issu des helpers génériques :
$ mc-rpc --agent service --action status --arg service=apache2 -v --with-class webserver::pool --wf country=fr
Determining the amount of hosts matching filter for 2 seconds .... 0
No request sent, we did not discover any nodes.
Finished processing 0 / 0 hosts in 0.00 ms
Aucune machine enregistrée ne répondant aux critères, rien ne se passe. À noter que la sortie des commandes Mcollective est « condensée » : en effet, en utilisant un outil comme fab ou capistrano, on récupère à l'écran toute la sortie de la commande et ce multiplié par le nombre de machines. Ce volume d'informations devient très vite illisible, ingérable et donc inutile. Ici, c'est Mcollective qui décide de la forme que doit prendre le rapport, notamment par le biais de fichiers DDL (Data Description Language), qui peuvent être associés à chaque agent. Néanmoins, dans sa forme basique, l'affichage est clair et concis, ce qui est convenable dans la plupart des cas. Le switch -v est disponible et est utilisé ici afin de gagner en verbosité.
Les agents constituant le côté utilisateur de Mcollective, c'est à eux que nous allons maintenant nous intéresser en écrivant le nôtre.
4. Écriture d'un agent
L'écriture d'agents pour Mcollective se fait à un assez bas niveau. Pour faciliter le développement, l'auteur a alors écrit un mini framework réutilisable nommé simpleRPC, qui permet d'avoir des conventions communes entre tous les agents, comme le passage des arguments, le formatage de la sortie (via les DDL), l'auditing, la gestion des permissions. On peut ainsi écrire un agent avec un niveau de Ruby qui n'est pas très avancé. De plus, il pourra être appelé par tous les clients via la commande mc-rpc.
Pour notre test, nous allons écrire un agent très simple, permettant de faire des actions basiques sur un dépôt git.
Commençons par écrire notre agent avec juste une action d'écho à l'intérieur et analysons la structure du fichier.
module MCollective
module Agent
class Glmfagent < RPC::Agent
metadata :name => "glmfagent",
:description => "Agent for GLMF",
:author => "Nicolas Szalay <nico@rottenbytes.info>",
:license => "BSD",
:version => "0.1",
:url => "http://www.rottenbytes.info/",
:timeout => 60
action "echo" do
reply[:data] = request[:data]
end
end
end
end
Ce code est à enregistrer en tant que /usr/share/mcollective/plugins/mcollective/agent/glmfagent.rb.
On peut alors invoquer simplement l'agent avec mc-rpc :
$ mc-rpc --agent glmfagent --action echo --arg data='hello world'
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
Finished processing 1 / 1 hosts in 316.10 ms
En mode verbeux, on aura des informations en plus :
$ mc-rpc --agent glmfagent --action echo --arg data=’hello world’ -v
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
mordor : OK
{:data=>"hello world"}
---- glmfagent#echo call stats ----
Nodes: 1 / 1
Pass / Fail: 1 / 0
Start Time: Sun Jan 30 18:41:18 +0100 2011
Discovery Time: 2003.07ms
Agent Time: 309.50ms
Total Time: 2312.57ms
La section « metadata » permet l'introspection des agents. Il est possible qu'elle disparaisse un jour au profit des DDL, qui permettent d'accéder via du code aux fonctionnalités et à la documentation des agents (pour, par exemple, construire automatiquement une interface web devant les agents déployés sur une machine). Tous les champs sont informatifs, sauf un. Le champ « timeout » sert à déterminer le temps maximum qu'aura l'agent pour effectuer son action. Si celle-ci dure plus longtemps que prévu, alors le processus sera tué.
Il est donc très simple de faire entrer et sortir des données des agents, l'auteur a tout prévu pour cela, y compris un système de validation que nous allons examiner un peu plus loin. Dans notre cas, nous transmettons une chaîne arbitraire et l'agent nous la retourne. Elle lui arrive dans la variable requestet nous lui renvoyons dans la variable reply.
Implémentons maintenant une fonctionnalité de base du dépôt Git : sa création par clonage d'un dépôt existant. Pour cela, nous allons implémenter une action et forcer l'utilisateur à fournir 2 paramètres : l'adresse du dépôt de base et l'emplacement où nous voulons cloner ce dernier. Nous allons alors utiliser le système de validation interne des paramètres passés à l'agent. Pour nous faciliter la tâche et rester purement dans le code Ruby, installons un petit gem Ruby sympathique [5] :
# gem install git
Ce qui nous donne le code source suivant :
require "rubygems"
require "git"
action "gitclone" do
validate :path, String
validate :url, String
g=Git.clone(request[:url],request[:path])
if $? != 0 then
reply.fail
else
# return the branch name when success
reply[:data]=g.branch.name
end
end
On crée une action gitclone (à noter que clone est un mot-clé réservé en Ruby, il n'est pas possible de l'utiliser comme nom d'action ici). Cette action demande 2 paramètres :
$ mc-rpc --agent glmfagent --action gitclone --arg url="https://github.com/schacon/ruby-git.git" --arg path="/tmp/foobar" -v
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
mordor : OK
{:data=>"master"}
---- glmfagent#gitclone call stats ----
Nodes: 1 / 1
Pass / Fail: 1 / 0
Start Time: Wed Feb 02 16:44:57 +0100 2011
Discovery Time: 2001.53ms
Agent Time: 2400.20ms
Total Time: 4401.73ms
Voyons ce qui se passe si nous omettons un des paramètres nécessaires à l'agent et enlevons l'argument path :
$ mc-rpc --agent glmfagent --action gitclone --arg url="https://github.com/schacon/ruby-git.git"
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
mordor please supply a path
Le système de validation interne nous informe que path est requis. L'auteur est allé plus loin et a prédéfini quelques types couramment utilisés : adresses ipv4, ipv6, chaîne de caractères et chaîne dite « shellsafe » afin d'améliorer la sécurité. À noter que l'on peut aussi tout simplement utiliser une expression rationnelle. Nous allons forcer l'utilisation d'un dépôt git en HTTPS comme source de données. Modifions la ligne
validate :url, String
en
validate :url, /https:.*/
et testons :
$ mc-rpc --agent glmfagent --action gitclone --arg url="http://github.com/schacon/ruby-git.git" --arg path="/tmp/foobar"
Determining the amount of hosts matching filter for 2 seconds .... 1
* [ ============================================================> ] 1 / 1
mordor : Failed to validate url: url should match (?-mix:https:.*)
Failed to validate url: url should match (?-mix:https:.*)
Si nous appelons une URL en HTTP simple, et qui ne matche donc pas l'expression rationnelle, alors l'agent nous rappelle à l'ordre et refuse d'exécuter l'action. Ce mécanisme de contrôle permet de s'affranchir de vérification du côté du client et améliore la fiabilité des agents.
Conclusion
J'espère que cette première introduction à Mcollective vous a plu et vous a donné une idée des immenses possibilités de ce framework. Il reste bien des aspects à découvrir, comme le reporting, l'intégration avec d'autres applications, les DDL, la gestion des droits, les plugins de remontée et de collecte d'informations. Tout ceci pourra être l'occasion d'un prochain article. À noter qu'il existe un canal IRC #mcollective sur le réseau freenode et une mailing list pour communiquer et trouver de l'aide.
Liens
[1] http://marionette-collective.org
[3] https://github.com/capistrano/capistrano
[4] http://projects.puppetlabs.com/projects/mcollective-plugins/wiki
[5] https://github.com/schacon/ruby-git