prometheus avec consul
Sur un environnement conséquent, la découverte automatique des services et applications à intégrer à prometheus est indispensable. Cet article présente l’implémentation possible avec consul.
Dans notre précédent article, nous avons connecté notre web service à monitorer avec prometheus et grafana. La configuration était manuelle et statique ce qui ne permet pas le passage à l’échelle. Voici comment automatiser la configuration dans prometheus sur la base du système clé/valeur consul.
Une vidéo est disponible en fin d’article si vous voulez voir le résultat sans mettre les mains sur le clavier, notamment les enregistrements dynamiques et l’utilisation du DNS pour trouver les services.
Si vous n’avez pas vu toute la série sur le sujet, voici la table des sujets abordés dans les différents articles :
- prometheus - concepts de base : introduction à prometheus, principes de fonctionnement, usages
- prometheus - bien démarrer en python : comment instrumenter son code python afin de fournir les services de métrologie pour prometheus
- prometheus et grafana : intégration de prometheus avec l’outil de dataviz grafana
- prometheus avec consul : intégration avec consul pour la découverte automatique des services à intégrer dans prometheus
- prometheus - bien démarrer en java : comment instrumenter du code java spring afin de remonter les métriques dans prometheus
Comme d’habitude, vous trouverez le code sur mon git dédié au lab prometheus, je vous laisse consulter les articles précédents pour la manip d’installation si besoin.
Consul
Sans rentrer dans le détail, consul est un outil permettant de partager des informations sur des services et des valeurs stockées dans des clés. Le système est distribué, réparti et redondant, se base sur les principes de fonctionnement du DNS et est interrogeable également via des API. Il se rapproche dont de etcd et dans une moindre mesure de redis.
Dans ce lab, nous avons besoin de faire découvrir à prometheus les cibles à intégrer automatiquement dans sa supervision et ainsi ne pas avoir à toucher à la configuration. Dans une approche de type function as a service, c’est indispensable. Ce qui est vrai pour prometheus l’est également pour la consommation des services, ceux-ci étant enregistrés dans un système de DNS on peut facilement les trouver dans notre infrastructure. Sans trop d’effort les applications consommatrices pourront via des requêtes DNS trouver les services et les interroger.
Dans le cadre de notre labo, le système consul est installé sur la base d’un noeud serveur isolé (pour de la redondance il faudra faire un peu mieux que cela, mais ce n’est pas l’objet ici). Sur chaque noeud portant nos services, nous installerons également un agent consul qui assurera la communication avec l’infrastructure globale et simplifiera le code de nos services. En effet, ceux-ci pourront communiquer avec un consul en local, que ce soit pour récupérer des informations, utiliser le service clé/valeur et bien entendu pour s’enregistrer dans l’annuaire pour prometheus.
La partie installation de consul sera donc à prendre en charge dans la construction des serveurs, il existe des modules ansible pour cela par exemple.
Principes de fonctionnement
- prometheus utilise le service consul pour récupérer la liste des cibles à monitorer
- le web service en python s’enregistre à son démarrage avec le consul en local, il se désenregistre également en s’arrêtant
- un système de health check est mis en place dans le consul pour valider la présence du web service, ceci est programmé au niveau du web service donc pas de configuration spéciale dans consul
Mise en place
Pour simplifier le suivi du lab et de la configuration, je vous communique les adresses IP de mon lab qui est composé de 2 serveurs :
- node01 : porte le consul serveur et le serveur prometheus
- probe-srv : prote un consul agent et le web service en python
consul
Sur le noeud serveur (node01 - 192.168.16.212), on démarre un serveur consul :
CONSUL_UI_BETA=true ./consul agent \
-bind 192.168.16.212 \
-client 0.0.0.0 \
-data-dir /tmp/consul/ \
-server -bootstrap \
-ui
- l’utilisation de la variable d’environnement
CONSUL_UI_BETA
est pour l’instant nécessaire pour profiter de la nouvelle version de l’interface graphique, activée avec-ui
, en fonction de la date de lecture de cet article vous pourriez ne pas en avoir besoin - bind spécifie l’adresse pour la communication du cluster
- client est utilisé pour les interrogations en DNS, ou API, ici on s’accroche à toutes les interfaces
- server et bootstrap pour démarrer le serveur
Une fois démarré, vous pouvez accéder à l’interface graphique sur le port 8500. Par défaut, seul le service consul
est enregistré sur le noeud local.
Sur le noeud applicatif (probe-srv - 192.168.16.205), on démarre un consul en mode agent et on l’intègre dans le cluster :
$ ./consul agent -bind 192.168.16.205 \
-data-dir /tmp/consul/ \
-join 192.168.16.212
- la valeur de join doit corresponde à l’adresse bind du serveur, avec un DNS consul intégré à notre DNS standard, on pourrait utiliser l’adresse retournée par
consul.service.consul.
, ce qui serait encore plus simple.
Vous pouvez vérifier dans l’interface d’adminstration que les 2 noeuds sont bien présents, avec leur nom et leur adresse IP respective. L’infrastructure consul est dynamique, l’arrêt du noeud applicatif le supprime immédiatement de la liste des noeuds consul.
prometheus
Sur la base des travaux que nous avons déjà mené sur prometheus, la seule modification à apporter est d’ajouter une section dans nos scraper en spécifiant d’utiliser consul pour s’informer des cibles à superviser (prometheus.yml
).
scrape_configs:
- job_name: consul
consul_sd_configs:
- server: 'localhost:8500'
services:
- pyTest
Dans notre cas, le serveur prometheus étant également installé sur le node01, on spécifie un serveur en local. La section services
permet de filtrer les cibles en fonction du nom du service renseigné.
On démarre notre serveur prometheus avec le fichier de configuration ainsi modifié, via une commande en ligne ou dans un conteneur. Dans son interface d’administration vous ne verrez pas de service en particulier, car bien que l’utilisation de consul soit en place, aucun service répondant au filtre de service n’est présent sur notre système.
Nous avons désormais toute l’infrastructure nécessaire, vous pouvez y ajouter un grafana si vous le souhaitez pour parfaire avec un peu de dataviz.
Instrumentation python
La partie intéressante arrive avec les modifications de notre web service python afin qu’il s’inscrive dans l’agent consul en local sur le serveur. Les sources sont dans le répertoire 03-python-hw-consul
.
Minimum
Les modifications minimales sont les suivantes (srv.py
):
import consul
CONSUL = consul.Consul()
CONSUL.agent.service.register("pyTest",
port=5000)
Si on lance notre web service avec ces modifications, il va alors s’enregistrer sur l’agent consul en local avec le nom du service et le port permetant de le joindre. C’est suffisant pour prometheus afin de récupérer l’information et de lancer automatiquement la récupération des métriques sur le end-point /metrics
.
Mais ceci n’est pas suffisant car le service ne disparait pas de l’annuaire à son arrêt ce qui va préter à confusion et n’est pas acceptable pour assurer de la redondance et de l’elasticité horizontale pour notre web service.
Service Check de consul
Pour cela, on ajoute un checker qui sera implémenté dans l’agent consul en local (d’où l’intérêt d’en disposer). Le code ressemble désormais à srv.py
:
1import consul
2CONSUL = consul.Consul()
3serviceChecker = consul.Check.http(url="http://127.0.0.1:5000/_checker",
4 interval="10s",
5 timeout="1s",
6 deregister="1m")
7CONSUL.agent.service.register("pyTest",
8 port=5000,
9 check=serviceChecker)
10
11@app.route('/_checker')
12def ws_checker():
13 return "ok"
- ligne 1 : définition du checker, on utilise un vérification de type HTTP (on peut également travailler au niveau TCP, au niveau docker ou encore via un script)
- ligne 2 à 4 : on interroge chaque 10 secondes, l’appel étant en local on limite le timeout et on supprime le service après une minute d’interruption du service
- ligne 7 : on ajoute le checker à notre enregistrement
- ligne 11 : on ajoute un end point spécifique pour ce check
Désormais, en cas d’arrêt du web service, la vérification régulière effectuée par l’agent consul va parmettre sa suppression de l’annuaire après une minute d’inactivité. Ceci permet de répondre au cas d’un crash de notre web service.
Désinscription
Afin de parfaire l’aspect dynamique de la supervision, il nous est possible d’ajouter une désinscription de l’annuaire à la sortie du web service (srv.py
):
1def trap_signal(sig, _):
2 """Exit signal for clean derigister from consul."""
3 CONSUL.agent.service.deregister(service_id="pyTest")
4 exit()
5
6signal.signal(signal.SIGTERM, trap_signal)
7signal.signal(signal.SIGINT, trap_signal)
Ici, dès que l’on stoppe le web service (CTRL-c), le service se désinscrit de l’annuaire et toute l’infrastructure se met à jour (consul local, consul serveur et prometheus).
Elasticité
La version proposée sur git intègre la modification du numéro de port TCP d’écoute du web service sur la ligne de commande, ceci permet de voir le comportement lorsque plusieurs services sont instanciés dans le cadre d’une élasticité horizontale.
Par exemple, en démarrant une instance sur le port 5000 du serveur probe-srv
, on peut observer dans le DNS :
$ dig @127.0.0.1 -p 8600 pyTest.service.consul SRV
;; ANSWER SECTION:
pyTest.service.consul. 0 IN SRV 1 1 5000 probe-srv.node.dc1.consul.
;; ADDITIONAL SECTION:
probe-srv.node.dc1.consul. 0 IN A 192.168.16.205
Les informations du service présentées en association du nom sont : le poids et la priorité, puis le port et enfin le serveur d’accueil. Celui-ci est résolu dans la section suivante pour éviter une nouvelle requête.
En en démarrant un second sur le même serveur, port 5001 :
$ dig @127.0.0.1 -p 8600 pyTest.service.consul SRV
;; ANSWER SECTION:
pyTest.service.consul. 0 IN SRV 1 1 5001 probe-srv.node.dc1.consul.
pyTest.service.consul. 0 IN SRV 1 1 5000 probe-srv.node.dc1.consul.
;; ADDITIONAL SECTION:
probe-srv.node.dc1.consul. 0 IN A 192.168.16.205
probe-srv.node.dc1.consul. 0 IN A 192.168.16.205
Enfin, en ajoutant un web service sur le serveur node01
:
$ dig @127.0.0.1 -p 8600 pyTest.service.consul SRV
;; ANSWER SECTION:
pyTest.service.consul. 0 IN SRV 1 1 5000 node01.node.dc1.consul.
pyTest.service.consul. 0 IN SRV 1 1 5000 probe-srv.node.dc1.consul.
pyTest.service.consul. 0 IN SRV 1 1 5001 probe-srv.node.dc1.consul.
;; ADDITIONAL SECTION:
node01.node.dc1.consul. 0 IN A 192.168.16.212
probe-srv.node.dc1.consul. 0 IN A 192.168.16.205
C’est là que l’on peut voir l’intérêt également pour les consommateurs de pouvoir bénéficier des informations maintenues dynamquement dans le système consul, puisque celui-ci est interrogeable par le DNS.
Intégration de consul au DNS
Un moyen assez simple de pouvoir interroger consul avec son client DNS standard (sans avoir à spécifier un serveur et un numéro de port donc) est de mettre en place sur les serveurs un mandataire DNS comme dnsmasq. On utilisera alors un relais DNS local sur le serveur, celui-ci faisant le tri des requêtes standards et de celles à destination du domaine consul.
qui seront redirigées en local sur l’agent consul.
Pour la configuration, suite à l’installation d’un dsnmasq, on peut simplement positionner dans le répertoire /etc/dnsmasq.d/
2 fichiers avec respectivement les lignes :
00-default :
server=192.168.16.115
ici mon serveur DNS par défaut.
10-consul :
server=/consul/127.0.0.1#8600
ici, pour le domaine consul.
vers l’agent consul en local.
Une fois ceci réalisé, les clients du web service python pourront simplement interroger le DNS afin de trouver à qui s’adresser, en effectuant une demande avant chaque appel, un système de partage de charge s’effectuera alors automatiquement sur l’ensemble des services démarrés.
Nous voici avec une infrastructure assez simple ne nécessitant pas de load balancer avec néanmoins du partage de charge et de la redondance. Cette solution simplifie également la gestion des ports de communication car cette information est également enregistrée dans le DNS, on simplifie donc grandement les fichiers de configuration et évite d’autant les erreurs d’environnement.
Homework
Si vous voulez aller un peu plus loin, vous pouvez regarder l’utilisation avec consul de son module de supervision (consul_exporter), ainsi que la supervision de la partie système avec node_exporter. Il est assez simple d’ajouter ces 2 services à la configuration de l’agent consul afin de disposer d’une supervision homogène de tous les noeuds porteurs d’application de notre système d’information.
Et voilà, en vidéo
Photo from Clint Adair