~/blog/fr/homelab-monitoring-grafana-loki-crowdsec
Surveiller votre Homelab avec Grafana, Loki et CrowdSec

Si vous gérez plusieurs conteneurs, vous avez besoin d’observabilité. Quand quelque chose casse — ou quand quelqu’un sonde vos services — vous voulez le savoir avant que cela ne devienne un problème.
Ma pile de surveillance :
- Alloy pour collecter les journaux
- Loki pour les stocker et les interroger
- Prometheus avec
node-exporterpour les métriques - Grafana pour les tableaux de bord et les alertes
- CrowdSec pour la détection d’intrusion et les menaces issues du crowd-sourcing
Tout tourne sur un petit conteneur, et rien ici n’a besoin d’un compte cloud.
Architecture
Conteneur A ──rsyslog──→ ┌─────────────────────────────┐
Conteneur B ──rsyslog──→ │ mon-srv │
node-exporter ─scrape──→ │ Alloy ──→ Loki ──┐ │
serveur public ─scrape─→ │ Prometheus ──────┼→ Grafana│
indexer Wazuh ─requête─→ │ OpenSearch ──────┘ │
└─────────────────────────────┘
│
CrowdSec (LAPI)
│
bouncer (nginx)
↓
Bloquer IP malveillantes
Les conteneurs envoient leurs journaux via rsyslog vers un répertoire central, Alloy suit ce répertoire et pousse vers Loki, et Prometheus scrape node-exporter sur chaque machine. Grafana lit les deux. CrowdSec analyse les mêmes journaux pour détecter des motifs d’attaque, partage des données anonymisées avec la communauté, et bloque les contrevenants récidivistes via un bouncer sur le proxy inverse.
Journaux : rsyslog → Alloy → Loki
Le premier maillon est rsyslog. Plutôt que d’installer un collecteur de journaux sur chaque conteneur, chacun transfère son syslog au conteneur de surveillance sur le port 514, qui l’écrit sous /var/log/remote/<hôte>/*.log. Une règle par conteneur, et tous les journaux atterrissent au même endroit.
Loki est le stockage. C’est un système d’agrégation de journaux de Grafana Labs qui n’indexe que les métadonnées — les étiquettes, pas le contenu — ce qui le rend assez léger pour tourner à côté de tout le reste.
wget https://github.com/grafana/loki/releases/latest/download/loki-linux-amd64.deb
dpkg -i loki-linux-amd64.deb
Configuration de Loki 3.x (/etc/loki-config.yaml), réduite à l’essentiel :
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /var/lib/loki
storage_config:
tsdb_shipper:
active_index_directory: /var/lib/loki/tsdb-shipper-active
cache_location: /var/lib/loki/tsdb-shipper-cache
filesystem:
directory: /var/lib/loki/chunks
schema_config:
configs:
- from: 2024-01-01 # pour une nouvelle installation ; la date de début de la période
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_tsdb_
period: 24h
limits_config:
retention_period: 1440h # 60 jours
Utilisez tsdb avec le schéma v13 dès le départ. Ma configuration d’origine utilisait boltdb-shipper et v11, que la plupart des anciens guides montrent encore. C’est déprécié, et sa rétention échoue sur les chunks qui traversent minuit. En sortir veut dire ajouter une seconde période de schéma, et garder l’ancienne jusqu’à ce que ses données aient expiré.
retention_period est la ligne qui décide de l’espace disque que tout cela consomme, et c’est celle à laquelle penser en premier : le volume de journaux grossit sans bruit, et 60 jours d’un conteneur bavard représentent beaucoup de disque. Fixez-la volontairement, et placez le répertoire de données quelque part que vous surveillez réellement.
Collecter avec Alloy
C’était Promtail avant. Grafana a déprécié Promtail au profit de Grafana Alloy — même travail, un seul binaire qui gère aussi les métriques et les traces — la migration est donc surtout de la configuration. La vision qu’Alloy a de sa tâche (/etc/alloy/config.alloy) :
loki.source.file "remote_logs" {
targets = [{
__address__ = "localhost",
__path__ = "/var/log/remote/*/*.log",
job = "remote_syslog",
}]
forward_to = [loki.process.remote_logs.receiver]
file_match { enabled = true }
legacy_positions_file = "/var/lib/promtail/positions.yaml"
}
loki.write "default" {
endpoint { url = "http://localhost:3100/loki/api/v1/push" }
}
Deux choses valent la peine d’être reprises : file_match fait découvrir à Alloy les fichiers de journaux qui apparaissent après son démarrage, donc les journaux d’un nouveau conteneur sont pris en compte sans rechargement ; et réutiliser le positions.yaml de Promtail évite de renvoyer tous les fichiers depuis le début lors de la migration.
L’étape intermédiaire, loki.process, est l’endroit où les étiquettes sont attachées avant l’écriture — c’est ce qui rend les journaux interrogeables ensuite :
loki.process "remote_logs" {
forward_to = [loki.write.default.receiver]
stage.labels {
values = { traffic = "", country = "geoip_country_code" }
}
}
Une étiquette comme country transforme « montre-moi ce qui tape sur le proxy » en une requête d’une ligne plutôt qu’en un grep.
Métriques : Prometheus + node-exporter
Les journaux racontent ce qui s’est passé ; les métriques disent ce qui se passe. node-exporter sur chaque machine expose CPU, mémoire, disque, réseau et systèmes de fichiers sur :9100, et Prometheus les scrape :
scrape_configs:
- job_name: node
static_configs:
- targets: ['10.99.99.1:9100'] # l'hôte Proxmox
- targets: ['10.99.99.10:9100'] # le conteneur de surveillance lui-même
- targets: ['10.99.99.11:9100'] # le proxy inverse
- targets: ['10.99.99.25:9100'] # le contrôleur VPN
Incluez le conteneur de surveillance dans ses propres cibles. Une pile de surveillance incapable de vous montrer son propre disque qui se remplit, c’est ainsi qu’une surveillance meurt sans bruit.
Les machines hors du réseau local sont scrapées de la même manière, mais elles ont besoin de deux choses : le scrape doit traverser l’Internet public, et le port 9100 ne doit pas être ouvert au monde. Je l’expose en TLS avec une authentification basique, et Prometheus porte les identifiants :
- job_name: node-public
scheme: https
basic_auth:
username: prometheus
password_file: /etc/prometheus/public-server-exporter.pass
tls_config:
ca_file: /etc/prometheus/public-server-exporter.crt
static_configs:
- targets: ['mon.example.org:9100']
Sinon, node-exporter est un inventaire lisible par machine de votre hôte — version du noyau, points de montage, services actifs — publié pour quiconque trouve le port. Sur un serveur public, ne le laissez jamais sans authentification.
Il n’y a pas toujours besoin d’un nouvel exporter non plus. Le collecteur textfile de node-exporter lit tout fichier *.prom d’un répertoire et le sert avec le reste :
# /etc/default/prometheus-node-exporter
ARGS="--collector.textfile.directory=/var/lib/prometheus/node-exporter"
Un timer qui y écrit quelques lignes, c’est une métrique. Le paquet Debian prometheus-node-exporter-collectors s’en sert pour les mises à jour apt en attente et les données SMART, et mon point d’accès WiFi y écrit de la même façon l’état de son VPN et son nombre de clients.
C’est toute l’astuce de Prometheus : tout ce qui peut répondre sur /metrics, ou écrire un fichier texte, est un citoyen de première classe. Exporters, tâche cron et script de cinq lignes écrit vous-même ont la même allure pour Grafana.
Grafana
Grafana est provisionné avec les deux sources de données — Loki pour les journaux, Prometheus pour les métriques :
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://localhost:3100
- name: Prometheus
type: prometheus
url: http://127.0.0.1:9090
Une troisième source, OpenSearch, lit l’indexer de Wazuh, si bien que ses alertes et ses constats de vulnérabilités se retrouvent au même endroit.
Pouvoir passer de l’un à l’autre est la raison de faire tourner les deux. Quand une métrique décroche, le panneau de journaux juste à côté dit généralement pourquoi — d’après mon expérience, c’est cette corrélation qui fait gagner du temps, pas l’une ou l’autre source isolément.
Les tableaux de bord que je garde :
- Fleet Overview — est-ce que chaque machine est en marche, et quelque chose est-il sur le point de manquer de disque ?
- Security Overview — qui m’attaque, et quelque chose est-il bloqué ?
- CrowdSec Analysis — quels scénarios se déclenchent, depuis quels pays, contre quel service
- Log Analytics — qu’est-ce qui est le plus journalisé, et qu’est-ce qui a changé aujourd’hui ?
- Web Analytics — qui lit les sites, construit à partir des journaux d’accès plutôt que d’un suivi côté client
- Public Server — la machine hors du réseau local
- Home WiFi — quels clients ont rejoint le réseau sans fil, et le VPN est-il actif ?
- DNS Queries — chaque requête DNS du réseau WiFi, bloquée ou non
- SOC (Wazuh) — les alertes par niveau, et quels hôtes portent des CVE critiques
Règles d’Alerte
Grafana peut joindre l’email, Slack, un webhook, ou un service de notification auto-hébergé. J’ai des règles pour :
- Tentatives de brute force SSH au-dessus d’un seuil
- Un hôte arrêté depuis 3 minutes, ou qui n’envoie plus de journaux depuis 20
- Disque ou mémoire au-dessus de 90 %
- Une unité systemd qui échoue au démarrage
- Des mises à jour de sécurité en attente sur un hôte depuis plus de trois jours
- Loki qui n’arrive plus à appliquer la rétention
- Le service de notifications push lui-même arrêté
Une alerte que personne ne voit est pire que pas d’alerte : elle apprend à ignorer le canal. Je préfère quatre règles qui m’atteignent que quarante qui n’arrivent pas.
CrowdSec
CrowdSec est un IPS piloté par la communauté. Il détecte les attaques en analysant les journaux, puis bloque l’IP incriminée via un bouncer.
La configuration :
- LAPI (Local API) — traite les alertes et sert les décisions
- Scénarios — règles de détection (brute force SSH, scan HTTP, scan de ports)
- Bouncers — les composants qui bloquent réellement (iptables, nginx, nftables)
Il lit les mêmes journaux que tout le reste, ce qui est bien l’intérêt de les centraliser : un collecteur, trois consommateurs — Loki pour l’historique, Grafana pour les graphiques, CrowdSec pour les décisions.
cscli alerts list
cscli decisions list
cscli metrics
Avec plus d’une machine, faites tourner une seule Local API et pointez-y chaque agent et chaque bouncer. Une attaque détectée sur le serveur public bannit alors l’adresse partout. La contrepartie : un bouncer qui continue de bloquer ne prouve rien sur son agent local, qui sur l’une de mes machines n’avait rien détecté pendant quatre mois. Vérifiez cscli scenarios list sur chaque agent.
La liste noire communautaire est ce qui fait de CrowdSec plus qu’un IPS : quand quelqu’un attaque un utilisateur de CrowdSec, tous les autres bénéficient de cette intelligence — sans flux de menaces à maintenir à la main.
Tout Assembler
L’ensemble tourne sur un seul conteneur (mon-srv) avec 3 Go de RAM, utilisant actuellement environ 1,5 Go :
| Service | Mémoire |
|---|---|
| Grafana | ~226 Mo |
| Prometheus | ~135 Mo |
| Loki | ~134 Mo |
| CrowdSec | ~114 Mo |
| Alloy | ~106 Mo |
Les chiffres qui grossissent sont sur le disque, pas en RAM : Loki contenait 3,4 Go de journaux au moment de la rédaction et Prometheus 608 Mo de métriques. Les deux sont bornés par la rétention — ce qui est exactement pourquoi il faut la fixer volontairement plutôt que de la laisser à la valeur par défaut.
Pour un laboratoire avec 10 à 15 conteneurs, cette pile est plus que suffisante et entièrement gratuite. En production, vous ajouteriez des répartiteurs de charge, de la réplication et du stockage objet à long terme, mais pour un homelab, cela fonctionne parfaitement dès la sortie de la boîte.
Mis à jour en octobre 2026 : le collecteur de journaux est désormais Grafana Alloy et non plus Promtail, et le volet métriques fait partie de l’article. Depuis, la caméra a été retirée, Loki écrit en TSDB, Wazuh s’est ajouté comme troisième source de données, et la liste des alertes correspond aux règles qui tournent réellement.