[[public:prometheus_docker]]

Prometheus, la tour de guet d'une infrastructure swarm

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 !

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

  • Le projet est full open-source, c'est un critère obligatoire chez nous
  • Le projet est issu de soundcloud qui en plus d'être un très bon service de streaming musical est forcément une entité qui sait produire des solutions fiables pour manager des infrastructures à très fortes charge ( comme netflix par exemple )
  • Le projet est gradué CNCF, ce fut le deuxième à avoir cet “honneur” juste après un certain kubernetes, vous savez l'orchestrator qui a littéralement écrasé le “marché” !
  • Il est adoptée par une vaste communauté !
  • Du point précédant en découle un nombre d’intégrations presque infini : les exporters, on verra ce que c'est, les libs clientes pour nos confrères développeurs et les intégrations à proprement parler!
  • La capacité à faire du “service discovery” comprendre par la : générer une partie de sa configuration tout seul comme un grand, et ça c'est bien l'objet de notre problématique !
  • Il est écrit en GO et on a beau ne pas être développeurs à Geco-iT, il y a quand même des langages qu'on affectionne…

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 :

  • En premier lieu, le serveur, qui s'occupe de récupérer les métriques auprès des exporters, de les stocker sur disque ou ailleurs ( on a une fonction remote write ), et aussi d'exposer un endpoint HTTP pour permettre aux autres composants d'exécuter des requêtes PromQL pour accéder aux métriques. Le serveur se charge aussi de générer la configuration dynamique via le service discovery et de pousser les alertes à l'alertmanager
  • En deuxième lieu, les exporters qui exposent les métriques en HTTP. Un exporter peut être soit un agent comme le node-exporter qui va collecter des métriques sur un OS et les exposer, ou directement une application qui expose ses propres métriques comme gitea ou grafana par exemple.
  • La webui qui permet de voir l'état de votre instance prometheus mais aussi de requêter les métriques, souvent on y préférera grafana qui apporte beaucoup de fonctionnalités en terme de visualisation.
  • L'alertmanager, qui va recevoir les alertes du serveur et les envoyer vers les services concernés pour vous faire parvenir des notifications sur les canaux de votre choix, teams, slack, mails , etc…
  • Et enfin la pushgateway qui est un composant un peu particulier puisqu'il permet de faire du push pour les éléments qui le nécessitent vraiment. Par exemple vous avez un job qui tourne dans un container, admettons une tâche cron qui fait le ménage des utilisateurs non utilisés depuis plus de 3 ans par exemple. Ce job peut s’exécuter, renvoyer à la fin une métrique “number_of_deleted_users” et la pousser dans la gateway. Cette dernière va stocker le résultat pour que le serveur prometheus puisse venir le lire en pull après coup. Ça ne permet pas de transformer le modèle de prometheus de pull vers push, mais ça solutionne les rares cas comme celui que j'ai pris en exemple ou l'on a besoin de faire du push ou de l’asynchrone.

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 !

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…

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, 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

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

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 :

  • Dire à prometheus de se connecter à l'API docker comme précédemment mais en mode task ce coup ci pour récupérer les services qui tournent
  • Garder seulement les containers qui doivent être dans un état “running”, ça évite d'essayer de scrapper des services qu'on a potentiellement éteint volontairement et temporairement
  • Garder seulement les containers qui ont un label “prometheus-job”, ça permet de choisir au déploiement d'un service si on doit le scrapper ou pas.
  • Garder seulement les containers qui sont dans le network “prometheus-metrics”, de cette manière je transite toutes les métriques dans ce réseau et je n'ai pas besoin d'exposer les ports de mes services qui restent bien au chaud derrière mon traeffik ;)
  • Sans rentrer dans le détail de la fin : remanier les labels pour pouvoir passer à prometheus l'URL où récupérer les métriques. D'où le label “prometheus-job-port” que j'utilise pour spécifier le port d’écoute du service qui expose les métriques.
  - 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 !

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 :

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 !

  • public/prometheus_docker.txt
  • Dernière modification: 23/12/2021/ 18:11
  • par h.campion