Ansible – inventaire dynamique avec PHP et MySQL
Au sommaire [afficher]
Ansible est un outil de gestion de configuration sous licence GNU GPL, très certainement disponible sur les dépôts de votre distribution Linux préférée. Il se démarque de ses concurrents par sa simplicité d’utilisation et de configuration et sa modularité. Pour les férus de sécurité, il est sans agents et utilise SSH pour contrôler les hôtes dont il a la charge.
Pour ceux qui ne connaissent pas, Ansible permet d’appliquer une configuration type à un système, de créer des comptes utilisateurs, d’installer des paquets… en parallèle et simultanément sur tous les hôtes d’une infrastructure.
Cet article n’est pas une introduction, il couvre un sujet bien précis : les inventaires dynamique. Et plus particulièrement, les inventaires dynamiques avec PHP et MySQL.
Note: les morceaux de codes présents dans cet article n’ont pas été testés. Vous devrez donc les corriger et les adapter pour pouvoir les utiliser.
Rappel sur les inventaires
Avant d’entrer dans le vif du sujet, voici un bref rappel de ce que sont les inventaires.
Un inventaire est une source de données qui permet à Ansible de connaître la liste des hôtes qu’il doit contrôler. C’est le minimum vital pour qu’Ansible puisse fonctionner.
Cette source de données peut être constituée d’un ou plusieurs fichiers respectant la syntaxe INI, on parle alors d’inventaire statique.
Voici un inventaire statique très simple regroupant trois hôtes dans deux groupes différents.
[aaa] 127.0.0.1 192.168.1.10 [bbb] 10.0.0.1
Pour un usage basique (i.e.: quelques hôtes à gérer et des playbook relativement simples), on peut très largement se contenter d’un inventaire statique.
Mais lorsque l’infrastructure devient conséquente, utiliser des fichiers devient très lourd à maintenir. Heureusement pour nous, Ansible est capable d’utiliser autre chose que des fichiers comme source de données.
Les inventaires dynamiques
De base, Ansible peut utiliser des sources de données dynamiques telles que AWS EC2 (le cloud Amazon), OpenStack, et bien d’autres.
Ansible est aussi capable d’utiliser un exécutable, on peut donc créer son propre inventaire dynamique. Peu importe le langage utilisé, du moment qu’il peut être exécuté et qu’il renvoi les données respectant la syntaxe JSON.
Si nous traduisons notre premier inventaire statique en JSON, et y ajoutons une « group_vars » domain, voici ce que ça donne :
{ "aaa" : { "hosts": [ "127.0.0.1","192.168.0.12" ] }, "bbb" : { "hosts" : [ "10.0.0.1" ], "vars" : { "domain" : "bbb.fr" } } }
Inventaire en PHP
Pour ma part, j’ai choisi d’utiliser PHP pour construire mon inventaire dynamique.
Si vous avez déjà joué avec PHP, vous ne serez pas perdus. Le but du jeu est de construire un tableau, qu’on traduira ensuite en JSON avant de l’afficher.
Le bout de code PHP qui suit reproduit l’inventaire précédent en JSON, group_vars comprise :
#!/usr/bin/php <?php $tab['aaa']['hosts'][] = '127.0.0.1'; $tab['aaa']['hosts'][] = '192.168.1.12'; $tab['bbb']['hosts'][] = '10.0.0.1'; $tab['bbb']['vars']['domain'] = 'bbb.fr'; print json_encode($tab); ?>
J’en conviens, c’est pas super dynamique tout ça…
Inventaire en PHP, avec MySQL
Imaginons maintenant que nous avons une base de données à disposition. MySQL pour l’exemple, mais on peut imaginer la même chose avec tout autre moteur.
Soit le SQL suivant, représentant un table « hosts_groups » et une table « hosts » :
CREATE TABLE IF NOT EXISTS `hosts_groups` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `domain` varchar(255) NOT NULL, `etat` enum('1','0') NOT NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=MyISAM ; INSERT INTO `hosts_groups` (`id`, `name`, `domain` `etat`) VALUES (1, 'aaa', '', '1'), (2, 'bbb', 'bbb.fr', '1'); -- ---------------------------------------------------------- CREATE TABLE IF NOT EXISTS `hosts` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id_hosts_groups` int(10) NOT NULL DEFAULT '1', `ip` varchar(15) NOT NULL, `etat` enum('1','0') NOT NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=MyISAM ; INSERT INTO `hosts` (`id`, `id_hosts_groups`, `ip`, `etat`) VALUES (1, 1, '127.0.0.1', '1'), (2, 1, '192.168.1.12', '1'), (3, 2, '10.0.0.1', '1');
On y retrouve nos groupes, nos hôtes et notre group_vars domain.
Le code PHP suivant vous donnera une idée de comment récupérer les données et les mettre en forme :
#!/usr/bin/php <?php $mysql_host = '127.0.0.1'; $mysql_user = 'ansible'; $mysql_pass = 'password'; $mysql_db = 'ansible'; $l = mysql_connect($mysql_host, $mysql_user, $mysql_pass) or die('echec de connexion au serveur MySQL : '.mysql_error()); mysql_select_db($mysql_db, $l); $q = 'SELECT g.name, g.domain, h.ip FROM hosts as h, hosts_groups as g WHERE g.id=h.id_hosts_groups AND g.etat=1 AND h.etat=1'; $r = mysql_query($q, $l) or die(mysql_error()); while($t = mysql_fetch_assoc($r)){ $out[$t['name']]['hosts'][] = $ip; $out[$t['name']]['vars']['domain'] = $t['domain']; } print json_encode($out); ?>
On s’approche de quelque chose d’exploitable par Ansible. Mais il manque encore un petit détail, les host_vars.
Les host_vars et _meta
Les host_vars sont des variables directement attribuées aux hôtes. Dans un inventaire statique, elles sont soit définies dans le fichier d’inventaire, soit dans un fichier yaml portant le nom de l’hôte et positionné dans /etc/ansible/host_vars/
Au lancement d’un playbook, Ansible va lire son inventaire, puis ira, pour chaque hôte concerné par le playbook et un par un, chercher les host_vars. Notre script doit donc pouvoir donner deux types d’informations différentes : la liste des groupes et hôtes, et les host_vars d’un hôte défini.
Pour pouvoir gérer ces deux scénarios, Ansible appelle notre script avec des arguments.
Pour construire son inventaire : –list
php inventaire.php --list
Pour récupérer les host_vars d’un hôte : –host <hostname>
php inventaire.php --host 127.0.0.1 php inventaire.php --host 192.168.1.12 php inventaire.php --host 10.0.0.1
Ce qui donnera au total quatre appels successifs puisque nous avons trois hôtes dans notre inventaire.
Un peu comme $_POST et $_GET, nous pouvons récupérer ces arguments grâce à PHP, et donc renvoyer des données différentes suivant leurs valeurs. Ces arguments sont mis dans un tableau nommé $argv :
#!/usr/bin/php <?php $mysql_host = '127.0.0.1'; $mysql_user = 'ansible'; $mysql_pass = 'password'; $mysql_db = 'ansible'; $l = mysql_connect($mysql_host, $mysql_user, $mysql_pass) or die('echec de connexion au serveur MySQL : '.mysql_error()); mysql_select_db($mysql_db, $l); if(!empty($argv[1]) && $argv[1] == '--host' && !empty($argv[2])){ # on génère les host_vars $q = 'SELECT h.ip, h.hostname, h.port_ssh FROM hosts WHERE h.ip=\''.$argv[2].'\' AND h.etat=1'; $r = mysql_query($q, $l) or die(mysql_error()); $t = mysql_fetch_assoc($r); $out['hostname'] = $t['hostname']; $out['ansible_port'] = $t['port_ssh']; } else{ # on génère l'inventaire complet $q = 'SELECT g.name, g.domain, h.ip FROM hosts as h, hosts_groups as g WHERE g.id=h.id_hosts_groups AND g.etat=1 AND h.etat=1'; $r = mysql_query($q, $l) or die(mysql_error()); while($t = mysql_fetch_assoc($r)){ $out[$t['name']]['hosts'][] = $ip; $out[$t['name']]['vars']['domain'] = $t['domain']; } } print json_encode($out); mysql_close($l); ?>
Note: Pour cet exemple j’ai utilisé deux champs qui ne sont pas définis dans mon SQL…
Comme je l’ai dit plus haut, notre script sera appelé autant de fois qu’il y a d’hôtes concernés par le playbook. Pour diminuer le nombre d’appels, nous pouvons générer une variable « _meta » qui regroupe toutes ces host_vars et la renvoyer avec l’inventaire complet.
La structure finale doit être du genre :
$out['_meta']['hostvars']['127.0.0.1']['hostname'] = 'aaalocalhost'; $out['_meta']['hostvars']['10.0.0.1']['hostname'] = 'myBhost'; $out['_meta']['hostvars']['10.0.0.1']['ssh_port'] = '22';
Vous avez maintenant un script exploitable par Ansible !
Mais on peut encore aller un peu plus loin…
Des hôtes, des groupes, des variables,…
Le JSON retourné est un gros tableau. On peut penser, et à juste titre, qu’on peut peupler notre tableau comme on le souhaite.
Imaginons qu’on ait besoin de déployer des comptes unix sur un groupe d’hôtes, c’est possible.
Pour ce faire, on va créer une table regroupant toutes les informations nécessaires pour créer un compte (identifiant, hash du mot de passe, id du groupe d’hôtes concerné, clé ssh publique, …, etat[1]).
Pensez à utiliser des jointures pour récupérer le nom du groupe :
$q = 'SELECT g.name as group, ... FROM hosts_groups as g, admins as a WHERE a.id_hosts_groups=g.id AND a.etat="1" AND g.etat=1'; $r = mysql_query(...); while($t = mysql_fetch_assoc($r)){ $tab[$t['group']]['vars']['admins_enabled'][$t['id']]['uname'] = $t['login']; $tab[$t['group']]['vars']['admins_enabled'][$t['id']]['password_hash'] = $t['password_hash']; $tab[$t['group']]['vars']['admins_enabled'][$t['id']]['cle'] = $t['cle'] # ... }
Pour utiliser cette variable « admins_enabled », je n’ai plus qu’à l’appeler dans une tâche :
- name: Admins account creation user: name="{{ item.value.uname }}" password="{{ item.value.password_hash }}" createhome=yes state=present shell=/bin/bash with_dict: "{{ admins_enabled }}"
Jusqu’où peut on aller
On peut faire exactement la même chose qu’avec un inventaire statique, mais sans la lourdeur de maintenir des fichiers à plat. Voici quelques idées :
- les vhosts apache,
- les dépôts git,
- les clés ssh des développeurs,
- les zones dns,
- …
Si une interface web est créée, on peut même envisager de mettre en place un système de hooks pour qu’un playbook soit lancé dès qu’il y a modification des données.
Quelques conseils
Le shebang
Le Shebang est la première ligne du script : #!/usr/bin/php
Elle est importante car elle permet au système de connaître l’interpréteur à utiliser pour exécuter le script.
Un argument en plus : –debug
Intégrer la gestion de ce troisième argument permet d’afficher les données sous un autre format plus lisible que le JSON. Un var_dump par exemple.
Les groupes
Généralement, on utilise des groupes fonctionnels, des groupes géographiques, et des groupes par rôles (serveurs web, serveurs de bases de données, etc.).
L’inventaire dynamique peut donner beaucoup plus de souplesse sur la façon dont on peut cibler des hôtes spécifiques.
Rien n’empêche de mettre un hôte dans plusieurs groupes.
Pour ma part, j’ai opté pour un structure qui peut être représentée comme ça :
# par rôles [web] www0 www12 [db] db5 db6 # par rôles dans un groupe fonctionnel [aaa_web] www0 [aaa_db] db5 [bbb_web] www12 [bbb_db] db6 # par groupes fonctionnels [aaa:children] aaa_web aaa_db [bbb:children] bbb_web bbb_sql
J’attribue mes group_vars en fonction des besoins. Mes comptes admins sont dans les groupes fonctionnels. Mais les vhost, dans les groupes « par rôle dans un groupe fonctionnel ». Et enfin les paquets à installer dans les groupes par rôle…
[1] Le champ état : admins_enabled vs admins_disabled
Je l’ai mis en gras mais je n’ai pas insisté dessus, et pourtant il est important.
Ansible est un outil de gestion de configuration et d’automatisation. On peut faire des choses. Mais on peut aussi en défaire…
Le champ état sur ma table admins peut me permettre de différencier un compte unix à créer d’un compte unix à supprimer.
Conclusion
Pour finir sur le sujet, je dirais que la seule limite est votre imagination.
Si vous avez des questions (je sais que je suis passé trèèèès rapidement sur certains points), n’hésitez pas à laisser un commentaire.
2 réflexions sur « Ansible – inventaire dynamique avec PHP et MySQL »
Bonjour, j’ai trouvé votre article intéressant pour plusieurs raisons : il est en français, il parle d’Ansible et fait le lien avec une approche dynamique du stockage des variables hosts, groups etc…
Ansible est un formidable outil pour la centralisation et la gestion de la configuration mais également un support incontournable pour le suivi de l’inventaire. Associé à Git, on peut complètement tracer l’évolution de son infrastructure.
Est-ce qu’on ne perd pas cette traçabilité en stockant dans mysql ? (pour ceux qui ne veulent pas commiter des mots de passe ou les stocker dans mysql, l’utilisation des vault est un vrai plus)
Bonjour et merci pour votre commentaire.
Un inventaire dynamique dans ce style est une application à part entière. On peut tout à fait la concevoir pour apporter une traçabilité de qui fait quoi, quand, comment, etc.
Je n’ai pas encore eu l’occasion d’utiliser vault.
Quoi qu’il en soit, il est possible de stocker les mots de passe chiffrés dans mysql et de les déchiffrer dans l’inventaire dynamique avec mcrypt ou phpseclib.