Un peu plus d'assaisonnement

GNU/Linux Magazine n° 166 | décembre 2013 | Emile (iMil) Heitor
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 !
Nous avons précédemment vu comment le système de gestion de configuration Salt nous permettait de contrôler différents aspects d'un parc de machines, par exemple en s'assurant que des fichiers de configuration communs étaient tous synchronisés et que les paquets nécessaires aux services souhaités sur ces serveurs étaient bien installés et à jour. Bien que le nombre de modules fournis par défaut par Salt permette l'orchestration d'un parc relativement standard, pour une infrastructure de grande envergure ou pour des besoins spécifiques, il peut s'avérer indispensable de créer son propre module de contrôle, c'est cet aspect du logiciel que nous allons explorer ici.

1. Vogue, petit module, vogue

Un master Salt sait évidemment communiquer avec ses minions pour les interroger ou leur faire exécuter des actions et dans ce dernier cas, une panoplie de possibilités permet de façonner les minions avec grande souplesse.

Salt est livré muni d'une quantité remarquable de modules, présents dans ${PREFIX}/lib/pythonX.Y/{site,dist}-packages/salt/modules. Mais d'emblée, nous sommes en mesure d'étendre cette liste sans polluer le contenu du répertoire placé sous le contrôle du paquet fourni par votre système. Pour ce faire, il suffit, dans le répertoire défini comme file_roots dans le fichier de configuration de salt-master d'ajouter un répertoire _modules (notez l'underscore devant le nom du répertoire). Une fois le répertoire _modules peuplé de modules maison, ces derniers seront synchronisés sur les minions grâce à la commande :

# salt ‘*’ saltutil.sync_modules

Où, comme vu dans l'article de présentation de la suite Salt, '*' représente l'environnement de base (tous les minions); on aurait pu spécifier un minion en particulier ou une condition relative à une catégorie de minions. Par exemple, pour cibler les minions dont le système d'exploitation est Debian GNU/Linux, on aurait fait :

# salt -G ‘os:Debian’ saltutil.sync_modules

Utilisant ainsi les grains, valeurs statiques chargées au démarrage des minions.

Deux intérêts majeurs à la possibilité de synchroniser ainsi dynamiquement des modules :

- Utiliser des modules spécifiques à votre environnement.

- Tester des modules que vous souhaiteriez publier plus largement et pourquoi pas les soumettre au projet Salt pour inclusion dans le logiciel lui même.

Munis de cette facilité, nous allons pouvoir démarrer l'apprentissage de l'écriture d'un module simple et nous familiariser ainsi avec les multiples possibilités offertes par l'environnement Salt.

2. Hello, Salt

Nous en avons parlé dans l'article d'introduction, Salt est écrit en python, et tout naturellement, les modules sont également proposés dans ce langage, simplifiant grandement leur écriture. Nous allons donc écrire un simple module qui renvoie une chaîne de caractères et constater son fonctionnement sur un sous-ensemble de minions. Exécution :

$ cat ~/salt/_modules/hello.py
def hello():
return ‘Hello, Salt!

On synchronise ce module comme vu précédemment, je choisis ici mes machines NetBSD :

# salt -G ‘os:NetBSD’ saltutil.sync_modules
watto:
- modules.hello
korriban:
- modules.hello
coruscant:
- modules.hello
exar:
- modules.hello
ragnos:
- modules.hello

Le maître nous informe que le module hello a été synchronisé sur nos minions dont le grain “os” vaut NetBSD. Il nous est maintenant possible de faire appel à notre nouveau module de la façon la plus simple qui soit :

$ sudo salt -G ‘os:NetBSD’ hello.hello
exar:
Hello, Salt!
watto:
Hello, Salt!
coruscant:
Hello, Salt!
korriban:
Hello, Salt!
ragnos:
Hello, Salt!

Simple non ?

Le développement de modules, comme tout développement, pouvant s'avérer semé d'embûches, il n'est pas obligatoire de passer systématiquement par la machinerie master - minion. L'utilitaire salt-call permet de réaliser un appel direct :

# salt-call hello.hello
[INFO ] Configuration file path: /usr/pkg/etc/salt/minion
local:
Hello, Salt!

Ce petit exemple ouvre d'ores et déjà un nombre de possibilités assez vaste mais sa portée reste limitée. Héritant des capacités objet de Python, Salt permet, au sein d'un module, de faire appel à toute sa machinerie interne.

Faisons-donc appel, dans notre module trivial, à la notion de grains :

$ cat hello.py
def hello():
if __grains__[‘kernelrelease’] < ‘6.1’:
return ‘VIEUX!’
return ‘JEUNE! {0}’.format(__grains__[‘kernelrelease’])

Après avoir synchronisé cette nouvelle version, nous pouvons constater son bon fonctionnement :

# salt -G ‘os:NetBSD’ hello.hello
watto:
JEUNE! 6.1_RC4
coruscant:
VIEUX!
exar:
VIEUX!
korriban:
VIEUX!
ragnos:
VIEUX!

Pour rappel, on consultera la liste des grains disponibles via la commande :

# salt ‘watto’ grains.items
watto:
cpu_flags: FPU DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA
CMOV PAT PSE36 CFLUSH MMX FXSR SSE SSE2 SSE3 CX16 POPCNT RAZ XD LAHF
cpu_model: Intel 686-class
cpuarch: i386
defaultencoding: ISO8859-15
defaultlanguage: fr_FR
domain: home.imil.net
fqdn: watto.home.imil.net
gpus:
{‘model’: ‘CL-GD5446’, ‘vendor’: ‘Cirrus Logic’}
host: watto
id: watto
ipv4:
127.0.0.1
192.168.1.151
kernel: NetBSD
kernelrelease: 6.1_RC4
localhost: watto.home.imil.net
master: coruscant
mem_total: 511
nodename: watto.home.imil.net
num_cpus: 2
num_gpus: 1
os: NetBSD
os_family: NetBSD
osrelease: 6.1_RC4
path: /usr/bin:/bin:/usr/pkg/bin:/usr/local/bin:/sbin:/usr/sbin:/
usr/pkg/sbin:/usr/X11R7/bin:/home/imil/bin
ps: ps auxwww
pythonpath:
/usr/pkg/bin
/usr/pkg/lib/python27.zip
/usr/pkg/lib/python2.7
/usr/pkg/lib/python2.7/plat-netbsd6
/usr/pkg/lib/python2.7/lib-tk
/usr/pkg/lib/python2.7/lib-old
/usr/pkg/lib/python2.7/lib-dynload
/usr/pkg/lib/python2.7/site-packages
pythonversion: 2.7.3.final.0
saltpath: /usr/pkg/lib/python2.7/site-packages/salt
saltversion: 0.15.1
server_id: 1276300030
shell: /usr/pkg/bin/bash
virtual: kvm

Toutes les clés et valeurs sont disponibles, soit à partir de la ligne de commande à la suite du flag -G, soit directement dans le module à l'aide du dict __grains__.

3. Pass'pass'l'module

Au delà des grains, c'est à l'intégralité des fonctionnalités de Salt auxquelles nous avons accès dans notre module, ceci à travers le dict __salt__. Accédons par exemple aux fonctions d'exécution :

$ cat hello.py
def hello(directory):
return __salt__[‘cmd.run’](‘ls {0}’.format(directory))
$ sudo salt ‘coruscant’ saltutil.sync_modules
$ sudo salt-call hello.hello ‘/usr’
[INFO ] Configuration file path: /usr/pkg/etc/salt/minion
[INFO ] Executing command ‘ls /usr’ in directory ‘/root’
local:
X11R6
X11R7
bin
games
include
lib
libdata
libexec
local
mdec
obj
obj-i386
pkg
pkgsrc
sbin
share
src
tests
tools
tools-i386

Ici, nous exécutons la commande ls suivie d'un paramètre variable (directory) en faisant appel au module cmdmod.py, présent dans l'installation de base de Salt. Ce dernier expose des fonctions d'interfaçage avec l'exécution de commandes sur le minion ciblé. Bien évidemment, notre module hello est également immédiatement disponible pour tous les autres modules chargés. Par exemple, lors de l'écriture de hello2, dans un fichier distinct hello2.py, nous sommes parfaitement autorisés à utiliser les fonctionnalités de hello.py :

$ cat hello2.py
def hello2():
return __salt__[‘hello.hello’](‘/stand’)
$ sudo salt ‘coruscant’ saltutil.sync_modules
coruscant:
- modules.hello2
$ sudo salt-call hello2.hello2
[INFO ] Configuration file path: /usr/pkg/etc/salt/minion
[INFO ] Executing command ‘ls /stand’ in directory ‘/root’
local:
amd64

La fonction hello2 fait appel à la fonction hello précédemment écrite dans hello.py en lui passant en paramètre le répertoire /stand. hello2 retourne à Salt la valeur de retour de la fonction hello que nous avons appelé via le dict __salt__ qui donne un accès direct à cette dernière.

4. Transformation

Dans l'exemple précédent, vous aurez probablement remarqué qu'alors qu'on accède aux fonctions d’exécution via l'appel à cmd.<fonction>, le module se nomme en réalité cmdmod.py. Bien plus qu'un simple renommage, le module utilise ici une puissante fonctionnalité: les modules virtuels.

L'un des intérêts de l'orchestration réside dans la généricité des fonctions appelées, par exemple, il serait fastidieux d'appeler séparément apt.install, pkgin.install ou encore yum.install pour écrire nos recettes d'installation de paquets sur notre parc hétérogène. Au lieu de cela, nous appelons Salt de façon générique :

# salt ‘*’ pkg.install vim

Pourtant, les fonctionnalités d'installation de logiciels tiers sont belles et bien présentes dans plusieurs modules, aux noms distincts. Quelle est cette sorcellerie ? C'est ici la notion de module virtuel qui se charge d'appeler le bon backend pour installer le paquet. Par exemple, dans pkgin.py, j'ai ajouté :

def _check_pkgin():
‘’’
Looks to see if pkgin is present on the system, return full path
‘’’
return salt.utils.which(‘pkgin’)
def __virtual__():
‘’’
Set the virtual pkg module if the os is supported by pkgin
‘’’
supported = [‘NetBSD’, ‘SunOS’, ‘DragonFly’, ‘Minix’, ‘Darwin’]
if __grains__[‘os’] in supported and _check_pkgin():
return ‘pkg’
else:
return False

Ainsi, si le système d'exploitation est supporté par pkgin et que pkgin est accessible dans l'un des $PATH (salt.utils.which), nous informons Salt que ce module sera appelé via son nom virtuel pkg. Autre avantage de cette fonctionnalité, on évitera élégamment les conflits de nommage en créant son module Python avec un nom différent que celui par lequel il est invoqué, exactement comme cela est réalisé pour les fonctions cmd inscrites dans cmdmod.py.

5. Une dernière pincée

Quelques astuces supplémentaires permettront à vos futurs modules de passer la barrière du pull-up request, participation au projet rendue extrêmement simple grâce au site GitHub[1].

5.1 Les fonctions internes

Il n'est pas rare, dans tout type de projet, d'écrire des fonctions qui n'ont qu'une portée interne, dans notre cas, des fonctions qui ne seront pas exposées aux minions. Cela se réalise très simplement en préfixant le nom de la fonction par un underscore, par exemple :

Fonction chargée par le minion :

def foo():
return ‘bar’

Fonction non-chargée par le minion :

def _foo():
return ‘bar’

5.2 Do-cu-men-tez !

La méthode de documentation d'un module Salt n'est pas différente de celle employée pour un code Python classique. En renseignant proprement un docstring dans vos fonctions, ces dernières seront consultables par la commande Salt sys.doc :

def foo():
‘’’
This function returns the number of fooes.
CLI Example::
salt ‘*’ test.foo
‘’’

6. Quoi, vous êtes encore là ?

Accordez-moi que le développement de modules Salt brille par sa simplicité, mais également par sa souplesse et ses concepts inhérents. De plus, l'excellente lisibilité des modules présents dans l'installation de base vous permettra de vous mettre au travail rapidement. Le projet étant développé collaborativement sur GitHub, il est aisé de se faire une idée assez précise des structures et modèles utilisés. Enfin, le canal #salt sur le réseau IRC freenode est peuplé de nombreux développeurs du projet dont la sympathie et l'entrain ne gâchent rien au plaisir de participer à un tel projet.

7. Références

[1] https://github.com/saltstack/salt