Laisser un commentaire

Monitorer les serveurs et services avec Sensu

Lorsque l’on opère des serveurs, il est impératif de s’assurer que tout fonctionne normalement. Sensu permet justement de nous alerter en cas de dysfonctionnement ou d’anomalie. Le maître mot : la proactivité. En d’autres termes, être au courant d’une défaillance avant que nos utilisateurs ne le remarquent. Intéressé ? Découvrons la puissance de Sensu !

Sensu permet d’effectuer des checks (des contrôles d’état) sur des serveurs et des services. C’est à dire qu’il s’assure que les services fonctionnent de manière normale, sinon il vous alerte. Il permet également de collecter des métriques telles que la charge CPU, l’utilisation de la mémoire ou des disques et de les rediriger vers des services tiers.

Bien d’autres outils existent, cependant, comme expliqué dans la définition du plan de monitoring, Sensu est moderne, performant et il dispose d’une interface simple et ergonomique. Par ailleurs, il a été conçu pour être agnostique en terme de langage. Ainsi, la plupart des checks et plugins d’autres systèmes de monitoring sont utilisables avec Sensu. Nous verrons qu’il est possible de facilement programmer nos tests dans le langage de notre choix.

ford t compared to tesla
Sensu est à Nagios ce que Tesla est à la Ford T. Ils répondent au même usage, différemment…

Installation

Sensu existe en deux versions légèrement différentes : la version communautaire, open source, et la version entreprise qui y adjoint quelques fonctionnalités. Nous utiliserons bien entendu ici la version open source.

Sensu se compose de plusieurs briques interdépendantes : d’un côté sensu-client, sensu-server et sensu-api, toutefois réunies en un seul package ; de l’autre, Uchiwa, la webapp de la version communautaire.

À cela s’ajoute les dépendances. Sensu a en effet besoin d’un moteur de stockage, il utilise pour cela Redis, et d’un transport pour faire converger les données des clients vers le serveur. Le transport est assuré au choix par RabbitMQ ou Redis.

Le serveur

Nous parlions juste au dessus du transport, lequel peut être soit Redis, soit RabbitMQ. L’avantage de Redis est qu’il est d’ors et déjà installé puisqu’il est nécessaire au stockage des données. Son gros inconvénient toutefois, est qu’il ne supporte pas le chiffrement. Même si on paramètre une authentification, le mot de passe est transféré en clair sur le réseau… moyen. RabbitMQ quant à lui, intègre directement TLS et est donc plus approprié. Cependant, il nécessite l’installation d’Erlang et ajoute des dépendances supplémentaires à notre système dont on aimerait autant se passer.

Redis secured

Si l’on veut utiliser Redis comme transport, deux solutions sont possibles :

C’est cette deuxième solution que nous allons mettre en place. Elle est simple et flexible. Par ailleurs, si vous disposez d’un réseau privé, vous n’aurez qu’à binder les communications sur la bonne interface et sauter la configuration ssh.

Côté serveur, rien de plus simple, il nous suffit de créer un utilisateur qui réceptionnera les connexions ssh :

# on l'appelle ssher,
# on désactive le mot de passe
# et on lui attribue un bash limité, le rbash
adduser ssher --disabled-password --shell=/bin/rbash

Il n’y a pas grand chose à dire ici, on créé l’utilisateur “ssher”, on désactive le mot de passe et on lui attribue un shell limité afin que l’utilisateur ne puisse pas faire grand chose sur notre serveur.

Il ne reste plus qu’à vous assurer que vous avez autorisé la connexion par clef ssh.

Sensu server

L’installation se fait dans notre exemple sur un serveur Ubuntu, elle sera la même sur une Debian. Attaquons !

# on s'assure que nos listings sont à jour et on installe Redis
apt-get update
apt-get install redis-server

# on ajoute la clef et le dépôt pour sensu
wget -q http://repositories.sensuapp.org/apt/pubkey.gpg -O- | apt-key add -
echo "deb http://repositories.sensuapp.org/apt sensu main" | tee /etc/apt/sources.list.d/sensu.list

# on actualise nos listings et on installe Sensu
apt-get update
apt-get install sensu

Voilà c’est installé. Il ne reste plus qu’à configurer tout ça. La configuration est en JSON et peut être scindée en plusieurs fichiers différents. Sensu scannera par défaut l’ensemble des fichiers /etc/sensu/conf.d/*. Il ira également chercher sa configuration dans /etc/sensu/config.json s’il est présent. J’utilise cette seconde option et réserve conf.d aux autres fichiers de configurations.

# /etc/sensu/config.json
{
    "redis": {
        "host": "127.0.0.1"
    },
    "transport": {
        "name": "redis"
    },
    "api": {
        "host": "127.0.0.1",
        "port": 4567
    }
}

Le JSON parle ici de lui-même. On indique à sensu-server comment se connecter à Redis, qu’il faut également l’utiliser comme transport et enfin le port et l’adresse d’écoute de l’API. Au besoin, pour d’autres options, voyez la doc de Sensu.

Uchiwa

Passons à l’installation et à la configuration d’Uchiwa. Uchiwa consiste en réalité en un petit serveur écrit en Go, lequel requête la sensu-api et expose les données à une interface en AngularJS. Pour l’installer, rien de plus simple :

apt-get install uchiwa

La configuration s’effectue dans le fichier /etc/sensu/uchiwa.json.

{
    "sensu": [{
        "name": "Sensu",
        "host": "127.0.0.1",
        "ssl": false,
        "port": 4567,
        "path": "",
        "timeout": 5000
    }],
    "uchiwa": {
        "host": "127.0.0.1",
        "port": 3000,
        "refresh": 10,
        "user": "mon-login",
        "pass": "mon-mdp"
    }
}

sensu fait ici référence au “datacenter” tel que présenté dans l’interface Uchiwa. On peut avoir plusieurs serveurs Sensu qui seront autant de datacenters. L’ensemble des serveurs seront réunis dans la même interface (vous notez d’ailleurs qu’il s’agit d’un array).

Chaque datacenter possède un nom, ici “Sensu”. Les informations relatives à sensu font donc référence aux paramètres de sensu-api, telle que nous l’avons configuré juste au dessus.

uchiwa permet ici de configurer l’adresse et port d’écoute, le taux de rafraichissement de l’interface, ainsi qu’éventuellement des utilisateurs et leurs permissions. Nous n’avons ici qu’un utilisateur qui sera l’administrateur. Vous trouverez plus de détails sur les configurations possibles dans la doc d’Uchiwa.

Les clients

Nous avons installé notre serveur mais il ne monitore pour l’instant rien du tout. Pour installer les clients, c’est très simple, sur chaque serveur à monitorer, il faudra installer le package sensu, comme nous l’avons fait précédemment pour le serveur (je vous avais dit que tout était dans le même package). Nous verrons ensuite qu’on ne démarre pas les mêmes services lorsqu’il s’agit du serveur ou du client.

Chaque client nécessite deux fichiers de configuration : /etc/sensu/config.json et /etc/sensu/conf.d/client.json. config.json est à un détail près le même que celui que nous avons configuré pour le serveur.

Nous allons monitorer un serveur web. Voyons tout d’abord nos fichiers de configuration :

# /etc/sensu/config.json
{
    "redis": {
        "host": "127.0.0.1"
    },
    "transport": {
        "name": "redis"
    }
}

Comme nous l’avons vu plus haut, le config.json est assez similaire entre le serveur et le client. Un seul détail change, nous n’avons plus le bloc api. Ce dernier étant uniquement utile pour le serveur, nous n’en avons pas besoin ici. Vous noterez qu’on définit l’adresse de Redis à localhost, c’est parce que nous allons rediriger le trafic local sur le port de Redis, vers notre serveur Sensu via un tunnel ssh.

# /etc/sensu/conf.d/client.json
{
    "client": {
        "name": "Web_server",
        "address": "9.8.7.6",
        "subscriptions": [
            "default",
            "webserver"
        ]
    }
}

Expliquons un peu tout cela. On attribue un nom et une adresse à notre client – le nom doit être unique et l’adresse est celle du client mais elle est purement informative – ainsi que les abonnements.

Sensu fonctionne sur un modèle pubsub d’abonnements. Ainsi, chaque client définit le type de checks auxquels ils veut souscrire. On pourra par exemple avoir webserver, database etc. Nous y reviendrons plus en détails juste après.

Tunnel ssh

On en parle depuis quelques paragraphes, il est temps de mettre ça en place. Vous l’avez compris, nous configurons nos clients comme si Redis était en local, puis nous faisons passer tout ce trafic à travers un tunnel ssh (donc chiffré) vers le serveur Sensu.

Tout d’abord, il faut que vous génériez des clefs ssh, puis que vous les exportiez afin que votre client puisse établir la connexion avec le server Sensu. Au cas où vous ayez besoin d’un rappel, tout est dans l’article sus-mentionné. Je génère les clefs directement pour l’utilisateur root sur le client, libre à vous d’en choisir un autre, mais il il doit être en mesure de faire de la redirection de ports.

Une fois que tout est fait, ouvrons notre tunnel :

ssh -nNT -f -L6379:127.0.0.1:6379 ssher@ip_sensu_server

On redirige le port local 6379 (Redis), vers le port distant 6379 via l’utilisateur “ssher” du serveur à l’ip indiqué. Les otpions -nNT permettent de : -N ne pas exécuter de commandes distantes ; -n empêcher la lecture depuis stdin (utile pour exécuter ssh en backgound) ; -T ne pas attribuer de tty à l’utilisateur distant. -f et -L indiquent qu’il s’agit de Forwarding d’un port Local.

Tout devrait fonctionner, vous pouvez constater que ssh tourne en faisant ps aux | grep ssh et que la connexion est ouverte en faisant netstat -nap | grep 22. Seul inconvénient, la connexion pourrait sauter… On va donc s’assurer qu’elle reste toujours ouverte en utilisant le programme autossh. Ce petit outil vérifie constamment que la connexion est ouverte et la relance en cas de besoin.

# on installe la bête
apt-get install autossh

# on configure autossh pour ouvrir notre tunnel au démarrage
# dans /etc/rc.local
# juste au dessus de "exit 0"
autossh -M 0 -N -f -L6379:127.0.0.1:6379 ssher@ip_sensu_server

-M 0 permet de désactiver le monitoring autossh et de ne relancer la connexion qu’à la fermeture de ssh. Direction le man autossh si vous désirez en savoir d’avantage. rc.local est appelé directement par root, pensez à utiliser sudo si vous souhaitez établir la connexion depuis un autre utilisateur.

Petite parenthèse, il est tout à fait possible – c’est même indiqué – de monitorer le Sensu server. Vous n’avez qu’à renseigner le client.json, le reste est déjà en place. Évidemment, nul besoin de tunnel ssh puisque Redis est déjà sur la machine locale.

Démarrage

Plus que deux minuscules étapes avant d’ouvrir Uchiwa dans notre navigateur. Il faut configurer les services pour qu’ils se lancent automatiquement au démarrage.

# on s'assure que tous les fichiers de conf appartiennent à sensu
chown -R sensu:sensu /etc/sensu

# pour le serveur, on lance au démarrage :
# sensu server, api et uchiwa
update-rc.d sensu-server defaults
update-rc.d sensu-api defaults
update-rc.d uchiwa defaults

# pour les clients, seulement sensu-client
update-rc.d sensu-client defaults

Voilà la différence dont je parlais précédemment. Clients et serveur partagent le même package, mais sur les clients, on ne lance que le sensu-client.

Vous avez peut-être noté qu’on configure le lancement au boot avec System V alors qu’à partir de Debian 8 et dérivés (Ubuntu 16.04 notamment), le standard est systemd. Sensu ne possède pas de script systemd pour le moment. Mais n’ayez crainte, car systemd sait convertir automatiquement les scripts init.d au besoin.

On démarre tout ce petit monde avec un grand coup de service :

# serveur
service sensu-server start
service sensu-api start
service uchiwa start

# client
service sensu-client start

Voilà, vous n’avez plus qu’à vous rendre sur l’ip de votre serveur sur le port 3000 et hop, vous êtes sur l’interface Uchiwa. Je vous encourage quand même à mettre un nom de domaine là dessus, à configurer un reverse proxy pour profiter confortablement d’une adresse plus clean et pourquoi pas, chauffons nous, passer tout ça en ssl.

Uchiwa supporte le SSL, mais sans reverse il vous faudra ruser si vous souhaitez autre chose que le 3000 (voyez la partie Node sur le port 80 dans l’article sur Nodejs). De plus, vous ne pourrez héberger alors qu’un seul domaine sur ce serveur. Bref, c’est un autre débat, je ne vais pas expliquer tout ça ici… Nous avons encore du pain sur la planche !

Interface Sensu

interface d'uchiwa
Comme énoncé, l'interface d'Uchiwa est simple et efficace

On distingue six onglets dans le menu de gauche :

Nous avons fait le tour. Peuplons maintenant cette interface avec des informations utiles, j’ai nommé les checks !

Les checks

Je pense que vous commencez à le comprendre, un check est une commande exécutée par le client Sensu et qui vise à définir l’état d’un servie ou, comme expliqué en introduction, collecter une métrique. Chaque check doit retourner un exit code qui définit l’état du service :

Les checks peuvent également retourner des données sur stdin ou stderr. Ces données seront affichées dans Uchiwa pour les checks d’état et elles serviront à récolter les données dans le cas de metric checks.

Le modèle pubsub

Nous en avons rapidement parlé, Sensu s’articule autour d’un modèle de souscriptions. Ainsi, vous définissez les checks sur le serveur Sensu et vous attribuez à chacun les subscribers, c’est à dire le type de client qui devra exécuter ce check. Ensuite, lors de la configuration, chaque client spécifie le type de subscriptions auquel il s’abonne. Ainsi, on pourra par exemple définir des checks comme défaut (cron, iptables, mail), et d’autres plus spécifiques webserver, database etc.

Ce système apporte une grande souplesse car chaque check peut se voir attribuer plusieurs subscribers et chaque client peut avoir plusieurs subscriptions, prenons un exemple concret. Je définis sur mon serveur deux checks : “nginx” et “mysql”. nginx s’appliquera à la fois à mes loads balancers et à mes serveurs web, il aura donc "subscribers": ["loadbalancer", "webserver"]. Le check mysql s’appliquera uniquement aux serveurs de base de données : "subscribers": ["database"].

Sans surprise, mon client load balancer sera configuré pour s’abonner aux checks de type “loadbalancer” subscriptions": ["loadbalancer"]. Mon serveur web quant à lui, fait aussi office de base de données, no problem : subscriptions": ["webserver", "database"]. Vous voyez la puissance ?

Enfin, sachez qu’il existe aussi des checks dit standalone. Vous les définissez directement sur le client et ils s’exécutent indépendamment des checks publiés par le serveur. Cette possibilité offre une alternative décentralisée aux abonnements. Elle peut d’ailleurs s’utiliser aussi bien en complément que comme stratégie principale. Je n’en parlerai pas plus dans cet article, aussi, je vous laisse avec la doc si nécessaire.

Créer des checks

Nous l’avions dit en introduction, la grande force de Sensu est de permettre d’écrire des checks dans tout type de langage. Nous allons pour l’exemple utiliser des scripts bash. Facile pour de simples tâches, bash est installé par défaut sur tous les serveurs, il n’y a donc aucun prérequis.

Ce premier check vérifie que le service cron est bien lancé.

#!/bin/bash

# on filtre avec grep à la recherche de la ligne contenant
# "active (running)"
cron=`service cron status | grep "active (running)"`

# si la ligne n'est pas trouvée, alors la variable est vide
if [ -z "$cron" ]
then
    # on génère un message d'erreur et un exit code critial (2)
    echo "Ooops, cron has stopped"
    exit 2
else
    # ici message de succes et exit code à 0
    echo "All good!"
    exit 0
fi

Ce check ci va s’assurer que notre serveur web tourne et qu’il répond sans erreur.

#!/bin/bash

# on requête le serveur en localhost en ne demandant que le header
# on filtre la réponse pour ne garder que le ligne avec "HTTP"
web=`curl -s -D - localhost -o /dev/null | sed -n '/HTTP/p'`

# si la ligne en question contient le code 200, ok
if [[ "$web" == *200* ]]
then
    echo "Webserver up & running"
    exit 0

# sinon, il y a un problème
else
    echo "Houston, we have a problem"
    exit 2
fi

Les fichiers de check doivent évidemment être exécutables et présents sur chaque serveur à contrôler. Ils sont à mettre dans /etc/sensu/plugins/. Une fois cela fait, nous allons les déclarer auprès du serveur via le fichier de configuration /etc/sensu/conf.d/checks.json.

Notez que ces deux checks sont donnés à titre d’exemple. J’en ai réunis quelques-uns sur un Gist, dont ces deux là mais un peu plus travaillés. Si vous voulez monitorer Mongo, j’ai aussi écrit un module nodejs à cet effet.

{
    "checks": {
        "cron": {
            "command": "cron.sh",
            "interval": 60,
            "handlers": ["default"],
            "subscribers": ["default"]
        },
        "httpd": {
            "command": "httpd.sh",
            "interval": 60,
            "handlers": ["default"],
            "subscribers": ["webserver"]
        }
    }
}

Notre fichier contient ici les deux checks que nous avons précédemment écrit. Chaque check possède un nom et des paramètres, passons ces derniers en revue :

`command`
La commande à exécuter. Vous noterez que les exécutables présents dans `plugins` sont automatiquement intégré au path de Sensu. On peut donc ici utiliser des chemins relatifs.
`interval`
L'intervalle auquel il faut exécuter le check.
`handlers`
L'action à effectuer en cas d'alerte (ou pour à appeler dans le cas d'un metric check).
`subscribers`
À quel type de serveur le check se destine.

Le socket input

Il s’agit d’un type de check un peu particulier. Ni subscritpion, ni standalone… chaque client Sensu ouvre un socket TCP sur le port 3030. Ainsi, tous les programmes ont la possibilité d’y poster le résultat d’un check au format JSON.

D’une part, on ne peut pas tout checker, mais en cas de pépin important, un service peut s’appuyer sur Sensu pour lever une alerte (problème de connexion à une BDD ou API externe etc) ; d’autre part, il est possible de préciser une période après laquelle une alerte sera automatiquement levée si un nouveau check result n’a pas été publié. Ce qui est précieux notamment pour les politiques de sauvegardes automatiques !

Dans un exemple similaire à celui de ce lien, il suffit d’accompagner la sauvegarde d’un check avec TTL de 24 heures, pour qu’une alerte soit levée si aucun autre n’est publié à cette issue. En une ligne, voici ce que ça pourrait donner :

echo '{"name": "backup_desktop", "ttl": 86400, "output": "rsync back up completed", "status": 0}' | nc localhost 3030

Tellement simple, mais diablement efficace !

Les handlers

Lorsque qu’un évènement est levé (une erreur en général, ou un metric check), il faut exécuter une action. Sensu appelle alors le ou les handlers définis dans la configuration des checks. Vous devez donc définir le ou les handlers que vous voulez utiliser. Dans le check précédent, nous avons spécifié le handler default pour cron, et httpd. Ces handlers se configurent dans /etc/sensu/conf.d/handlers.json.

{
    "handlers": {
        "default": {
            "type": "set",
            "handlers": ["notify"]
        },
        "notify": {
            "type": "pipe",
            "command": "/etc/sensu/handlers/notify.js"
        }
    }
}

Il existent plusieurs types de handlers. Nous nous servirons ici du pipe, qui redirige la sortie du check vers une commande, et de set qui permet de regrouper plusieurs handlers pour les actionner tous à la fois. Dans l’exemple ci-dessus, le groupe default ne sert pas à grand chose puisqu’il revient au même que d’appeler directement notify. Néanmoins nous gagnons en flexibilité car si nous souhaitons par la suite rajouter un handler à tous nos checks utilisant default, nous n’aurons qu’une seule ligne à modifier.

Dans notre exemple, Sensu pipe simplement le résultat du check à notify.js. D’ailleurs ce merveilleux petit handler que j’ai écrit se charge d’envoyer les notifs à la fois par mail et via Slack.

De nouveau, assurez-vous bien que tous les fichiers appartiennent à Sensu, les checks et les handlers doivent aussi être exécutables. Finalement, redémarrez le serveur.

chown -R sensu:sensu /etc/sensu
chmod +x /etc/sensu/plugins/*
chmod +x /etc/sensu/handlers/*

service sensu-server restart

Vous devriez voir vos checks apparaître dans Uchiwa.

Conclusion

Avant de conclure, sachez qu’il est recommandé d’augmenter la limite des open files de Redis à 65536. Si vous ne monitorez que quelques serveurs, cela n’a sans doute aucun intérêt, mais si votre parc est plus conséquent, vous éviterez ainsi d’avoir une erreur du type Too Many Open Files. Voyez mon article sur ulimit si vous ne savez pas comment faire.

Ouf, c’était intense ! Vous êtes maintenant en possession d’un système robuste et vous avez la certitude d’être au courant si un problème survient. Cependant, comme je l’ai évoqué en introduction, Sensu est capable de collecter des données métriques, et c’est justement ce qu’il nous manque pour avoir un aperçu globale de notre infrastructure. Ça tombe bien, c’est justement l’objet du prochain article !

Avant de rattaquer tête baissée, n’hésitez pas à faire part de vos commentaires. D’ailleurs, comment faisiez vous et qu’utilisiez-vous jusque là ?

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```