Ansible – inventaire dynamique avec PHP et MySQL

Ansible – inventaire dynamique avec PHP et MySQL

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 »

  1. 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)

  2. 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.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.