Skip to main content

Service discovery avec Netbox et Prometheus

13 nov. 2018

Prometheus, système de métrologie dont la réputation n'est plus à faire, présente la particularité de venir chercher lui même les metrics auprès de l'équipement (ou target) qu'il surveille. Si ce mode de fonctionnement, dit "pull" (par opposition au "push") possède bien des avantages en termes de scalabilité et de praticité il implique néanmoins que chacun des services à surveiller soit déclaré auprès du serveur de supervision.

Dans le cadre d'une infrastructure très dynamique (par exemple, si elle est pilotée par un système d'orchestration), il devient assez vite pénible de maintenir manuellement la liste des services à surveiller et l'idée d'automatiser tout cela émerge naturellement. Fort heureusement, Prometheus propose différents mécanismes de découverte automatique des services à travers de nombreux service discovery modules.

Le code contient déjà une dizaine de modules permettant d'interroger automatiquement les composants de différents systèmes tels que Kubernetes, OpenStack, Google Cloud ou encore AWS. Un mécanisme particulièrement intéressant pour sa flexibilité vient du module file_sd. Celui-ci propose un moyen générique, en passant par des fichiers, d'implémenter un processus de service discovery. C'est même maintenant le moyen préféré des développeurs de Prometheus, plutôt que d'ajouter de nouveaux modules de service discovery spécifiques dans leur code, complexifiant sa maintenance. C'est donc en utilisant le module file_sd que nous allons pouvoir implémenter notre propre système de service discovery.

Depuis quelques années maintenant, nous utilisons Netbox afin d'inventorier la totalité de notre infrastructure. Au fil du temps, Netbox a fini par s'imposer comme notre source of trust, c'est à dire l'endroit où l'infrastructure est décrite comme elle est supposée exister, et à laquelle les outils de déploiement automatiques et les humains doivent se référer pour réconcilier la réalité avec cette description. Il n'est cependant pas simple de maintenir une telle base de données car elle peut très vite diverger de ce qui est effectivement en place. Le maillon faible étant dans ce cas (et comme souvent) l'humain, en particulier lorsqu'il s'agit d'effectuer des tâches un peu ingrates de saisie de données. Nous avons donc tout intérêt à coupler le plus possible nos systèmes de déploiement avec Netbox, notre source of trust.

La déclaration d'un équipement dans Netbox devrait idéalement pouvoir orchestrer tout ce qui gravite autour de l’existence de cet équipement :

  • la création de l'équipement en lui-même
  • la configuration du réseau
  • la création des accès aux personnes devant administrer l'équipement
  • la configuration de l'outil de sauvegarde
  • la création des entrées DNS
  • [...]
  • et... la configuration du serveur de supervision. Vous l'aviez deviné, c'est ce dont il est question aujourd'hui.

Nous avons, chez Enix, déjà développé ou intégré plusieurs mécanismes de ce type. Il nous est par exemple possible de déployer et d'intégrer un nouvel hyperviseur dans l'un de nos clusters OpenStack très simplement, en renseignant quelques champs dans notre instance de Netbox.

Automation

La dernière intégration en date concerne la supervision, et plus particulièrement l'intégration entre Netbox et Prometheus. Étant donné le faible couplage entre ce morceau de code et l'infrastructure d'Enix, il nous a semblé être une bonne idée de le publier afin d'en faire profiter les autres utilisateurs de ces deux outils.

Le projet, très originalement baptisé "netbox-prometheus-sd" est disponible sur Github sous license MIT. Vous êtes bien entendu invités à nous faire des retours et à y contribuer si le cœur vous en dit !

En attendant, voici les quelques étapes qui vous permettront de mettre en place ce projet dans votre infrastructure.

1. Configuration de Netbox

Ajout des "custom fields"

Un custom field doit être ajouté dans Netbox afin de porter les différents labels attachés à un équipement. Pour cela, rendez-vous dans la zone d'administration de Netbox et ajoutez un nouveau custom field avec les paramètres suivants :

Image

Création du token d'API

Un token d'API est également nécessaire afin de permettre à netbox-prometheus-sd de se connecter à l'API REST de Netbox. La création du token se fait dans votre profil utilisateur, section "API Tokens". Créez un nouveau token avec les paramètres suivants :

Image

Configuration d'un équipement dans Netbox

Afin qu'un équipement soit découvert et remonté à Prometheus par netbox-prometheus-sd, il est nécessaire que celui-ci comporte une primary_ip sur l'une de ses interfaces. Il faut également saisir son custom field prom_labels. Ce champ doit être encodé au format JSON, sous la forme suivante :

{"foo": "bar"}

Ajoute sur la cible Prometheus un label "foo" avec la valeur "bar".

Il est aussi possible de remonter plusieurs cibles par équipement. Cela peut être utile dans le cas où plusieurs exporters Prometheus sont lancés sur le même équipement ou lors de l'utilisation de l'exporter_exporter (voir plus bas). Pour cela, utilisez une liste d'objets JSON :

[{"foo": "bar"}, {"bar": "foo"}]

Certains labels renseignés ici sont spécialement interprétés :

  • __metrics_path__ modifie le chemin d'URL de la cible
  • __scheme__ modifie le schéma d'URL de la cible (typiquement, http ou https)
  • __meta_netbox_port modifie le port de l'URL de la cible
  • les labels commençant par __param_ spécifient les paramètres de l'URL de la cible
  • tous les labels commençant par __ sont accessibles pendant la phase de relabeling de Prometheus mais ne sont pas enregistrés

Notons que définir le label __address__ à ce niveau ne permet pas de changer l'adresse de la cible. Dans le cas d'un exporter ne fonctionnant pas directement sur l'équipement pointé par Netbox (cas spécial d'un serveur interrogé en SNMP par exemple), il faudra utiliser une astuce (voir plus bas, la section "Cas spéciaux / Utilisation du SNMP exporter")

Des labels sont également automatiquement ajoutés par netbox-prometheus-sd depuis les données récupérées dans Netbox :

  • __meta_netbox_name : nom de l'équipement
  • __meta_netbox_tenant : slug du tenant auquel l'équipement appartient
  • __meta_netbox_tenant_group : slug du group auquel le tenant de l'équipement appartient
  • __meta_netbox_cluster : nom du cluster auquel l'équipement appartient
  • __meta_netbox_asset_tag : asset tag défini pour l'équipement
  • __meta_netbox_role : slug du rôle auquel l'équipement appartient
  • __meta_netbox_type : type de model de l'équipement
  • __meta_netbox_rack : nom du rack dans lequel l'équipement est localisé
  • __meta_netbox_pop : slug du POP dans lequel l'équipement est localisé
  • __meta_netbox_serial : numéro de série de l'équipement
  • __meta_netbox_parent : nom du parent de l'équipement

Ces labels sont préfixés par __meta_netbox et sont automatiquement exclus des métriques enregistrées par Prometheus. Il faudra donc utiliser des règles de relabeling pour les garder (voir la partie "Configuration de Prometheus").

2. Lancement de netbox-prometheus-sd

Ce projet étant un simple script Python, il suffira de le copier sur le serveur hébergeant l'instance de Prometheus puis de le lancer de manière régulière, par exemple par l'intermédiaire d'une Crontab.

La ligne de commande à lancer est typiquement la suivante :

$ netbox-prometheus-sd.py https://nebox.your-company.com/ 'API_TOKEN' '/path/to/generated/output.json'

Après avoir lancé cette commande, vous devriez pouvoir observer vos cibles dans le fichier JSON généré.

Notre infrastructure de supervision étant containérisée, nous fournissons également un Dockerfile dans le repository Git du projet. Il faudra prendre le soin de partager un volume entre le container Prometheus et le container netbox-prometheus-sd afin que celui-ci puisse accéder au fichier JSON généré par ce dernier.

3. Configuration de Prometheus

Enfin, il faudra configurer un job dans le fichier prometheus.yaml afin que Prometheus puisse apprendre ses cibles depuis le fichier JSON généré par netbox-prometheus-sd par l'intermédiaire du file service discovery:

- job_name: 'netbox'
  file_sd_configs:
    - files:
      - '/path/to/generated/output.json'
      relabel_configs:
      - regex: __meta_netbox_(.+)
        replacement: netbox_$1
        action: labelmap

La section relabel_configs permet de renommer tous les labels définis par Netbox sous la forme __meta_netbox_foo vers la forme foo. Cela permet de garder les informations provenant des Netbox puisque Prometheus se débarrasse de tous les labels commençant par __ avant d'enregistrer une métrique.

Redémarrez Prometheus et vous devriez voir apparaître vos cibles dans la page Status -> Targets de la page d'administration de Prometheus.

Cas spéciaux

Utilisation de l'exporter_exporter

L'exporter_exporter est un reverse proxy très léger conçu pour agréger plusieurs exporters écoutant sur des ports différents. Ces exporters sont alors accessibles en les sélectionnant via le paramètre HTTP module.

Avec une configuration de l'exporter_exporter similaire à ceci :

modules:
  node:
    method: http
    http:
       port: 9100

  cadvisor:
    verify: false
    method: http
    http:
       port: 4194

Il faudra attacher la configuration JSON suivante à notre équipement dans Netbox afin que chacun des exporters soit interrogé :

[{"__param_module": "node"}, {"__param_module": "cadvisor"}]

Utilisation du snmp_exporter

Le snmp_exporter est un cas intéressant car il illustre bien le problème que peut poser un exporter Prometheus ne fonctionnant pas directement sur l'équipement à interroger. Le fonctionnement est similaire à un serveur proxy : on s'adresse à l'exporter en lui indiquant l'adresse réelle du dispositif à interroger en SNMP.

Le snmp_exporter intègre en outre un concept de module qui permet de lui indiquer les différents paramètres à utiliser pour poller un dispositif (communauté, OIDs à poller etc).

Lorsque Prometheus interroge le snmp_exporter il doit donc lui fournir l'adresse de la cible réelle et le module à utiliser. Pour cela nous allons introduire un label "__snmp_module___" qui contiendra le nom du module à utiliser. De plus, quand il est défini, ce label permettra de distinguer les cibles à interroger via l'exporter_snmp de celles à interroger directement. L'adresse de la cible SNMP sera quant à elle récupérée depuis le label __adress__ lors du relabeling.

Nous allons modifier la configuration des jobs Prometheus de la manière suivante :

- job_name: 'netbox'
  file_sd_configs:
    - files:
      - '/path/to/generated/output.json'
      relabel_configs:
      - regex: __meta_netbox_(.+)
        replacement: netbox_$1
        action: labelmap
      - source_labels: [__snmp_module__]
        regex: .+
        action: drop

- job_name: 'netbox_snmp'
  file_sd_configs:
    - files:
      - '/path/to/generated/output.json'
      relabel_configs:
      - regex: __meta_netbox_(.+)
        replacement: netbox_$1
        action: labelmap
      - source_labels: [__snmp_module__]
        regex: .+
        action: keep
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: snmp-exporter:9116

Le champ prom_labels de notre équipement serait configuré ainsi :

{"__snmp_module__": "arista_sw"}

Note : le module arista_sw est défini dans la configuration par défaut du snmp_exporter

Une fois la cible générée, elle devrait ressembler à ceci dans Prometheus :

  • Cible : http://snmp-exporter:9116/proxy
  • __param_target : 11.22.33.44 (primary_ip de l'équipement)
  • instance : 11.22.33.44
  • __snmp_module__ : arista_sw
  • [...]

Conclusion

Et voilà, votre Prometheus est maintenant capable de découvrir automatiquement ses cibles depuis votre instance de Netbox. On espère que ce projet pourra vous faire gagner un peu de temps. N'hésitez surtout pas à nous faire part de vos remarques dans les commentaires et à contribuer au projet sur Github !!