Table des matières

Prometheus, la tour de guet d'une infrastructure swarm

Contexte

Si vous avez lu mon précédant article sur traefik, vous connaissez déjà les problématiques de “volatilité” associées aux workload docker. Rappelons brièvement qu'à Geco-iT nous avons en gestion des clusters docker swarm, et donc des containers qui peuvent se trouver sur plusieurs nœuds différents, apparaître et disparaître, bref des infrastructures dynamiques. Cela apporte son lot d'avantages mais aussi d’inconvénients. Traefik nous solutionne la question du routage des flux jusqu’à ces services et de leur sécurisation via le SSL/TLS avec des certificats let's encrypt. La ou traefik ne nous aide pas, c'est dans la manière de superviser et d'obtenir des métriques sur ces services. Aujourd'hui nous utilisons un système de supervision fonctionnel mais sous un modèle un peu vieillissant : celui de nagios. Ce dernier nous contraint, à chaque ajout de serveur chez nous ou chez un client, à déclarer à la main un host et ses services. Une fois la configuration écrite, elle est chargée dans l'outil et restera fixe jusqu'au prochain update / reload. Pas pratique du tout dans un environnement qui peut se transformer comme avec swarm !

La réponse se trouve dans une technologie alien : prometheus

Geco-iT a porté son choix, comme beaucoup d'autres d'ailleurs, sur prometheus. Les raisons de ce choix son multiples :

Architecture du produit

Prometheus est un système basé sur le “pull”, c'est à dire que c'est lui qui se connecte aux agents pour récupérer les métriques et non l'inverse, comme dans le cas d'un système push. Pour cela il s'appuie sur 5 composants :

Un autre concept important à saisir c'est la notion de label! Dans prometheus chaque timeseries ( une timeseries design la valeur d'une métrique sur un intervalle de temps ) va se voir accrocher un ou plusieurs label(s) qui sont des groupes de clef:valeur qui vont pouvoir être utilisés pour différencier les timeseries entre elles ! Plusieurs labels sont fournis d'office, vous allez pouvoir les remanier et en ajouter !

Big brother is watching my lab

Pour illustrer les possibilités de prometheus nous allons le deployer sur un cluster swarm :

❯ docker node ls
ID                            HOSTNAME          STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
d011tsg2fpujizh6og10wub7b     docker-manager2   Ready     Active         Leader           20.10.6
610fs14p3cnpuc0fv15pgdwkv     docker-manager3   Ready     Active         Reachable        20.10.6
jgn4jxmdrlpa0hamzlvq6rim5     docker-worker1    Ready     Active                          20.10.6
ikoj4h0j1nactm1pcqabwhsrq     docker-worker2    Ready     Active                          20.10.6
qvn7z1j4yiwt1pmppczcp7jml     docker-worker3    Ready     Active                          20.10.6
u1i03hhiq2e4h0pebzm2y41wb *   geco-swarm        Ready     Active         Reachable        20.10.6

Voici la config à créer pour faire tourner notre premier prometheus

global:
  scrape_interval:     15s #A quelle frequence on recupère les metriques
 
  # On attache ces labels à tout les timeseries, ca permet de les différencier si on communique avec un système exterieur.
  external_labels:
      monitor: 'geco-swarm'
 
#les scrape configs sont le nerf de la guerre, c'est tout ce qu'on va vouloir interoger, on va definir ici un endpoint statique qui est prometheus lui même pour qu'on puisse le surveiller lui aussi et le tester
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

Et enfin la stack swarm qui va appeler notre config :

version: '3.3'
services:
  prometheus:
    image: prom/prometheus:latest
    command:
     - --config.file=/etc/prometheus/prometheus.yml #specification du fichier de config
     - --storage.tsdb.path=/prometheus_data         #le chemin ou l'on store les data
     - --storage.tsdb.retention.time=90d            #durée de retention des datas
    ports:
     - 9090:9090
    volumes:
     - prometheus-data:/prometheus_data
    networks:
     - metrics
    configs:
     -
      source: geco-prometheus
      target: /etc/prometheus/prometheus.yml # On remonte la config crée plus haut dans le container.
    deploy:
      placement:
        constraints:
         - node.role == manager # On demande explicitement à swarm un déploiement sur un nœud manager de cluster car sinon le socket docker ne sera pas accessible pour prometheus.

On deploie notre stack dans le swarm :

docker stack deploy -c prometheus.yml prometheus

Au lancement de la stack on va avoir accès à l'interface et pouvoir aller sur Status > Targets et ainsi voir notre seul point de collecte pour le moment qui est le prometheus lui même :

En allant sur l'onglet Graph et en sélectionnant le mode graph, on peut trace une métrique par exemple le temps cpu :

On se retrouve avec une droite qui ne fait que monter car la métrique est de type compteur. Ce compteur étant incrémenté sans fin, il faut lui appliquer la fonction rate() qui porte bien son nom pour obtenir quelque chose de lisible par un oeil humain.

De manière générale il faut toujours faire attention à se poser la question du type de data que l'on manipule pour éviter certaines surprises…

En avant les exporter !

Le daemon docker

On a une instance qui marche, elle s'automonitore, c'est beau mais ça nous avance pas à grand chose si on ajoute pas un peu de target… Pour se faire on va utiliser plusieurs outils, le premier c'est la fonction d'export metriques du daemon docker lui même ! Pour cela on va éditer le fichier de config daemon.json dans /etc/docker :

{
  "metrics-addr" : "0.0.0.0:9323",
  "experimental" : true
} 

Ensuite on restart le daemon :

sudo service docker restart

L’opération est à faire sur chaque node donc vous pouvez utiliser un outil de gestion de conf pour vous faciliter la vie. On peut ensuite vérifier que docker expose bien les métriques avec un simple navigateur, ça vous permet aussi de voir le format utilisé par prometheus pour lire les métriques, vous voyez comme il est simple d’implémenter sa propre solution vu qu'il suffit d'exposer du texte en HTTP :

Vient le moment d'indiquer à prometheus ou aller chercher les métriques, et c'est la qu'on va pouvoir utiliser son mécanisme de service discovery pour qu'il aille chercher les IPs des nœuds de notre cluster swarm tout seul. Je le ferai pas car l'article risque déjà d'être long mais si je supprime ou je déploie un node de plus, prometheus va le détecter seul ! Voici le morceau de config en question:

  - job_name: 'docker'
    dockerswarm_sd_configs:
      - host: "tcp://socket-proxy_socket-proxy:2375"  # On specifie la methode de connexion au socket docker, et le mode "nodes".
        role: nodes                                   # Le mode permet de choisir le type d'objet decouvert entre node, task et service.
    relabel_configs:
      # Fetch metrics on port 9323.
      - source_labels: [__meta_dockerswarm_node_address]   # Ici on indique que l'on veut remplacer l’adresse de collecte des métriques par les adresses des nodes docker
        target_label: __address__
        replacement: $1:9323
      # Set hostname as instance label
      - source_labels: [__meta_dockerswarm_node_hostname]  # et que l'on va remplir la valeur du label "instance" avec le hostname du node docker, pour les identifier facilement!
        target_label: instance

Si l'on retourne voir les target sur les webui de prometheus on retrouve bien tout nos nodes :

Cadvisor, le hibou aux grand yeux

Cadvisor, pour container advisor est un outil qui va collecter des métriques sur les containers lancés sur un node docker, on aura accès aux quantités de ressources utilisées, cpu, ram, network pour chaque containers. Nous allons donc ajouter un service, de type global à notre swarm, le mode global va permettre de déployer une instance du service en question sur chaque node du cluster. C'est souvent le cas pour les outils de supervision ou de logging ou l'on a forcement besoin d'un container par host pour pouvoir auditer l'infrastructure dans sa totalité.

  cadvisor:
    image: google/cadvisor:latest
    volumes:
     - /var/run/docker.sock:/var/run/docker.sock:ro    #Remonté de certaines volumes nécessaires à la collecte d'info de cadvisor
     - /:/rootfs:ro
     - /var/run:/var/run
     - /sys:/sys:ro
     - /var/lib/docker:/var/lib/docker:ro
    networks:
     - metrics
    deploy:
      mode: global
      labels:
        prometheus-job: cadvisor
        prometheus-job-port: 8080

Et mes nodes physiques dans tout ca ?

Avec les deux solutions précédentes on possède les informations nécessaires sur les containers mais pas sur les hosts eux-mêmes, pour combler ce manque on va utiliser le node exporter de prometheus qui est en quelque sorte l'agent pour les OS de prometheus. Il va permettre d'exporter les métriques de nos hôtes linux. Comme il existe sous forme de container et que nous sommes dans un cluster swarm on va aussi le déployer en tant que service comme cadvisor. Si vous préférez utiliser un binaire standalone c'est possible aussi car il est fourni par prometheus.

  node-exporter:
    image: prom/node-exporter:latest
    command:
     - --path.sysfs=/host/sys     #Definition des chemin des points de montage ci-dessous
     - --path.procfs=/host/proc
     - --collector.filesystem.mount-points-exclude=^/(dev|host|proc|run/credentials/.+|sys|var/lib/docker/.+)($$|/)
    environment:
      NODE_ID: '{{.Node.ID}}'
    volumes:
     - /proc:/host/proc:ro        #Remonté de certaines volumes nécessaires à la collecte d'info comme pour cadvisor
     - /sys:/host/sys:ro
     - /:/rootfs:ro
     - /etc/hostname:/etc/nodename
    networks:
     - metrics
    deploy:
      mode: global
      labels:
        prometheus-job: node-exporter
        prometheus-job-port: 9100

Config de prometheus pour scrapper nos deux autres exporter fraichement déployés

La dernière chose qu'il nous reste à faire c'est de dire à prometheus ou collecter les données de cadvisor et node-exporter, comme dans le cas du daemon docker on va pouvoir utiliser le service discovery mais on va pousser un peu plus la chose. Si vous reprenez les configs des services ci-dessus vous allez voir à la fin :

    deploy:
      mode: global
      labels:
        prometheus-job: cadvisor
        prometheus-job-port: 8080

et aussi

    deploy:
      mode: global
      labels:
        prometheus-job: node-exporter
        prometheus-job-port: 9100

J'ajoute ici volontairement deux labels de mon choix pour les utiliser dans le mécanisme de service discovery. La configuration suivante va permettre plusieurs choses :

  - job_name: 'swarm'
    dockerswarm_sd_configs:
      - host: "tcp://socket-proxy_socket-proxy:2375"         #Methode de connexion au socket docker
        role: tasks
    relabel_configs:
      # On garde seulement les containers qui sont en status désiré "running"
      - source_labels: [__meta_dockerswarm_task_desired_state]
        regex: running
        action: keep
      # On garde seulement les containers qui possèdent un label "prometheus-job"
      - source_labels: [__meta_dockerswarm_service_label_prometheus_job]
        regex: .+
        action: keep
      # On garde seulement les containers attachés au network swarm "metrics"
      - source_labels: [__meta_dockerswarm_network_name]
        regex: prometheus_metrics
        action: keep
      # On remplace le label "job" par le label "prometheus-job" que l'on définie nous mêmes dans la stack
      - source_labels: [__meta_dockerswarm_service_label_prometheus_job]
        target_label: job
      # Et enfin ci-dessous la petite suite d'opérations qui permettent de récupérer l'URL de la target en compilant nos deux labels
      - source_labels: [__address__]
        target_label: real_target
        regex: "([^:]+):\\d+"
      - source_labels: [real_target,__meta_dockerswarm_service_label_prometheus_job_port]
        separator: ":"
        target_label: __address__
        replacement: $1
      - source_labels: [__meta_dockerswarm_node_hostname]
        target_label: hostname
        replacement: $1

Nos targets dans la webui doivent se peupler encore un peu plus :

On retrouvera à la fois les cadvisor et les node-exporter, respectivement sur les bons ports grâce au système de label, à l'avenir pour monitorer un service swarm de plus il suffira d'ajouter les deux labels vu précédemment. Vous avez la déjà un bel exemple de ce que l'on peut faire avec le service discovery de prometheus !

Construction d'une petite requête dans le web ui de prometheus

On va se donner pour but de construire une requête pour avoir le pourcentage d'utilisation cpu du node “geco-swarm” avec le détail ( user, iowait, etc… ). Pour cela on va commencer par chercher “node” dans le module de construction de graph et vous allez voir qu'il y a un minimum d'auto complétion et c'est très pratique pour construire ses graphs

La on se retrouve avec les métriques de tout le monde, on va ajouter un filtre par label avec {label=“valeur”}

Prometheus va automatiquement vous proposez les labels disponibles, hostname correspond bien à ce qu'on veut faire, garder les métriques d'un seul node

Il va aussi nous proposer les valeurs disponibles pour ce label dans lesquelles on va retrouver notre nœud “geco-swarm”

On obtient la requête suivante :

Et effectivement après exécution on n'a plus que les graphiques du noeud “geco-swarm”

Maintenant ce qui nous embête c'est la métrique “idle” car ce que j'aimerai faire c'est stacker toutes les séries les unes sur les autres pour avoir le total d'utilisation CPU. Donc j'ai besoin de toutes les séries SAUF idle. Nous pouvons utiliser les labels pour EXCLURE. Très simple, dans le cas précédent on a fait {label=“valeur”}, donc on inverse avec {label!=“valeur”}, notez bien le “!”

Si on exécute à nouveau le requête on ne voit plus la série “idle”

On retrouve la particularité d'avant ou la série est un compteur donc une droite qui ne fait que monter, on va donc appliquer la fonction rate comme plus haut dans l'article et on va aussi multiplier le résultat par 100 avec “ * 100” pour avoir un pourcentage :

Et en exécutant, puis en passant le mode de graphing à “stack” avec le petit bouton à coté de “show exemplars” on retrouve bien un graphique stacké de tout les temps cpu sauf idle de la machine geco-swarm :

Pour conclure

J'espère vous avoir demontré une partie des possibilité de prometheus qui sont assez vastes finalement. Je pourrai faire un autre article uniquement sur les requêtes possibles avec le language PromQL ou encore sur le système des alertes que propose prometheus. Pour aller plus loin il faudrait aussi reprendre la configuration pour garder seulement les données que l'on veut collecter dans une soucis de place et de performance. On peut aussi envisager de connecter le prometheus à un grafana pour faire des beaux dashboards de l'état de notre infra, voir à un systéme de monitoring externe comme checmk pour l'utiliser comme source de données supplémentaire. Tout ça pour dire que le sujet est à peine abordé et que l'on peut faire bien d'autre chose avec ce produit très complet !