Skip to content

Monitoring de mes serveurs avec OpenTelemetry

Introduction

J'héberge chez moi plusieurs machines : des Raspberry Pi, un NAS, quelques services auto-hébergés. Jusqu'ici, la supervision se résumait à un htop quand quelque chose semblait lent. Pas idéal.

J'ai donc ajouté un module /monitoring à Nerv, mon application personnelle de pilotage de la maison. Ce module lit des métriques au format Prometheus, calcule CPU, mémoire, disque, réseau et uptime, et déclenche des alertes sur seuils. Restait à faire remonter les métriques de chaque machine. C'est là qu'intervient OpenTelemetry.

L'architecture

Le principe est le même sur chaque serveur à superviser :

  1. prometheus-node-exporter expose les métriques système locales sur 127.0.0.1:9100/metrics.
  2. OpenTelemetry Collector scrape ce node_exporter.
  3. Le Collector re-expose les métriques au format Prometheus sur 0.0.0.0:9464/metrics.
  4. Nerv interroge http://IP_DU_SERVEUR:9464/metrics.

On pourrait se demander pourquoi intercaler un Collector alors que node_exporter expose déjà du Prometheus. Deux raisons : je ne veux exposer qu'un seul port par machine sur le réseau (node_exporter reste en local), et le Collector me donne un point de contrôle : limitation mémoire, relabeling, et la possibilité d'ajouter d'autres receivers plus tard sans toucher à Nerv.

Choisir le bon binaire selon l'architecture

Premier piège avec les Raspberry Pi : l'architecture. Un uname -m sur la machine donne la réponse :

  • armv7l (Raspberry Pi 2, ou Pi 3/4 avec un OS 32 bits) : il faut le paquet Contrib linux_armv7, le Collector officiel n'est pas publié pour cette plateforme ;
  • aarch64 (Pi 3 et supérieur avec un OS 64 bits) : le Collector officiel linux_arm64 suffit.

Installation

D'abord node_exporter, via le paquet système :

bash
sudo apt update
sudo apt install prometheus-node-exporter
sudo systemctl enable --now prometheus-node-exporter
curl http://127.0.0.1:9100/metrics | grep node_cpu_seconds_total

Puis le Collector, ici pour un Pi 2 en armv7l :

bash
VERSION=0.132.0
wget "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${VERSION}/otelcol-contrib_${VERSION}_linux_armv7.tar.gz"
tar xzf "otelcol-contrib_${VERSION}_linux_armv7.tar.gz"
sudo install -m 0755 otelcol-contrib /usr/local/bin/otelcol-contrib

Pour une machine en aarch64, même principe avec l'archive otelcol_${VERSION}_linux_arm64.tar.gz et le binaire otelcol.

Configuration du Collector

Le fichier /etc/otelcol-contrib/config.yaml (ou /etc/otelcol/config.yaml) :

yaml
receivers:
  prometheus:
    config:
      global:
        scrape_interval: 60s
        scrape_timeout: 10s
      scrape_configs:
        - job_name: raspberry_pi_node_exporter
          static_configs:
            - targets: ['127.0.0.1:9100']
              labels:
                host: raspberry-pi-2

processors:
  memory_limiter:
    check_interval: 5s
    limit_mib: 64
    spike_limit_mib: 16

exporters:
  prometheus:
    endpoint: 0.0.0.0:9464
    send_timestamps: true
    metric_expiration: 5m

service:
  pipelines:
    metrics:
      receivers: [prometheus]
      processors: [memory_limiter]
      exporters: [prometheus]

Trois points importants :

  • targets: ['127.0.0.1:9100'] : le Collector lit node_exporter en local uniquement ;
  • endpoint: 0.0.0.0:9464 : c'est le seul port exposé au réseau, celui que Nerv consomme ;
  • le memory_limiter à 64 Mo : sur un Pi 2 avec 1 Go de RAM, on ne laisse pas le Collector faire ce qu'il veut.

Le module monitoring de Nerv consomme du texte Prometheus, pas un payload OTLP natif. Le Collector sert donc ici de pont entre node_exporter et Nerv.

Le service systemd

Un test manuel d'abord (otelcol-contrib --config=/etc/otelcol-contrib/config.yaml puis curl sur le port 9464), et si tout répond, le service /etc/systemd/system/otelcol-contrib.service :

ini
[Unit]
Description=OpenTelemetry Collector Contrib
After=network-online.target
Wants=network-online.target

[Service]
User=root
ExecStart=/usr/local/bin/otelcol-contrib --config=/etc/otelcol-contrib/config.yaml
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
bash
sudo systemctl daemon-reload
sudo systemctl enable --now otelcol-contrib

Variante Docker Compose pour le NAS

Sur le NAS, Docker est déjà là, autant s'en servir. Même architecture, même endpoint final, mais tout tient dans un docker-compose.yml :

yaml
services:
  node-exporter:
    image: prom/node-exporter:v1.8.2
    container_name: node-exporter
    restart: unless-stopped
    network_mode: host
    pid: host
    command:
      - --path.rootfs=/host
    volumes:
      - /:/host:ro,rslave

  otelcol:
    image: otel/opentelemetry-collector:0.154.0
    container_name: otelcol
    restart: unless-stopped
    ports:
      - "9464:9464"
    extra_hosts:
      - host.docker.internal:host-gateway
    command:
      - --config=/etc/otelcol/config.yaml
    volumes:
      - ./otelcol/config.yaml:/etc/otelcol/config.yaml:ro
    depends_on:
      - node-exporter

Le network_mode: host sur node-exporter est volontaire : il doit lire les métriques de la machine hôte, pas celles de son conteneur. Le Collector, lui, reste en réseau Docker standard et scrape host.docker.internal:9100 grâce au host-gateway. Dans la config du Collector, la cible devient donc host.docker.internal:9100 au lieu de 127.0.0.1:9100.

Piège rencontré sur le NAS : les ACL. Le conteneur Collector redémarrait en boucle avec un permission denied sur le fichier de config monté. La solution :

bash
chmod 755 . otelcol
chmod 644 otelcol/config.yaml
docker compose up -d --force-recreate otelcol

Sécuriser les serveurs externes

Sur le réseau local, exposer le port 9464 ne pose pas de problème. Mais pour un serveur externe (un VPS par exemple), ce port serait ouvert sur Internet : n'importe qui pourrait lire les métriques système de la machine. Rien de confidentiel en soi, mais c'est de l'information offerte gratuitement à un attaquant : uptime, charge, points de montage, interfaces réseau.

L'exporter Prometheus du Collector ne propose pas d'authentification, la parade la plus simple est donc le firewall : n'autoriser l'accès au port 9464 qu'à l'IP de Nerv. Avec ufw :

bash
sudo ufw allow from IP_DE_NERV to any port 9464 proto tcp
sudo ufw deny 9464/tcp

Attention au cas Docker : les ports publiés via ports: dans un docker-compose.yml contournent ufw (Docker écrit ses propres règles iptables). Dans ce cas, la restriction se fait dans la chaîne DOCKER-USER :

bash
sudo iptables -I DOCKER-USER -p tcp --dport 9464 ! -s IP_DE_NERV -j DROP

Je ne l'ai pas encore mis en place puisque mes machines actuelles sont toutes derrière ma box, mais c'est un prérequis avant de brancher le moindre serveur exposé sur Internet.

Branchement dans Nerv

Côté Nerv, il suffit de déclarer le serveur dans /monitoring :

text
Nom: Raspberry Pi 2
Host: raspberry-pi-2.local
Environnement: home
Endpoint OpenTelemetry Collector: http://IP_DU_SERVEUR:9464/metrics

Avec les seuils standards : CPU 80 %, mémoire 80 %, disque 85 %. Le module calcule tout automatiquement à partir des métriques node_exporter : CPU depuis node_cpu_seconds_total, mémoire depuis node_memory_MemTotal_bytes et node_memory_MemAvailable_bytes, disque depuis node_filesystem_*, réseau depuis les compteurs node_network_*_bytes_total.

En complément, je peux suivre des métriques spécifiques via les « métriques observées ». Exemple pour la température CPU d'un Pi :

text
Libellé: Température CPU
Nom de métrique: node_thermal_zone_temp
Unité: °C
Agrégation: Maximum
Warning: 70
Critical: 80

À noter : CPU et réseau peuvent être vides au premier relevé. Ces valeurs sont calculées par delta entre deux collectes, il faut donc deux checks espacés d'une minute pour voir apparaître les premières valeurs. L'historique est conservé 30 jours.

Conclusion

Pour quelques Raspberry Pi et un NAS, cette stack est un bon compromis : node_exporter fait ce qu'il sait faire, le Collector OpenTelemetry sert de point d'exposition unique et contrôlé, et Nerv se contente de lire du Prometheus sans dépendre du détail de chaque machine. Le tout tourne sans broncher, y compris sur un Pi 2 de plus de dix ans, à condition de choisir le bon binaire et de brider la mémoire du Collector.