
Je gère un cluster RKE2 avec une trentaine de workloads. Des apps Python, du Go, du Java, du Traefik, des opérateurs Kubernetes. Chaque composant loggue dans son propre format.
Pendant longtemps, j’ai utilisé Datadog pour mes logs. Le produit est vraiment bon - l’interface, le parsing, les alertes, tout fonctionne. Mais j’ai voulu sortir des SaaS cloud. Garder mes données chez moi, ne plus dépendre d’un service externe, et ne plus payer au volume de logs ingérés.
J’ai monté une stack avec Vector, VictoriaLogs et Grafana. Vector collecte et parse les logs en temps réel, VictoriaLogs les stocke avec une empreinte mémoire ridicule, et Grafana les affiche via 5 dashboards auto-provisionnés. Le tout déployé en GitOps via ArgoCD.
Architecture
Pods Kubernetes (30+ workloads)
│
▼
Vector DaemonSet (collecte + parsing)
├─ cleanup_fields ─── supprime les labels k8s inutiles
├─ detect_and_parse ── détecte et parse 10 formats de logs
├─ categorize_errors ─ classifie les erreurs en 10 catégories
├─ merge_stack_trace ─ fusionne les stack traces multi-lignes
│
▼
VictoriaLogs (stockage)
├─ Index journalier : vector-YYYY-MM-DD
├─ Compression gzip
├─ API compatible Elasticsearch
│
▼
Grafana (visualisation)
├─ Plugin victoriametrics-logs-datasource
└─ 5 dashboards auto-provisionnés via sidecar
Vector tourne en DaemonSet - un pod par node qui lit les logs de tous les containers via le filesystem. Les logs passent par 4 transforms en pipeline avant d’être envoyés à VictoriaLogs via l’API Elasticsearch bulk.
Le parsing intelligent avec VRL
J’ai écrit un script VRL (Vector Remap Language) de 525 lignes qui détecte automatiquement le format de chaque log et l’extrait en champs structurés.
Le script teste les formats dans l’ordre : JSON d’abord (le plus courant), puis Python, logfmt, klog, Go, Java, syslog, Nginx, et en dernier plain text avec détection de level par regex.
Voici un exemple concret. Ce log brut JSON Traefik :
{"RequestMethod":"GET","RequestPath":"/api/health","DownstreamStatus":200,"Duration":1250000}
Devient après parsing :
{
"log_format": "traefik_access",
"level": "info",
"method": "GET",
"path": "/api/health",
"status_code": 200,
"duration_ms": 1,
"message": "GET /api/health 200 1ms"
}
Un log klog Kubernetes comme E0126 10:30:45.123456 12345 file.go:123] Error occurred est parsé en format klog, level error, avec le fichier source et le numéro de ligne extraits.
Le script normalise aussi les niveaux : WARNING devient warn, CRITICAL devient fatal. Au final, tous les logs ont le même schéma quel que soit le format d’origine.
La configuration Vector référence ces scripts VRL comme fichiers externes montés depuis un ConfigMap :
transforms:
detect_and_parse:
type: "remap"
inputs:
- cleanup_fields
file: "/etc/vector/vrl/detect_and_parse.vrl"
categorize_errors:
type: "remap"
inputs:
- detect_and_parse
file: "/etc/vector/vrl/categorize_errors.vrl"
Les scripts VRL vivent dans le repo Git, pas inline dans les values Helm. Ça permet de les versionner, tester et reviewer indépendamment.
La catégorisation des erreurs
Le deuxième script VRL est plus court - 43 lignes. Il prend tous les logs en error ou fatal et les classe par catégorie via des regex sur le message :
timeout ── "timeout", "timed out", "deadline exceeded"
network ── "connection refused", "unreachable", "ECONNREFUSED"
memory ── "out of memory", "oom", "MemoryError"
auth ── "permission denied", "unauthorized", "403"
not_found ── "not found", "404", "ENOENT"
crash ── "panic", "segfault", "SIGSEGV"
validation ── "invalid", "malformed", "ValueError"
database ── "sql", "postgres", "connection pool"
rate_limit ── "rate limit", "throttle", "429"
ssl ── "ssl", "tls", "certificate"
Ça permet de filtrer dans Grafana par type d’erreur. Au lieu de chercher dans des milliers de lignes, je filtre error_category = "timeout" et je vois tous les timeouts du cluster en un coup d’œil.
Le workflow GitOps
Modifier le parsing suit un workflow strict :
- Editer les fichiers
.vrldansextra/vector-vrl/ - Valider avec
./validate.sh- le script teste la syntaxe et passe 18 cas de test - Générer le ConfigMap avec
./generate-configmap.sh - Commit + push
- ArgoCD sync automatique, Vector recharge les scripts
Le script de validation est pratique. Il lance chaque log de test dans le parser et vérifie que le format et le level sont bien détectés :
$ ./validate.sh
Vector VRL Validator
==========================================
1. Validating VRL syntax...
✓ Syntax OK
2. Running test cases...
✓ format=json level=error {"level":"error","msg":"connection fa...
✓ format=python level=error 2024-01-26 10:30:45,123 - ERROR - mya...
✓ format=klog level=info I0126 10:30:45.123456 12345 file.go...
✓ format=go level=error 10:30:45 ERR connection failed error=...
...
Results: 18 passed, 0 failed
All tests passed!
Si un test échoue, le generate-configmap.sh refuse de générer le ConfigMap. Pas de risque de pousser un script cassé en prod.
Les dashboards Grafana
Grafana est configuré avec le plugin victoriametrics-logs-datasource et un sidecar qui scanne les ConfigMaps avec le label grafana_dashboard: "1".
J’ai 5 dashboards :
- Simple - filtre par namespace/workload avec recherche texte
- Errors - taux d’erreurs, breakdown par namespace, container et catégorie
- Format Analysis - distribution des formats de logs, volume d’erreurs par format
- Custom Views - top error patterns, severity distribution, log explorer filtré
- Monitoring - métriques internes de VictoriaLogs (ingestion rate, storage, performance)
Le log explorer permet de chercher dans tous les logs du cluster avec des filtres par namespace, pod et container. Ici une recherche sur les erreurs :

Les dashboards sont des ConfigMaps dans le repo Git. Pour en ajouter un, je crée un YAML avec le bon label et ArgoCD le déploie au prochain sync.
Le datasource Grafana est aussi auto-provisionné :
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-victorialogs-datasource
labels:
grafana_datasource: "1"
data:
victorialogs-datasource.yaml: |
apiVersion: 1
datasources:
- name: VictoriaLogs
type: victoriametrics-logs-datasource
access: proxy
url: http://victorialogs-victoria-logs-single-server.kube-monitoring.svc.cluster.local:9428
Déploiement
Tout est géré par ArgoCD. Vector utilise le chart Helm officiel v0.50.0 depuis helm.vector.dev. VictoriaLogs utilise le chart victoria-logs-single v0.11.25.
Les trois composants sont dans le namespace kube-monitoring. VictoriaLogs est exposé en interne sur le port 9428 et en externe via un ingress Traefik sur logs.dc-tech.work avec TLS Let’s Encrypt.
Vector envoie les logs en mode Elasticsearch bulk avec compression gzip, des batchs de 10 Mo max ou 1000 events, et un flush toutes les 2 secondes. Les stream fields sont configurés pour optimiser le stockage : kubernetes.pod_name, kubernetes.pod_namespace et le node name.
Champs disponibles après parsing
Après le passage dans Vector, chaque log contient potentiellement :
- Contexte k8s :
kubernetes.pod_name,kubernetes.pod_namespace,kubernetes.container_name,kubernetes.node_name - Parsing :
log_format,level,message,original_message,logger - Erreurs :
error_category,error_type,error_message,traceback - Code :
file,line,function,thread - HTTP :
request_id,status_code,method,path,client_ip,duration_ms
Ça donne une base uniforme pour les requêtes dans Grafana, quel que soit le format d’origine du log.
Code source
Les fichiers de configuration sont disponibles sur GitHub :
values.in-cluster.yaml- Configuration Helm de Vector avec le pipeline de transformsdetect_and_parse.vrl- Parser multi-format VRL (525 lignes)categorize_errors.vrl- Catégorisation des erreursgenerate-configmap.sh- Génération du ConfigMap depuis les VRLvalidate.sh- Validation et tests des scripts VRLvictorialogs-datasource.yaml- Datasource Grafana auto-provisionné