Gestion centralisée des logs avec Grafana Loki sur un VPS
Déployez Grafana Loki, Promtail et Grafana via Docker Compose sur un VPS. Collectez les logs systemd, Docker et Nginx, interrogez-les avec LogQL et configurez la rétention pour la production.
Des logs dispersés entre /var/log et la sortie de docker logs deviennent ingérables dès que vous faites tourner plus de deux services. Ce tutoriel déploie la stack Grafana + Loki + Promtail sur un VPS unique via Docker Compose. Vous collecterez les logs de systemd, des conteneurs Docker et de Nginx, les interrogerez avec LogQL, configurerez la rétention pour éviter de remplir votre disque, et verrouillerez la stack pour un serveur exposé à internet.
À la fin, l'API HTTP de Loki sera prête pour des requêtes programmatiques. L'article Analyse de logs par IA avec Ollama sur un VPS : détecter les anomalies avec un LLM local s'appuie sur cette base pour alimenter un LLM local en détection d'anomalies.
Que fait la stack Grafana + Loki + Promtail ?
Grafana Loki est un système open-source d'agrégation de logs qui indexe uniquement les labels (métadonnées), pas le texte intégral des entrées de log. Il consomme donc bien moins de ressources qu'Elasticsearch. Loki stocke des blocs de logs compressés sur le système de fichiers ou un stockage objet. Associé à Promtail pour la collecte et Grafana pour la visualisation, il forme une stack de logging centralisé complète.
Les trois composants ont des rôles distincts :
| Composant | Rôle | Empreinte mémoire |
|---|---|---|
| Loki | Reçoit, stocke et indexe les logs. Répond aux requêtes. | 300-600 Mo de RAM au repos, jusqu'à 1 Go sous forte charge |
| Promtail | Découvre les sources de logs, lit les fichiers en continu, envoie les entrées à Loki | 50-100 Mo de RAM |
| Grafana | Interface web pour interroger et visualiser les logs via Explore | 200-300 Mo de RAM |
Empreinte totale de la stack : 1-1,5 Go de RAM. Un VPS de 2 Go est le minimum. Un VPS de 4 Go offre une marge confortable pour les requêtes LogQL sur des jeux de données volumineux.
Loki vs Elasticsearch : Elasticsearch indexe chaque mot de chaque ligne de log, ce qui offre la recherche plein texte mais coûte 10 à 20 fois plus en RAM et en disque. L'index par labels de Loki signifie que vous filtrez d'abord par labels, puis vous parcourez les blocs correspondants. Pour la plupart des charges VPS, c'est le bon compromis. Si vous avez besoin de recherche plein texte sur des téraoctets de logs, Loki n'est pas le bon outil.
Prérequis
- Un VPS avec au moins 2 Go de RAM (4 Go recommandés). Un VPS Virtua Cloud avec 4 vCPU et 8 Go de RAM gère cette stack sans problème.
- Docker et Docker Compose installés. Si vous avez besoin d'aide pour l'installation, consultez [-> docker-compose-multi-service-vps].
- Un utilisateur non-root avec accès sudo.
- Des bases en terminal et en syntaxe YAML.
Vérifiez que Docker tourne :
docker --version
docker compose version
Vous devriez voir Docker 24+ et Compose v2+. Si l'une des commandes échoue, Docker n'est pas installé ou le plugin Compose est absent.
Comment déployer Loki avec Docker Compose sur un VPS ?
Créez un répertoire de projet et trois fichiers de configuration : docker-compose.yml, loki-config.yml et promtail-config.yml. Le fichier Docker Compose épingle toutes les images sur des versions précises, définit des limites de ressources, configure des volumes persistants et lie Loki à localhost uniquement.
Structure du projet
mkdir -p ~/loki-stack/{loki-data,promtail-data}
cd ~/loki-stack
Le répertoire loki-data contient les blocs, les index et le journal d'écriture anticipée (WAL). Le répertoire promtail-data stocke le fichier de positions de Promtail pour reprendre après un redémarrage.
Définissez le propriétaire du répertoire de données Loki. L'image Docker de Loki 3.x tourne avec l'UID 10001, pas root. Sans cela, Loki échoue au démarrage avec « permission denied » lors de la création des sous-répertoires :
chown 10001:10001 ~/loki-stack/loki-data
docker-compose.yml
services:
loki:
image: grafana/loki:3.6.7
command: -config.file=/etc/loki/config.yml
volumes:
- ./loki-config.yml:/etc/loki/config.yml:ro
- ./loki-data:/loki
ports:
- "127.0.0.1:3100:3100"
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
networks:
- loki-net
promtail:
image: grafana/promtail:3.6.7
command: -config.file=/etc/promtail/config.yml
volumes:
- ./promtail-config.yml:/etc/promtail/config.yml:ro
- ./promtail-data:/var/lib/promtail
- /var/log:/var/log:ro
- /var/log/journal:/var/log/journal:ro
- /run/log/journal:/run/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
deploy:
resources:
limits:
memory: 256m
depends_on:
- loki
networks:
- loki-net
grafana:
image: grafana/grafana:11.5.2
volumes:
- grafana-data:/var/lib/grafana
ports:
- "127.0.0.1:3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin_pw
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_USERS_ALLOW_SIGN_UP=false
- GF_ANALYTICS_REPORTING_ENABLED=false
secrets:
- grafana_admin_pw
restart: unless-stopped
deploy:
resources:
limits:
memory: 512m
depends_on:
- loki
networks:
- loki-net
secrets:
grafana_admin_pw:
file: ./secrets/grafana_admin_pw
volumes:
grafana-data:
networks:
loki-net:
driver: bridge
Point important : Loki et Grafana écoutent sur 127.0.0.1, pas 0.0.0.0. Cela empêche tout accès externe. Vous accéderez à Grafana via un tunnel SSH ou un reverse proxy. Exposer Loki ou Grafana directement sur internet est une erreur courante que l'on retrouve dans la plupart des tutoriels en ligne.
La politique restart: unless-stopped garantit que chaque service survit aux redémarrages. Si vous stoppez manuellement un service avec docker compose stop, il reste arrêté. Sinon, il redémarre automatiquement.
Les tags d'images épinglés (3.6.7, 11.5.2) évitent les mises à jour surprises. N'utilisez jamais :latest en production. Quand vous souhaitez mettre à jour, changez le tag et lancez docker compose up -d pour récupérer la nouvelle image.
Générer le mot de passe admin de Grafana
N'utilisez jamais les identifiants par défaut. Générez un mot de passe fort et stockez-le dans un fichier de secrets avec des permissions restreintes :
mkdir -p ~/loki-stack/secrets
openssl rand -base64 32 > ~/loki-stack/secrets/grafana_admin_pw
chmod 644 ~/loki-stack/secrets/grafana_admin_pw
Vérifiez les permissions :
ls -la ~/loki-stack/secrets/grafana_admin_pw
Vous devriez voir -rw-r--r--. Le fichier doit être lisible par tous car Docker Compose (hors mode Swarm) monte les secrets basés sur fichier avec les permissions du fichier source. Grafana tourne avec l'UID 472 dans le conteneur et a besoin d'un accès en lecture. Le fichier reste protégé par son emplacement dans un répertoire de secrets dédié, et seul l'utilisateur root de l'hôte peut le modifier. La variable d'environnement GF_SECURITY_ADMIN_PASSWORD__FILE indique à Grafana de lire le mot de passe depuis ce fichier au démarrage plutôt que de l'intégrer dans le fichier compose.
loki-config.yml
Cette configuration utilise TSDB avec le schéma v13 (valeurs par défaut de Loki 3.x), le stockage sur système de fichiers pour un déploiement mono-nœud, et le compactor pour la rétention :
auth_enabled: false
server:
http_listen_port: 3100
http_listen_address: 0.0.0.0
log_level: warn
http_server_read_timeout: 30s
http_server_write_timeout: 30s
common:
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /loki
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
filesystem:
directory: /loki/chunks
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
limits_config:
retention_period: 720h
max_streams_per_user: 10000
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_label_names_per_series: 15
ingester:
chunk_encoding: snappy
wal:
dir: /loki/wal
enabled: true
chunk_store_config:
chunk_cache_config:
embedded_cache:
enabled: true
max_size_mb: 100
Choix importants dans cette configuration :
auth_enabled: falseest sûr ici car Loki n'écoute que sur localhost (réseau interne Docker + binding sur 127.0.0.1). Les configurations multi-tenants nécessitentauth_enabled: trueavec un headerX-Scope-OrgIDsur chaque requête.retention_period: 720hconserve les logs pendant 30 jours. Loki 3.x utilise par défaut0s(conservation illimitée) si vous ne définissez pas cette valeur. Votre disque finira par se remplir.schema: v13avecstore: tsdbest requis pour les fonctionnalités de Loki 3.x. Les anciennes configurationsboltdb-shipperdes tutoriels Loki 2.x échoueront au démarrage ou produiront des avertissements de dépréciation.chunk_encoding: snappycompresse les blocs avec Snappy. Plus rapide que gzip, fichiers légèrement plus gros. Bon choix par défaut pour un nœud unique où le CPU est plus contraint que le disque.- WAL activé : le journal d'écriture anticipée protège contre la perte de données si Loki plante en cours d'écriture. Au redémarrage, Loki rejoue le WAL pour récupérer les entrées non validées. Vous verrez des messages « WAL replay » dans les logs au démarrage. C'est normal.
max_label_names_per_series: 15correspond à la valeur par défaut de Loki 3.x. Gardez une cardinalité de labels basse. Des labels commeuser_idourequest_idcréent trop de flux et dégradent les performances.
Comment configurer Promtail pour collecter les logs du journal systemd ?
Promtail récupère les logs depuis plusieurs sources et les envoie à Loki. La configuration ci-dessous collecte depuis trois sources dans des jobs scrape_configs séparés : le journal systemd, les conteneurs Docker et les fichiers de logs Nginx.
Avis de fin de vie de Promtail : Promtail a atteint sa fin de vie le 2 mars 2026. Grafana Alloy est le successeur officiel. Ce tutoriel utilise Promtail car les concepts de configuration se transposent directement à Alloy, et des millions de déploiements l'utilisent encore. Consultez la section « Promtail est-il déprécié ? » ci-dessous pour les étapes de migration.
promtail-config.yml
server:
http_listen_port: 9080
log_level: warn
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# --- Systemd journal ---
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: unit
- source_labels: ['__journal_priority_keyword']
target_label: severity
- source_labels: ['__journal__hostname']
target_label: hostname
# --- Docker containers ---
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.+)'
target_label: container
- source_labels: ['__meta_docker_container_log_stream']
target_label: stream
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: compose_service
# --- Nginx access and error logs ---
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
type: access
__path__: /var/log/nginx/access.log
- targets:
- localhost
labels:
job: nginx
type: error
__path__: /var/log/nginx/error.log
Le fichier positions.yaml garde la trace de l'avancement de la lecture de Promtail dans chaque source. Si Promtail redémarre, il reprend là où il s'est arrêté au lieu de renvoyer d'anciens logs ou d'en manquer.
Fonctionnement du job journal
Le bloc journal lit directement le journal systemd via l'API journal. Le paramètre max_age: 12h indique à Promtail de n'ingérer que les entrées des 12 dernières heures au premier démarrage. Sans cela, Promtail tenterait d'ingérer tout l'historique du journal, qui peut peser des gigaoctets sur des serveurs anciens.
Les relabel_configs extraient des métadonnées des entrées du journal vers des labels Loki. __journal__systemd_unit devient le label unit (par ex. sshd.service, nginx.service). __journal_priority_keyword devient severity (par ex. warning, err, info). Ces labels permettent de filtrer efficacement les logs dans LogQL sans parcourir chaque ligne.
Pour que le scraping du journal fonctionne, le conteneur Promtail a besoin de deux montages de volumes : /run/log/journal (ou /var/log/journal si votre système persiste les logs) et /etc/machine-id. L'identifiant machine détermine quel journal lire.
Limitation de l'image Docker : L'image Docker standard
grafana/promtailn'est pas compilée avec le support du journal systemd. Si vous voyezsupport for reading the systemd journal is not compiled into this build of promtaildans les logs, le scraping du journal ne fonctionnera pas depuis le conteneur Docker. Deux options s'offrent à vous :
- Installer Promtail comme binaire sur l'hôte au lieu de l'image Docker. Téléchargez-le depuis la page des releases de Loki et exécutez-le directement sur l'hôte, où il a un accès natif à l'API journal.
- Utiliser Grafana Alloy (voir la section migration ci-dessous), qui supporte le scraping du journal dans son image Docker.
Les jobs de scraping Docker et Nginx par fichier fonctionnent sans problème dans l'image Docker. Seul le scraping du journal nécessite le binaire hôte ou Alloy.
Comment collecter les logs des conteneurs Docker avec Promtail ?
Le job docker utilise la découverte de services Docker pour détecter automatiquement tous les conteneurs en cours d'exécution sur l'hôte. Promtail se connecte au démon Docker via /var/run/docker.sock et interroge les nouveaux conteneurs toutes les 5 secondes. Quand un conteneur démarre ou s'arrête, Promtail commence ou cesse automatiquement de suivre ses logs.
Les relabel_configs extraient des métadonnées utiles :
__meta_docker_container_namedevient le labelcontainer. La regex'/(.+)'supprime le/initial que Docker ajoute aux noms de conteneurs.__meta_docker_container_log_streamdevient le labelstream(stdoutoustderr).__meta_docker_container_label_com_docker_compose_serviceextrait le nom du service Compose (par ex.loki,grafana). Ce label n'existe que pour les conteneurs gérés par Docker Compose.
Pas besoin de configurer chaque conteneur individuellement. Tout conteneur utilisant un pilote de logs qui écrit sur le système de fichiers (le pilote json-file par défaut) sera découvert. Si vous faites tourner un conteneur de base de données, une application web et un cache, les trois apparaissent automatiquement dans Loki sous leur nom de conteneur.
Pour exclure certains conteneurs de la collecte de logs, ajoutez un label Docker et filtrez dessus :
relabel_configs:
# ... existing relabel rules ...
- source_labels: ['__meta_docker_container_label_logging']
regex: 'disabled'
action: drop
Puis sur le conteneur à exclure :
noisy-service:
image: some/image
labels:
logging: "disabled"
Comment récupérer les logs d'accès et d'erreur Nginx avec Promtail ?
Le job nginx utilise static_configs avec le label __path__ pour suivre des fichiers de logs spécifiques. Contrairement à la découverte de services Docker, il faut connaître les chemins des fichiers à l'avance. Nginx écrit par défaut dans /var/log/nginx/access.log et /var/log/nginx/error.log.
Le label type distingue les logs d'accès des logs d'erreur. Cela permet de les interroger séparément en LogQL :
{job="nginx", type="access"} # uniquement les logs d'accès
{job="nginx", type="error"} # uniquement les logs d'erreur
{job="nginx"} # les deux
Si Nginx n'est pas installé sur l'hôte, Promtail signale un avertissement pour les fichiers manquants mais continue à récupérer les autres sources. C'est inoffensif. Supprimez le job nginx de la configuration si vous n'utilisez pas Nginx.
Pour Nginx dans un conteneur Docker, deux options. Vous pouvez utiliser la découverte de services Docker (la sortie stdout/stderr du conteneur sera capturée automatiquement). Ou vous pouvez monter le répertoire de logs Nginx comme volume partagé et utiliser le scraper de fichiers statiques. L'approche Docker est plus simple. L'approche par fichiers vous donne des labels séparés type: access et type: error.
Démarrer la stack
cd ~/loki-stack
docker compose up -d
Vérifiez que les trois conteneurs tournent :
docker compose ps
Vous devriez voir loki, promtail et grafana avec le statut Up. Si un service affiche Restarting, consultez ses logs :
docker compose logs <service-name> --tail=30
Vérifiez que Loki est prêt :
curl -s http://127.0.0.1:3100/ready
Sortie attendue : ready. Si vous obtenez Ingester not ready: waiting for 15s after being ready, attendez 15 secondes et réessayez. Loki a besoin de temps pour initialiser le ring de l'ingester.
Vérifiez les cibles de Promtail :
docker compose logs promtail --tail=20
Cherchez les lignes montrant les cibles découvertes. Vous devriez voir des entrées pour le journal, le socket Docker et les chemins des logs Nginx. Aucune ligne level=error ne devrait apparaître.
Comment vérifier que les logs apparaissent dans Grafana Explore ?
Ouvrez un tunnel SSH pour accéder à Grafana depuis votre machine locale. Le tunnel est nécessaire car Grafana écoute sur localhost sur le VPS et n'est pas exposé à internet.
ssh -L 3000:127.0.0.1:3000 user@your-vps-ip
Ouvrez http://localhost:3000 dans votre navigateur. Connectez-vous avec le nom d'utilisateur admin et le mot de passe de ~/loki-stack/secrets/grafana_admin_pw. Lisez-le avec :
cat ~/loki-stack/secrets/grafana_admin_pw
Ajouter Loki comme source de données
- Allez dans Connections > Data Sources > Add data source
- Sélectionnez Loki
- Définissez l'URL sur
http://loki:3100(c'est le nom d'hôte interne Docker, pas localhost) - Cliquez sur Save & test
Vous devriez voir « Data source successfully connected. » Si ça échoue, vérifiez que les deux conteneurs sont sur le même réseau Docker (loki-net).
Exécuter votre première requête
- Allez dans Explore (icône boussole dans la barre latérale)
- Sélectionnez la source de données Loki
- Passez en mode Code (pas Builder) et entrez cette requête LogQL :
{job="systemd-journal"} |= "ssh"
Cela affiche toutes les entrées du journal systemd contenant « ssh ». Si vous voyez des lignes de log, le pipeline complet fonctionne : journal -> Promtail -> Loki -> Grafana.
Essayez une requête sur un conteneur Docker :
{compose_service="loki"}
Cela renvoie les propres logs de Loki, collectés par Promtail via la découverte de services Docker.
Et une requête Nginx (si Nginx est installé et génère des logs) :
{job="nginx", type="error"}
Si Grafana affiche « No data », attendez 2-3 minutes. Loki a besoin de temps pour ingérer et indexer le premier lot de logs.
Quelles sont les requêtes LogQL les plus utiles pour les logs serveur ?
LogQL propose deux types de requêtes : les requêtes de logs renvoient des lignes de log, les requêtes de métriques renvoient des valeurs numériques. Les deux commencent par un sélecteur de flux ({label="value"}) qui choisit les flux de logs à parcourir, puis ajoutent des filtres et des parseurs pour affiner les résultats.
Sélecteurs de flux et filtres de lignes
# Tous les logs du démon SSH
{unit="sshd.service"}
# Lignes contenant "Failed password"
{unit="sshd.service"} |= "Failed password"
# Lignes NE contenant PAS "Accepted"
{unit="sshd.service"} != "Accepted"
# Correspondance regex : adresses IP
{unit="sshd.service"} |~ "\\d+\\.\\d+\\.\\d+\\.\\d+"
# Correspondance insensible à la casse
{job="nginx", type="error"} |~ "(?i)timeout"
Les filtres de lignes (|=, !=, |~, !~) s'exécutent après le sélecteur de flux. Ils parcourent le contenu des lignes de log. Plusieurs filtres s'enchaînent et doivent tous correspondre :
{unit="sshd.service"} |= "Failed" |= "root"
Cela trouve les lignes contenant à la fois « Failed » et « root ».
Parseurs
Les parseurs extraient des champs structurés à partir de lignes de log non structurées. Une fois parsés, vous pouvez filtrer sur des champs extraits comme status >= 500 au lieu d'utiliser des regex. Choisissez le bon parseur selon votre format de log :
| Parseur | Syntaxe | Adapté pour | Notes de performance |
|---|---|---|---|
logfmt |
| logfmt |
Logs clé=valeur (systemd, apps Go) | Le plus rapide. Zéro regex. |
json |
| json |
Logs structurés en JSON | Rapide. Parsing JSON natif. |
pattern |
| pattern "<pattern>" |
Logs à format fixe (Nginx combined, Apache) | Rapide. Extraction positionnelle. |
regexp |
| regexp "<regex>" |
Formats irréguliers, structures mixtes | Le plus lent. À utiliser en dernier recours. |
Utilisez logfmt ou json quand vos logs ont déjà une structure. Utilisez pattern pour les formats connus comme le log combined de Nginx. N'utilisez regexp que quand rien d'autre ne fonctionne, car le parsing par regex est bien plus lent sur les flux à haut volume.
Log d'accès Nginx avec le parseur pattern :
{job="nginx", type="access"}
| pattern "<ip> - - [<timestamp>] \"<method> <uri> <_>\" <status> <bytes>"
| status >= 500
Cela parse le format combined de Nginx en champs nommés et filtre les erreurs 5xx. Le placeholder <_> ignore les champs inutiles (la version HTTP dans ce cas).
Parseur de logs JSON :
{compose_service="myapp"}
| json
| level="error"
| line_format "{{.timestamp}} {{.message}}"
L'étape line_format reformate la sortie. Pratique quand les logs JSON sont verbeux et que vous voulez un affichage plus propre dans Grafana.
Métriques à partir des logs
Les requêtes de métriques transforment les lignes de log en nombres. Elles alimentent les tableaux de bord Grafana et les règles d'alerte :
# Taux de connexions SSH échouées par minute sur la dernière heure
rate({unit="sshd.service"} |= "Failed password" [5m])
# Total des erreurs Nginx 5xx par fenêtres de 5 minutes
count_over_time(
{job="nginx", type="access"}
| pattern "<_> - - [<_>] \"<_> <_> <_>\" <status> <_>"
| status >= 500
[5m]
)
# P95 du temps de réponse depuis les logs JSON d'une app
# unwrap extrait un champ numérique pour l'agrégation
quantile_over_time(0.95,
{compose_service="myapp"}
| json
| unwrap response_time_ms
| __error__=""
[5m]
) by (endpoint)
# Octets servis par seconde par Nginx
sum(rate(
{job="nginx", type="access"}
| pattern "<_> - - [<_>] \"<_> <_> <_>\" <_> <bytes>"
| unwrap bytes
| __error__=""
[5m]
))
Le filtre | __error__="" après unwrap écarte les lignes où l'extraction numérique a échoué (valeurs non numériques, champs manquants). Sans lui, ces lignes produisent silencieusement des valeurs nulles qui faussent vos résultats. Incluez toujours ce filtre après unwrap.
Le range [5m] définit la taille de la fenêtre. Des ranges plus courts (1m) donnent des données plus granulaires mais plus bruitées. Des ranges plus longs (15m, 1h) lissent les pics. Pour les tableaux de bord, 5m est un bon point de départ.
Comment configurer la rétention des logs dans Loki 3.x ?
Dans Loki 3.x, la rétention est gérée par le compactor. Définissez retention_period sous limits_config et activez le compactor avec retention_enabled: true. La rétention par défaut dans Loki 3.0+ est 0s (conservation illimitée), il faut donc la configurer explicitement sous peine de remplir votre disque.
Le fichier loki-config.yml ci-dessus inclut déjà la rétention. Voici comment les paramètres interagissent :
compactor:
retention_enabled: true # Doit être true, sinon le compactor ne fait que compacter (pas de suppression)
retention_delete_delay: 2h # Attendre 2h après le marquage des blocs avant suppression
compaction_interval: 10m # Fréquence d'exécution du compactor
limits_config:
retention_period: 720h # 30 jours par défaut globalement
Le compactor tourne dans le processus Loki en mode nœud unique. Il parcourt l'index TSDB, identifie les blocs plus anciens que la période de rétention, les marque pour suppression, puis les supprime après le retention_delete_delay. Ce délai vous donne une fenêtre de récupération en cas de mauvaise configuration.
Rétention par flux
Vous pouvez définir des périodes de rétention différentes selon les flux de logs. Les logs à haut volume et faible valeur (comme la sortie de debug) peuvent expirer plus vite :
limits_config:
retention_period: 720h
retention_stream:
- selector: '{job="nginx", type="access"}'
priority: 1
period: 336h # 14 jours pour les logs d'accès
- selector: '{severity="debug"}'
priority: 2
period: 72h # 3 jours pour les logs de debug
Les valeurs de priorité plus élevées l'emportent quand plusieurs sélecteurs correspondent au même flux. Un log d'accès Nginx de niveau debug correspond aux deux règles. La priorité 2 l'emporte, il obtient donc une rétention de 3 jours.
Dimensionnement de la rétention
Estimez l'utilisation disque avant de vous engager sur une période de rétention. Loki compresse bien les logs (ratio 5-10x avec Snappy), mais les chiffres s'additionnent sur des serveurs actifs :
| Volume brut/jour | Compressé (est.) | Rétention 7 jours | Rétention 30 jours | Rétention 90 jours |
|---|---|---|---|---|
| 100 Mo | ~15 Mo | ~105 Mo | ~450 Mo | ~1,35 Go |
| 500 Mo | ~75 Mo | ~525 Mo | ~2,25 Go | ~6,75 Go |
| 1 Go | ~150 Mo | ~1,05 Go | ~4,5 Go | ~13,5 Go |
| 5 Go | ~750 Mo | ~5,25 Go | ~22,5 Go | ~67,5 Go |
Ce sont des estimations. La compression réelle dépend du contenu des logs. Les logs répétitifs (logs d'accès avec des chemins similaires) se compressent mieux que la sortie de debug aléatoire. Surveillez l'utilisation réelle avec :
du -sh ~/loki-stack/loki-data/
Exécutez cette commande chaque semaine pour détecter une croissance inattendue avant de manquer d'espace disque.
Comment optimiser Loki pour la production sur un VPS unique ?
Une configuration Loki par défaut fonctionne pour les tests mais nécessite des ajustements pour un VPS de production. Les modifications ci-dessous réduisent les pics de mémoire, protègent contre les flux de logs incontrôlés et renforcent la stack pour un serveur exposé à internet.
Lier Loki à localhost
Déjà fait dans le docker-compose.yml ci-dessus (127.0.0.1:3100:3100). Vérifiez après le déploiement :
ss -tlnp | grep 3100
Vous devriez voir 127.0.0.1:3100, pas 0.0.0.0:3100. Faites de même pour Grafana sur le port 3000.
Règles de pare-feu
Si vous utilisez ufw, bloquez l'accès externe aux ports de logging :
sudo ufw deny 3100/tcp comment "Loki - localhost only"
sudo ufw deny 3000/tcp comment "Grafana - localhost only"
sudo ufw status numbered
Puisque les ports écoutent déjà sur localhost, le pare-feu constitue une défense en profondeur. Si quelqu'un modifie accidentellement le fichier compose pour écouter sur 0.0.0.0, le pare-feu bloque toujours l'accès externe.
Masquage des informations de version
La divulgation de version aide les attaquants à cibler des vulnérabilités connues. Le GF_ANALYTICS_REPORTING_ENABLED=false dans le fichier compose désactive déjà la télémétrie Grafana. L'endpoint /loki/api/v1/status/buildinfo de Loki expose les détails de version, mais puisque Loki écoute sur localhost, seuls les processus locaux peuvent y accéder.
Si vous placez Grafana derrière un reverse proxy (Nginx, Caddy), ajoutez ces paramètres Nginx :
server_tokens off;
proxy_hide_header X-Powered-By;
Changements majeurs de Loki 3.x
Si vous migrez depuis une configuration Loki 2.x ou suivez un ancien tutoriel, tenez compte de ces changements :
| Changement | Défaut Loki 2.x | Défaut Loki 3.x | Action requise |
|---|---|---|---|
| Schéma | v11/v12 | v13 | Utilisez schema: v13 avec store: tsdb |
| Index store | boltdb-shipper |
tsdb |
Migrez vers TSDB (BoltDB déprécié) |
| Rétention | 0s (conservation illimitée) | 0s (conservation illimitée) | Définissez retention_period explicitement |
| Métadonnées structurées | Désactivé | Activé | Nécessite le schéma v13 |
| Labels max par série | 30 | 15 | Réduisez la cardinalité des labels ou augmentez la limite |
| Image Docker | Inclut le shell BusyBox | Pas de shell | Impossible d'exécuter docker exec dans le conteneur |
L'image Docker sans shell de Loki 3.6+ signifie que vous ne pouvez pas lancer docker exec -it loki sh pour le débogage. Consultez plutôt les logs avec docker compose logs loki et la disponibilité avec curl http://127.0.0.1:3100/ready.
Système de fichiers vs stockage objet
Pour un VPS unique, le stockage sur système de fichiers est le bon choix. Le stockage objet (S3, GCS, MinIO) ajoute de la complexité et de la latence qui ne se justifient que si vous avez besoin de :
- Plusieurs instances Loki partageant les mêmes données
- Un stockage illimité au-delà du disque de votre VPS
- Une réplication multi-régions
Restez sur le stockage fichier tant que vous n'avez pas dépassé les capacités d'un nœud unique.
Surveiller la stack
Vérifiez l'utilisation des ressources des conteneurs en cours d'exécution :
docker stats --no-stream
Vérifiez les logs Loki pour les avertissements et erreurs :
docker compose logs loki --tail=50 | grep -E "level=(error|warn)"
Problèmes courants et solutions :
stream limit exceeded: augmentezmax_streams_per_userdanslimits_config. Généralement causé par des labels à haute cardinalité.ingestion rate limit reached: augmentezingestion_rate_mb. Se produit lors de pics de logs (déploiements, tempêtes d'erreurs).WAL replay: normal au démarrage. Loki récupère les écritures non validées depuis le journal d'écriture anticipée.- Utilisation mémoire élevée : réduisez
max_size_mbdanschunk_cache_configou diminuezingestion_burst_size_mb.
Promtail est-il déprécié ? Dois-je utiliser Grafana Alloy à la place ?
Oui. Promtail est entré en support à long terme le 13 février 2025 et a atteint sa fin de vie le 2 mars 2026. Plus aucune mise à jour, correction de bugs ou patch de sécurité ne sera publié. Grafana Alloy est le remplacement officiel. C'est la distribution par Grafana Labs du collecteur OpenTelemetry, qui gère les logs, les métriques, les traces et les données de profilage dans un agent unique.
Pourquoi ce tutoriel utilise encore Promtail
Les concepts de configuration de Promtail se transposent directement à Alloy. Les scrape configs, les règles de relabeling et les étapes de pipeline fonctionnent de la même façon. Apprendre Promtail reste utile car :
- Des millions de déploiements existants l'utilisent
- L'outil de migration Alloy convertit automatiquement les configs Promtail
- Comprendre Promtail facilite le débogage des configurations Alloy
- Le volume de recherche pour les tutoriels Promtail reste élevé, et les concepts sont transférables
Migrer vers Alloy
Convertissez votre configuration Promtail au format Alloy en une commande :
alloy convert --source-format=promtail --output=alloy-config.alloy promtail-config.yml
Cela génère un fichier de configuration au format Alloy. Relisez la sortie avant de déployer. Le convertisseur gère la plupart des cas mais peut nécessiter des ajustements manuels pour les étapes de pipeline personnalisées.
Puis remplacez le service Promtail dans docker-compose.yml :
alloy:
image: grafana/alloy:v1.14.1
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12345
volumes:
- ./alloy-config.alloy:/etc/alloy/config.alloy:ro
- /var/log:/var/log:ro
- /var/log/journal:/var/log/journal:ro
- /run/log/journal:/run/log/journal:ro
- /etc/machine-id:/etc/machine-id:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
depends_on:
- loki
networks:
- loki-net
Supprimez le bloc du service promtail et lancez docker compose up -d. Alloy commencera à collecter les mêmes sources de logs avec la configuration convertie.
Pour les nouveaux déploiements partant de zéro, utilisez Alloy dès le départ. Pour les installations Promtail existantes, planifiez la migration sans urgence immédiate. Le binaire Promtail continue de fonctionner. Épinglez le tag de l'image (grafana/promtail:3.6.7) pour contrôler ce qui tourne.
Comment interroger les logs Loki par programmation via l'API HTTP ?
Loki expose une API REST pour les requêtes programmatiques de logs. C'est ainsi que vous intégrez Loki avec des scripts, des pipelines d'alerte, ou la couche d'analyse de logs par IA couverte dans Analyse de logs par IA avec Ollama sur un VPS : détecter les anomalies avec un LLM local.
L'API accepte les mêmes requêtes LogQL que celles utilisées dans Grafana. L'endpoint principal est /loki/api/v1/query_range pour les requêtes sur une plage de temps.
Interroger les logs récents
END=$(date +%s)000000000
START=$(( $(date +%s) - 3600 ))000000000
curl -s "http://127.0.0.1:3100/loki/api/v1/query_range" \
--data-urlencode "query={unit=\"sshd.service\"} |= \"Failed password\"" \
--data-urlencode "start=$START" \
--data-urlencode "end=$END" \
--data-urlencode "limit=50" | jq .
Les paramètres start et end utilisent des timestamps Unix en nanosecondes. Le calcul shell ci-dessus détermine « maintenant moins 1 heure » et ajoute neuf zéros pour la précision nanoseconde.
La réponse est en JSON :
{
"status": "success",
"data": {
"resultType": "streams",
"result": [
{
"stream": {"unit": "sshd.service", "severity": "info"},
"values": [
["1710850000000000000", "Mar 19 14:00:00 vps sshd[1234]: Failed password for root from 203.0.113.5 port 22"]
]
}
]
}
}
Chaque valeur est une paire [timestamp_nanosecondes, ligne_de_log]. C'est le format exact que vous parserez pour alimenter un LLM local en analyse de logs.
Interroger les labels et les flux
Lister tous les noms de labels :
curl -s "http://127.0.0.1:3100/loki/api/v1/labels" | jq .
Lister les valeurs d'un label spécifique :
curl -s "http://127.0.0.1:3100/loki/api/v1/label/unit/values" | jq .
Ces endpoints sont utiles pour construire des requêtes dynamiques dans des scripts d'automatisation. Vous pouvez énumérer toutes les unités, puis interroger chacune pour des erreurs.
Requêtes instantanées
Pour des requêtes « à l'instant T » sans plage de temps, utilisez /loki/api/v1/query :
curl -s "http://127.0.0.1:3100/loki/api/v1/query" \
--data-urlencode 'query=count_over_time({job="systemd-journal"} |= "error" [1h])' | jq .
Cela renvoie un point de données unique : le nombre d'erreurs du journal sur la dernière heure. Utile pour les vérifications de santé et les scripts de monitoring.
Dépannage
Promtail affiche « permission denied » pour le socket Docker :
Le conteneur Promtail a besoin d'un accès en lecture à /var/run/docker.sock. Vérifiez les permissions du socket sur l'hôte :
ls -la /var/run/docker.sock
Le socket appartient généralement à root:docker. L'image Promtail tourne en root par défaut, ce qui fonctionne normalement. Si vous exécutez Promtail avec un utilisateur personnalisé, cet utilisateur doit être dans le groupe docker.
Pas de logs journal qui apparaissent :
D'abord, vérifiez les logs de Promtail pour le message support for reading the systemd journal is not compiled into this build of promtail. Si vous le voyez, l'image Docker ne supporte pas le scraping du journal. Installez Promtail comme binaire sur l'hôte ou passez à Grafana Alloy (voir les sections ci-dessus).
Si Promtail tourne comme binaire hôte avec le support journal, vérifiez que le répertoire du journal existe :
ls -la /var/log/journal/
S'il n'existe pas, systemd utilise un stockage journal volatile (en mémoire uniquement). Activez le stockage persistant :
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Puis mettez à jour le montage de volume Promtail dans docker-compose.yml de /run/log/journal à /var/log/journal et redémarrez :
docker compose up -d promtail
Loki signale « too many outstanding requests » :
La charge de requêtes dépasse la capacité de Loki. Réduisez la plage de temps de vos requêtes ou ajoutez des limites :
limits_config:
max_query_parallelism: 16
max_query_series: 500
Grafana affiche « Data source connected, no labels found » :
Loki a besoin de quelques minutes pour ingérer et indexer les premiers logs. Attendez 2-3 minutes, puis relancez la requête. Vérifiez que Loki est prêt :
curl -s http://127.0.0.1:3100/ready
Vérifier les logs de tous les services d'un coup :
docker compose logs -f --tail=50
Cela suit les trois services. Filtrez les problèmes :
docker compose logs --tail=100 | grep -i error
Prochaines étapes
Votre pipeline Loki collecte les logs de systemd, Docker et Nginx. Vous pouvez les interroger avec LogQL dans Grafana ou via l'API HTTP.
À partir d'ici :
- Analyse de logs par IA avec Ollama sur un VPS : détecter les anomalies avec un LLM local Alimentez ces logs dans un LLM local pour la détection d'anomalies et l'alerte automatisée
- AIOps sur un VPS : gestion de serveur par IA avec des outils open source Vue d'ensemble de la stack AIOps auto-hébergée
- Auto-héberger SigNoz ou OpenObserve sur un VPS : alternatives à Datadog comparées Si vous préférez un APM tout-en-un à la stack Grafana
Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.