La Lanterne Rouge

Warning: Geek Inside

Utiliser les ancres et alias (anchors & aliases) dans la config YAML de Prometheus

- Posted in Sans catégorie by

J'utilise depuis plusieurs années maintentant Prometheus comme système d'agrégation de métriques pour du monitoring de machines et de services. C'est bougrement puissant, surtout quand on le combine ensuite à Grafana pour créer de jolis dashboards qui feraient pâlir de jalousie les scénaristes d'Hollywood des années 2000.

Un dashboard système avec Grafana

Je l'utilise néanmoins de manière assez basique, avec un petit docker-compose.yml qui tourne sur mon serveur maison, et avec un fichier de configuration rédigé à la mimine en YAML.

services:
  app:
    image: prom/prometheus
    ports:
      - '9090:9090'
    volumes:
      - ./data/tsdb:/tsdb:rw,delegated
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro,cached
    command:
     - '--config.file=/etc/prometheus/prometheus.yml'
     - '--enable-feature=promql-experimental-functions'
     - '--storage.tsdb.path=/tsdb'
     - '--storage.tsdb.retention.time=100d'
     - '--web.console.libraries=/usr/share/prometheus/console_libraries'
     - '--web.console.templates=/usr/share/prometheus/consoles'
     - '--web.enable-admin-api'

Pour surveiller des problèmes sporadiques de résolution DNS, j'ai dû récemment dupliquer des jobs existants pour effectuer ces résolutions depuis plusieurs machines, notamment depuis une machine externe chez OVH. Cela signifiait copier-coller de nombreux blocs, alors qu'il n'y avait que des modifications mineures de l'un à l'autre.

La solution la plus évidente ici était d'utiliser la puissance de YAML et de ses ancres et alias. Mais c'était sans compter que le YAML est un standard, et que chaque langage (voire application) l'implémente plus ou moins strictement et en supportant plus ou moins de fonctionnalités arrivées au fil des versions.

Prometheus évidemment, ne supporte pas les ancres et les alias à ce jour. 😢

Mais rien ne nous oblige à utiliser le YAML pour écrire la configuration, car Prometheus sait aussi lire du JSON, une autre représentation structurée mais moins human-friendly. Il est possible de passer de l'un à l'autre facilement via yq, un petit script Python qui repose aussi sur jq (qui ne sait traiter que le JSON).

On perd dans ce cas les fonctionnalités avancées du YAML dans le résultat en JSON, mais si c'est simplement pour le donner à manger à Prometheus et qu'on conserve la version YAML comme référence, alors c'est tout bénéf.

Petit trick par contre, c'est que Prometheus valide quand même la configuration qu'on lui passe, donc s'il ne connaît pas une clé de configuration il va se plaindre et refuser de se lancer. Il faut donc affiner un petit peut la conversion et supprimer les clés customs qui seront utilisées comme des ancres.

Note : j'utilise ici blackbox_exporter pour les résolutions DNS, mais cela dépasse le cadre de cet article donc je ne m'étends pas sur ce sujet.

.job-dns-from-lan: &job-dns-from-lan
  static_configs:
    - targets:
      - 80.67.169.12          # FDN 1
      - 80.67.169.40          # FDN 2
      - 2001:910:800::12      # FDN 1 (IPv6)
      - 2001:910:800::40      # FDN 2 (IPv6)
      - 8.8.8.8               # Google
      - 1.1.1.1               # Cloudflare
      - 2001:4860:4860::8888  # Google
      - 2606:4700::1111       # Cloudflare
  relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: instance
    - target_label: __address__
      replacement: myhost-on-lan:9115

.job-dns-from-ovh: &job-dns-from-ovh
  static_configs:
    - targets:
      - 80.67.169.12          # FDN 1
      - 80.67.169.40          # FDN 2
      - 2001:910:800::12      # FDN 1 (IPv6)
      - 2001:910:800::40      # FDN 2 (IPv6)
      - 8.8.8.8               # Google
      - 1.1.1.1               # Cloudflare
      - 2001:4860:4860::8888  # Google
      - 2606:4700::1111       # Cloudflare
  relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: instance
    - target_label: __address__
      replacement: myhost-at-ovh:9115

scrape_configs:
  # LAN HOST
  - job_name: 'dns-a-google-fr-from-lan'
    metrics_path: /probe
    params:
      module: [dns_a_google.fr]
    <<: *job-dns-from-lan

  - job_name: 'dns-a-example-org-from-lan'
    metrics_path: /probe
    params:
      module: [dns_a_example.org]
    <<: *job-dns-from-lan

  # EXTERNAL HOST
  - job_name: 'dns-a-google-fr-from-ovh'
    metrics_path: /probe
    params:
      module: [dns_a_google.fr]
    <<: *job-dns-from-ovh

  - job_name: 'dns-a-example-org-from-ovh'
    metrics_path: /probe
    params:
      module: [dns_a_example.org]
    <<: *job-dns-from-ovh

Ici les 2 clés .job-dns-from-lan et .job-dns-from-ovh ne sont pas du tout standards et si j'essaye de lancer Prometheus avec ce fichier YAML, il va chouiner et sortir un

level=ERROR source=main.go:1142 msg="Error reloading config" err="couldn't load configuration (--config.file=\"/etc/prometheus/prometheus.json\"): parsing YAML file /etc/prometheus/prometheus.json: yaml: unmarshal errors:\n  line 2: field .job-dns-from-lan not found in type config.plain [...]

Pour éviter ça, il suffit de convertir la version "aplatie" en JSON de la configuration en supprimant les sections invalides pour Prometheus. Pour cela, le petit oneliner suivant fait l'affaire :

yq '. | with_entries(select(.key | test("^\\.") | not))' prometheus.yml > prometheus.json

Puis on ajuste la stack Docker Compose pour charger notre nouveau fichier JSON à la place :

services:
  app:
    image: prom/prometheus
    ports:
      - '9090:9090'
    volumes:
      - ./data/tsdb:/tsdb:rw,delegated
      - ./prometheus.json:/etc/prometheus/prometheus.json:ro,cached
    command:
     - '--config.file=/etc/prometheus/prometheus.json'
     - '--enable-feature=promql-experimental-functions'
     - '--storage.tsdb.path=/tsdb'
     - '--storage.tsdb.retention.time=100d'
     - '--web.console.libraries=/usr/share/prometheus/console_libraries'
     - '--web.console.templates=/usr/share/prometheus/consoles'
     - '--web.enable-admin-api'

Et avec un petit docker compose up -d, le voilà qui démarre sans broncher et on peut continuer à factoriser notre configuration dans le YAML tout en sachant qu'il suffit de lancer une petite commande pour mettre à jour le "vrai" fichier de config de Prometheus - en JSON à présent - avant de le redémarrer.