Sommaire
La stack Elasticsearch, Logstash, Kibana a été la réponse standard à la centralisation des logs pendant près d’une décennie. Elle fonctionne encore. Mais pour les équipes qui font tourner Kubernetes sur VCF, son coût opérationnel — nœuds Elasticsearch gourmands en mémoire, gestion complexe des index, requêtes froides lentes — est devenu un problème réel.
La stack PLG (Promtail ou Fluent Bit, Loki, Grafana) échange l’indexation plein texte contre une indexation par labels et délivre une réduction du coût de stockage de 10 à 20 fois pour des volumes de logs Kubernetes typiques. Le compromis est réel : sans indexation plein texte, vous ne pouvez pas faire une recherche dans le corps du log via l’index. Vous pouvez filtrer sur les labels et utiliser des regex dans les requêtes LogQL, mais cela scanne des chunks plutôt que de frapper un index. Pour les équipes plateforme dont 90% des requêtes sont “montrez-moi tous les logs d’erreur du pod X dans le namespace Y”, le compromis est valable.
Architecture de Loki
Loki est un système d’agrégation de logs inspiré de Prometheus. Sa décision de conception clé est de stocker uniquement les labels comme index et le contenu des logs comme chunks compressés en object storage.
Un déploiement Loki en production se compose de cinq composants principaux :
Distributor — reçoit les flux de logs des clients (Fluent Bit, Promtail, OTel Collector). Valide la structure du flux, applique les limites d’ingestion, et distribue vers les ingesters. Le distributor est le point d’entrée du chemin d’écriture et est stateless.
Ingester — conserve les chunks de logs entrants en mémoire jusqu’à ce qu’ils atteignent un intervalle de flush configurable (défaut : 5 minutes) ou un seuil de taille, puis les écrit en object storage. L’ingester répond aussi aux requêtes sur les données récentes avant que les chunks ne soient flushés. Il est stateful — il faut faire tourner au moins 3 ingesters avec un facteur de réplication de 2 ou 3 pour la résilience en production.
Querier — gère les requêtes LogQL en récupérant les chunks depuis les ingesters (données récentes) et l’object storage (données historiques), puis en appliquant les expressions de filtre et de métrique localement. Passe à l’échelle horizontalement.
Compactor — applique les politiques de rétention, la déduplication et la compaction des index. Un processus de fond à un seul réplica ; pas dans le chemin de requête à chaud.
Query Frontend (optionnel mais recommandé) — se place devant les queriers, divise les grandes requêtes sur des plages de temps en sous-requêtes parallèles plus petites, met les résultats en cache. Améliore significativement les performances pour les grandes plages temporelles.
Loki en mode monolithique vs microservices
Le chart Helm utilise par défaut le mode monolithique (tous les composants dans un seul process) pour le développement. Pour la production sur VKS, passez en mode microservices ou au minimum en cible simple-scalable, qui sépare les chemins de lecture et d’écriture en déploiements distincts qui passent à l’échelle indépendamment.
Déploiement de Loki sur VKS
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm upgrade --install loki grafana/loki \
--namespace logging \
--create-namespace \
--values loki-values.yaml \
--version 6.x
# loki-values.yaml — production sur VKS
loki:
auth_enabled: false
commonConfig:
replication_factor: 2
storage:
type: filesystem
filesystem:
chunks_directory: /var/loki/chunks
rules_directory: /var/loki/rules
limits_config:
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_label_names_per_series: 15
max_entries_limit_per_query: 5000
retention_period: 744h # 31 jours
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: loki_index_
period: 24h
deploymentMode: SimpleScalable
read:
replicas: 2
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 1000m, memory: 1Gi }
persistence:
storageClass: vsan-default-storage-policy
size: 10Gi
write:
replicas: 3
resources:
requests: { cpu: 200m, memory: 512Mi }
limits: { cpu: 1000m, memory: 2Gi }
persistence:
storageClass: vsan-default-storage-policy
size: 20Gi
backend:
replicas: 1
persistence:
storageClass: vsan-default-storage-policy
size: 10Gi
Fluent Bit : le pipeline DaemonSet
Fluent Bit est le processeur et forwarder de logs léger conçu pour Kubernetes. Il tourne en DaemonSet — un pod par nœud — lisant les fichiers de logs depuis le système de fichiers hôte et les expédiant avec enrichissement de métadonnées Kubernetes.
ConfigMap complet
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: logging
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser cri
DB /run/fluent-bit/flb_kube.db
Mem_Buf_Limit 50MB
Skip_Long_Lines On
Refresh_Interval 10
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Merge_Log_Key log_processed
Keep_Log Off
Annotations Off
Labels On
[FILTER]
Name grep
Match kube.*
Exclude $kubernetes['namespace_name'] monitoring
[OUTPUT]
Name loki
Match kube.*
Host loki-gateway.logging.svc.cluster.local
Port 80
Labels job=fluent-bit,namespace=$kubernetes['namespace_name'],pod=$kubernetes['pod_name'],container=$kubernetes['container_name'],node=$kubernetes['host']
Line_Format json
Auto_Kubernetes_Labels Off
Retry_Limit False
Batch_wait 1s
Batch_size 1048576
parsers.conf: |
[PARSER]
Name cri
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
Manifest DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels:
app: fluent-bit
template:
metadata:
labels:
app: fluent-bit
spec:
serviceAccountName: fluent-bit
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: fluent-bit
image: cr.fluentbit.io/fluent/fluent-bit:3.3
ports:
- containerPort: 2020
name: http-metrics
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: config
mountPath: /fluent-bit/etc/
- name: db
mountPath: /run/fluent-bit
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { cpu: 200m, memory: 256Mi }
volumes:
- name: varlog
hostPath:
path: /var/log
- name: config
configMap:
name: fluent-bit-config
- name: db
emptyDir: {}
Le filtre d'exclusion du namespace monitoring
Sans le filtre grep excluant les logs du namespace monitoring, Grafana et Prometheus génèrent des logs que Fluent Bit envoie à Loki, que Grafana interroge, générant d’autres logs. La boucle de rétroaction peut saturer votre pipeline d’ingestion en quelques minutes sur un cluster actif.
LogQL : interroger vos logs
LogQL est le langage de requête de Loki, modelé sur PromQL. Il existe sous deux formes : les requêtes de logs (retournent des lignes de log) et les requêtes de métriques (calculent des métriques depuis des lignes de log).
Anatomie d’une requête de log
{namespace="production", pod=~"api-.*"} # sélecteur de flux (obligatoire)
| json # parser : extraire les champs JSON
| level = "error" # filtre de ligne
| message =~ "timeout|connection refused" # filtre regex sur champ extrait
| line_format "{{.timestamp}} {{.message}}" # formater la sortie
Le sélecteur de flux {namespace="production"} est l’équivalent d’un sélecteur de labels Prometheus — il réduit les flux de logs récupérés depuis l’object storage. Tous les filtres après | sont appliqués en mémoire sur les chunks récupérés. Cela signifie que l’efficacité du sélecteur de flux détermine les performances des requêtes. Incluez toujours au minimum namespace et idéalement pod ou container dans le sélecteur de flux.
Exemples de requêtes de métriques
# Taux d'erreur par namespace sur 5 minutes
sum by(namespace) (rate({job="fluent-bit"} | json | level="error" [5m]))
# Octets reçus par pod sur 1 minute
sum by(pod) (bytes_rate({job="fluent-bit"}[1m]))
# P99 de latence extrait depuis les logs structurés
quantile_over_time(0.99,
{namespace="production"} | json | unwrap duration_ms [10m]
) by (service)
EFK vs PLG : la comparaison honnête
Coût de stockage
Performances des requêtes
Complexité opérationnelle
Quand choisir Elasticsearch
Intégration avec Grafana
Ajoutez Loki comme source de données dans Grafana sous Configuration → Sources de données → Ajouter une source de données → Loki. Définissez l’URL sur http://loki-gateway.logging.svc.cluster.local:80. Activez les champs dérivés pour créer des liens automatiques depuis les lignes de log contenant un champ traceId vers la trace correspondante dans Tempo — c’est la corrélation log-vers-trace qui rend la réponse aux incidents dramatiquement plus rapide.
Références.
- Documentation Loki — référence officielle d’architecture, paramètres de configuration et spécification LogQL
- Documentation Fluent Bit — référence des plugins INPUT/FILTER/OUTPUT, configuration du filtre Kubernetes
- Référence LogQL — syntaxe complète pour les requêtes de logs et de métriques
- Documentation stockage Loki — options backend, configuration du schéma, rétention
Reçois le prochain par email
Les nouveaux articles et séries, envoyés à leur publication. Aucun autre courrier.



