Ansible, construire un dictionnaire depuis un script local

Un billet rapide sur une utilisation particulière d’Ansible.

Le besoin

J’ai besoin de déployer des comptes utilisateur sur différentes machines cibles, dans des contextes différents, avec des paramètres différents. Je pense qu’il y a plusieurs solutions pour faire cela avec Ansible 1, mais celle que j’expose ici m’a paru élégante.

Un des critères est de ne pas stocker le mot de passe dans le playbook Ansible, ou dans les variables, mais dans une base de données externe, et de permettre à l’utilisateur de changer son mot de passe et faire qu’il soit déployé partout 2.

Le besoin peut s’exprimer comme cela :

  • user1 :
    • mot de passe : secret1
    • machines cibles : host1, host2
    • sudoer : oui
    • accès VNC via reverse proxy : oui
    • port WAN : 5981
    • port VNC : 5901
  • user2 :
    • mot de passe : secret2
    • machines cibles : host1, host4
    • sudoer :non
    • accès VNC via reverse proxy :oui
    • port WAN : 5982
    • port VNC : 5902

Pour chaque rôle, les variables utiles sont différentes. Dans mon cas, j’ai un rôle pour programmer un reverse proxy sur un serveur et le VNCServer sur une autre machine, mais les données doivent être communes.

La solution

J’utilise les possibilités d’Ansible de :

  • lancer un script en local sur le serveur qui gère les playbooks
  • récupérer la sortie standard de ce script
  • en faire des « facts » pouvant être utilisé dans le playbook
  • Comme cela, je peux avoir un ensemble de variables pour chaque utilisateur dépendant du contexte d’utilisation sans avoir à redéfinir les dites variables dans plusieurs fichiers.

    J’ai donc un script « get_user.pl » qui prend en argument :

    • un contexte correspondant au rôle dans lequel il est utilisé
    • un nom d’hôte correspondant au serveur qu’Ansible est en train de traiter

    et avec cela me donne exactement la configuration de variables et d’attributs nécessaires au contexte/rôle, sous forme d’un objet JSON écrit sur la sortie standard.

    Par exemple, pour le rôle du reverse proxy VNC

    - name: Get users for VNC
      local_action: shell bin/get_users.pl VNC "{{ ansible_hostname }}"
      register: local_users
    
    - name: set fact
      set_fact:
        vnc_users: "{{local_users.stdout|from_json}}"
    
    - name: Generate nginx config file
      template: src="nginx/vnc_proxy.j2" dest=/etc/nginx/sites-available/vnc_proxy
      sudo: yes
      notify: restart nginx
    
    ...
    

    et le template peut contenir du code comme :

    {% for item in vnc_users %}
    
        ....
    
    {% endfor %}
    

    Je suis ainsi sûr d’avoir les variables correspondant au contexte voulu, à condition que le script de génération soit correct.

    Notes   [ + ]

    1. avec des groupes et des fichiers de variables par groupe, par exemple
    2. ce point peut être traité avec un annuaire central, en faisant que chaque application aille s’authentifier avec les informations de l’annuaire. Ce n’est pas possible dans mon contexte cible, donc j’ai éliminé cette possibilité.

InfluxDB requêtes avancées

Le précédent article montrait la mise en place d’InfluxDB, son alimentation via collectd et quelques requêtes élémentaires. Nous allons un peu plus explorer le langage de d’interrogation, ce qui nous sera utile pour la construction de tableaux de bords avancés avec Grafana (ou autre).
Comme nous l’avons vu, il est possible d’utiliser les trois interfaces (ligne de commande, application web ou requêtes HTTP) pour tester les exemples. Pour ma part, les exemples ci-dessous ont été réalisés avec le mode ligne de commande.

Concepts et définitions

Il faut maintenant revenir sur quelques concepts clés d’InfluxDB pour nous aider à comprendre la manière de construire les requêtes. Je reprends ici l’introduction faite dans la documentation, en gardant les termes clés en anglais car nous les retrouverons dans les requêtes et qu’une traduction serait plus perturbante qu’utile.

InfluxDB est organisée en « time séries » contenant des « points » composés de :

  • un « timestamp » donnant le moment de capture et exprimé en nano-secondes
  • un « measurement » qui permet d’identifier le point. Il faut penser à cela comme un nom de table dans une base de données SQL.
  • un ou plusieurs « fields » qui contiennent les valeurs mesurées
  • et, optionnellement, des « tags » qui permettent de qualifier les valeurs mesurées

Si nous reprenons notre requête sur l’utilisation du CPU :

> select * from cpu_value limit 1
name: cpu_value
---------------
time			host			instance	type	type_instance	value
1460882128927840000	influxdb.lutra.io	0		cpu	user		8812

on retrouve :

  • le « measurement » : c’est le nom « cpu_value » qui nous donne accès aux points
  • le temps : « time »
  • les tags : « host », « instance », « type » et « type_instance »
  • le « field » : « value » qui contient la mesure

Un autre exemple, tiré de la première version d’un module dédié aux NAS Synology, montre une autre organisation :

> select * from cpu_load limit 2
name: cpu_load
--------------
time			host	long	mid	short
1461138964000000000	lutra	1.01	1.15	0.66
1461139191000000000	lutra	0.95	0.98	1.14

Là, nous avons un seul « tag » (host) et plusieurs « fields » (long, mid et short). À première vue, il n’est pas évident de différencier les « tags » des « fields », mais il existe une requête qui permet d’afficher les « tags » d’un « measurement » :

> show tag keys from "cpu_value"
name: cpu_value
---------------
tagKey
host
instance
type
type_instance

Si on fait une analogie avec une base SQL, on peut voir le « measurement » comme étant le nom d’une table, qui aurait toujours le temps comme index primaire, et des colonnes de différents type : les « tags » qui sont indéxés et les « fields » qui ne le sont pas. Cela signifie que l’on peut sélectionner certains points en fonction de la valeur d’un ou plusieurs « tags » alors qu’on en peut pas le faire (efficacement) en fonction d’un « field ».

L’analogie, s’arrête rapidement, car il n’y a pas de notion de « schéma » dans InfluxDB, chaque enregistrement (point) pouvant avoir une structure différente. Bien entendu, il est conseillé de bien choisir et respecter les tags si on veut pouvoir extraire les données de manière simple et performante.

Il y a bien d’autres notions intéressantes à découvrir, notamment sur la rétention des données, la réplication (pas utile sur un système avec un seul serveur), les « continous query » qui permettent de construire un autre « measurement » à partir d’une requête donnée et ce en continu. Pour tout cela, je vous renvoie à lecture de la documentation du produit.

Groupements et fonction basiques

Si l’on souhaite analyser des tendances, il peut être intéressant de regarder des intervalles beaucoup plus grand, de quelques jours à quelques semaines ou mois. Dans ce cas là, il n’est pas pertinent de garder l’ensemble des points car les temps de transferts et de tracé seront prohibitifs.

Par exemple, à partir de la base Synology, j’aimerais connaître le nombre de données récoltées en une semaine pour la mesure « load average ». Cette mesure est stockée comme montré ci-dessous :

> select * from cpu_load where host='lutra' and time > now() - 1d limit 5
name: cpu_load
--------------
time			host	long	mid	short
1462007522000000000	lutra	0.61	0.54	0.86
1462007583000000000	lutra	0.62	0.61	0.96
1462007643000000000	lutra	0.62	0.61	0.77
1462007703000000000	lutra	0.63	0.65	0.84
1462007763000000000	lutra	0.68	0.79	1.21

La sélection temporelle « time > now() -1d » permet d’isoler la dernière journée de données. Bien sûr on peut utiliser des valeurs strictes pour délimiter un intervalle de temps, comme dans :

> select * from cpu_load where host='lutra' and time >= '2016-04-29T00:00:00Z' and time < '2016-04-29T00:10:00Z'
name: cpu_load
--------------
time			host	long	mid	short
1461888002000000000	lutra	0.67	0.64	0.46
1461888062000000000	lutra	0.66	0.63	0.52
1461888122000000000	lutra	0.67	0.64	0.62
1461888182000000000	lutra	0.65	0.6	0.5
1461888242000000000	lutra	0.65	0.61	0.62
1461888302000000000	lutra	0.66	0.64	0.76
1461888362000000000	lutra	0.65	0.6	0.52
1461888422000000000	lutra	0.63	0.56	0.42
1461888482000000000	lutra	0.63	0.58	0.59
1461888542000000000	lutra	0.65	0.63	0.74

Donc notre comptage sur la semaine (7 jours) s’exprime comme suit :

> select count(short) from cpu_load where host='lutra' and time>now() - 7d
name: cpu_load
--------------
time			count
1461488903750090303	10080

Sans surprise la valeur est égale à 10080, sachant que j’enregistre un point toute les minutes, soit 24 * 60 * 7 valeurs.

Maintenant, si je veux le décompte par jour, je fais un groupement sur le temps.

> select count(short) from cpu_load where host='lutra' and time >= '2016-04-25T00:00:00Z' and time < '2016-05-01T00:00:00Z' group by time(1d)
name: cpu_load
--------------
time			count
1461542400000000000	1440
1461628800000000000	1440
1461715200000000000	1440
1461801600000000000	1440
1461888000000000000	1440
1461974400000000000	1440

Plus intéressant, je souhaite la valeur moyenne, minimum et maximum par jour de ma charge système :

> select mean(short),min(short),max(short) from cpu_load where host='lutra' and time >= '2016-04-25T00:00:00Z' and time < '2016-05-01T00:00:00Z' group by time(1d)
name: cpu_load
--------------
time			mean			min	max
1461542400000000000	0.7479166666666659	0.06	3.61
1461628800000000000	0.7288749999999988	0.06	3.75
1461715200000000000	0.750694444444444	0.05	3.88
1461801600000000000	0.7310625000000006	0.06	4.22
1461888000000000000	0.7703680555555557	0.04	4.22
1461974400000000000	0.8268125000000012	0.08	4.67

Il existe d’autres fonctions d’agrégation de données ainsi que la possibilité de faire des groupements sur autre chose que le temps, comme montré ci-dessous :

> select mean(short),min(short),max(short) from cpu_load where time >= '2016-04-25T00:00:00Z' and time < '2016-05-01T00:00:00Z' group by  host
name: cpu_load
tags: host=lutra
time			mean			min	max
----			----			---	---
1461542400000000000	0.7592881944444436	0.04	4.67

name: cpu_load
tags: host=sam
time			mean			min	max
----			----			---	---
1461542400000000000	0.7407083333333324	0.02	5.37

Nous verrons d’autres exemples dans l’exploitation par Grafana.

Compteurs et dérivées

Parmi les valeurs remontées par collectd, il existe des compteurs, c’est à dire des valeurs qui sont continuellement incrémentées. C’est le cas, par exemple, du nombre d’octets transmis sur une carte d’interface réseau, comme montré sur la capture ci-dessous.
Capture du 2016-05-01 09:23:29

ou l’extrait de la requête :

> select value from interface_rx where host='influxdb.lutra.io' and instance='eth0'
                                   and type = 'if_octets' and time > now() - 10m
name: interface_rx
------------------
time			value
time			value
1462086943726727000	1.85962207e+09
1462087003726655000	1.859742904e+09
1462087063726607000	1.859860587e+09
1462087123726531000	1.859980626e+09
1462087183726419000	1.86009354e+09
1462087243726523000	1.86022071e+09
1462087303726635000	1.860333241e+09
1462087363727313000	1.860448405e+09
1462087423726677000	1.860563382e+09
1462087483726813000	1.860691435e+09

Telle quelle, la valeur présente peu d’intérêt car ce qui nous intéresse, c’est le débit réseau, donc le nombre d’octets reçus ou envoyés par seconde.

On peut déjà avoir la différence entre chacune des valeurs et la précédente :

> select difference(value) from interface_rx where host='influxdb.lutra.io' and instance='eth0' and type = 'if_octets' and time > now() - 10m
name: interface_rx
------------------
time			difference
1462087003726655000	120834
1462087063726607000	117683
1462087123726531000	120039
1462087183726419000	112914
1462087243726523000	127170
1462087303726635000	112531
1462087363727313000	115164
1462087423726677000	114977
1462087483726813000	128053

Cette requête nous montre donc le nombre exact d’octets reçus entre les mesures. Du coup, on perd un point de mesure sur l’intervalle considéré (ici on passe de 10 à 9) car il faut 2 points pour faire une différence.

On sait maintenant qu’il y a eu 120834 octets reçus dans le premier intervalle, entre les temps 1462087003726655000 ns et 1462086943726727000 ns, soit pendant 59,9999 secondes. Cela nous donne donc un débit de 2013 octets/seconde.
Le calcul que nous venons de faire est une dérivée df/dt qui représente bien la variation d’une fonction pendant un intervalle de temps. InfluxDB nous propose une fonction qui calcule cette dérivée sur l’ensemble des points :

> select derivative(value) from interface_rx where host='influxdb.lutra.io' and instance='eth0' and type = 'if_octets' and time > now() - 10m
name: interface_rx
------------------
time			derivative
1462087003726655000	2013.9024166829001
1462087063726607000	1961.3849024412552
1462087123726531000	2000.6525341598765
1462087183726419000	1881.9035128865573
1462087243726523000	2119.496326206368
1462087303726635000	1875.5131657087572
1462087363727313000	1919.3783110250854
1462087423726677000	1916.3036461519825
1462087483726813000	2134.211829119854

Cela représente bien ce que nous souhaitons obtenir :
Capture du 2016-05-01 09:44:06

Il est temps maintenant mettre tout cela en image avec Grafana et nous verrons cela dans un prochain article.

NAS Synology et InfluxDB

Cet article présente un petit script permettant d’intégrer les paramètres de fonctionnement d’un NAS Synology dans InfluxDB. Ce script fait partie d’un ensemble d’outils regroupés dans un paquet commun influxdbtools (https://github.com/gpolart/influxdbtools).

Après un git clone du dépôt, il suffit de recopier le script synology/monitoring.pl dans un répertoire du NAS, soit par le gestionnaire de fichier, soit par un scp ou soit par une copie directe si vous avez un volume monté sur votre système.

Par exemple, on peut le déposer dans le répertoire « home » de l’administrateur du NAS. Il faut connaître le chemin d’accès exact du script dans le synology, ce qui peut s’obtenir via le gestionnaire de fichiers avec un clic droit sur le nom pour visualiser les propriétés :
Capture du 2016-04-24 18:39:10

Il faut vérifier que le fichier est bien exécutable :

Capture du 2016-04-24 18:41:21

Pour programmer les relevés d’informations, on peut utiliser le « Planificateur de tâches » du NAS, accessible à partir du tableau de bord. Il suffit de créer une nouvelle tâche planifiée de type « Script défini par l’utilisateur »

Capture du 2016-04-24 18:45:33

Ensuite il faut planifier le délai de répétition ainsi que la commande à exécuter. Le script prend trois paramètres :

  • le nom du serveur InfluxDB (ou @IP si vous n’avez pas de nom)
  • le port d’accès à l’API (8086 classiquement)
  • le nom de la base de données (il faut l’avoir créer auparavant, voir plus loin)

Capture du 2016-04-24 18:46:25

Capture du 2016-04-24 18:47:39

Capture du 2016-04-24 18:46:47

Au bout du délai programmé, les données commencent à arriver dans la base.

Création de la base

Cela s’effectue simplement en se connectant sur l’interface d’InfluxDB (port 8083) et en tapant la ligne suivante dans la zone de saisie, suivie de la touche « Entrée » du clavier:

create database synology

En cas de problèmes

Il faut valider que le script s’exécute correctement en se connectant en ssh sur le NAS et en lançant la commande à la main, telle que vous l’avez saisie dans la zone « Script » de la tache planifiée.

Il ne doit y avoir aucun message affiché.

Si il y a des erreurs, il faut vérifier :

  • que la base est bien créée
  • que le serveur InfluxDB est bien accessible
  • que vous avez Perl et les modules utilisés installés sur le NAS (test effectués en 6.X)