26 - Tester ses pipelines avant la prod
Ce que tu vas apprendre
- Valider la syntaxe d'un pipeline sans le démarrer
- Tester un pipeline avec des donnees d'echantillon
- Écrire un script de test automatise
- Intégrer la validation dans une CI (GitHub Actions)
- Organiser les fichiers de test
Prerequisites
- Avoir un pipeline a tester
- Connaitre Docker et Docker Compose
Tester un pipeline, c'est pas optionnel
J'ai pousse un changement de pipeline Logstash en prod un vendredi soir. Un caractère en trop dans un pattern Grok. Le pipeline a recharge a chaud, le Grok a echoue sur 100% des événements, et tous les logs de la nuit sont partis dans _grokparsefailure sans les champs extraits. Les dashboards Kibana etaient vides le lundi matin.
Depuis, je teste chaque changement de pipeline avant de déployer. Pas avec des outils sophistiques. Avec des fichiers de test et un script bash.
Validation de syntaxe
La première vérification, la plus rapide :
bashdocker exec logstash bin/logstash --config.test_and_exit \
-f /usr/share/logstash/pipeline/main.conf
Si la syntaxe est valide :
Configuration OK
Si elle est invalide :
ERROR: Expected one of [ \t\r\n], "#", "input", "filter", "output" at line 14, column 3
Le message indique la ligne et la colonne. Ca détecté les accolades manquantes, les => oublies, et les blocs mal fermes.
Dans un script
bash#!/bin/bash
# scripts/validate.sh
CONFIG_DIR="/usr/share/logstash/pipeline"
ERRORS=0
for conf in ${CONFIG_DIR}/*.conf; do
echo "Validating $(basename $conf)..."
if docker exec logstash bin/logstash --config.test_and_exit -f "$conf" 2>&1 | grep -q "Configuration OK"; then
echo " OK"
else
echo " FAILED"
ERRORS=$((ERRORS + 1))
fi
done
if [ $ERRORS -gt 0 ]; then
echo "FAILED: $ERRORS file(s) with errors"
exit 1
fi
echo "All configurations valid"
Tester avec des donnees d'echantillon
La validation de syntaxe ne détecté pas les erreurs logiques. Un pattern Grok valide peut ne rien matcher. Pour ca, tu as besoin de donnees de test.
Structure du projet de test
logstash/
├── pipeline/
│ └── main.conf
├── test/
│ ├── input/
│ │ ├── nginx-access.log
│ │ ├── app-json.log
│ │ └── edge-cases.log
│ ├── expected/
│ │ ├── nginx-expected.json
│ │ └── app-expected.json
│ └── pipeline/
│ └── test-main.conf
└── scripts/
└── test.sh
Fichiers de test
# test/input/nginx-access.log
192.168.1.10 - admin [31/Mar/2026:14:23:01 +0000] "GET /api/users HTTP/1.1" 200 1234 "https://example.com" "Mozilla/5.0"
10.0.0.5 - - [31/Mar/2026:14:23:02 +0000] "POST /api/orders HTTP/1.1" 201 567 "-" "curl/8.4.0"
invalid line that should fail grok
192.168.1.10 - admin [31/Mar/2026:14:23:03 +0000] "DELETE /api/users/42 HTTP/1.1" 404 89 "-" "Mozilla/5.0"
La troisieme ligne est intentionnellement invalide. On veut vérifier que le pipeline gere le cas d'erreur.
Pipeline de test
Le pipeline de test remplace les inputs/outputs par des versions locales :
# test/pipeline/test-main.conf
input {
file {
path => "/test/input/nginx-access.log"
start_position => "beginning"
sincedb_path => "/dev/null"
exit_after_read => true
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
remove_field => ["timestamp"]
}
mutate {
convert => {
"response" => "integer"
"bytes" => "integer"
}
remove_field => ["message", "@version", "event", "host", "log"]
}
}
output {
file {
path => "/test/output/result.json"
codec => json_lines
}
}
Le exit_after_read => true (disponible dans les versions recentes du file input) fait que Logstash quitte apres avoir lu tout le fichier. Pas besoin de timeout.
Le script de test
bash#!/bin/bash
# scripts/test.sh
set -e
echo "=== Logstash Pipeline Tests ==="
# Nettoyer les outputs precedents
rm -f test/output/result.json
# Lancer Logstash avec le pipeline de test
echo "Running pipeline on test data..."
docker run --rm \
-v "$(pwd)/test:/test" \
-v "$(pwd)/logstash/patterns:/usr/share/logstash/patterns:ro" \
docker.elastic.co/logstash/logstash:8.17.0 \
bin/logstash -f /test/pipeline/test-main.conf \
--path.data /tmp/logstash-test \
2>/dev/null
# Verifier que l'output existe
if [ ! -f test/output/result.json ]; then
echo "FAIL: No output produced"
exit 1
fi
# Compter les evenements
TOTAL=$(wc -l < test/output/result.json)
echo "Events produced: $TOTAL"
# Verifier le nombre d'evenements attendus
EXPECTED=3
if [ "$TOTAL" -ne "$EXPECTED" ]; then
echo "FAIL: Expected $EXPECTED events, got $TOTAL"
exit 1
fi
# Verifier qu'un evenement a le tag _grokparsefailure (la ligne invalide)
FAILURES=$(grep -c "grokparsefailure" test/output/result.json || true)
if [ "$FAILURES" -ne 1 ]; then
echo "FAIL: Expected 1 grok failure, got $FAILURES"
exit 1
fi
# Verifier les champs d'un evenement valide
FIRST=$(head -1 test/output/result.json)
if echo "$FIRST" | python3 -c "
import json, sys
e = json.load(sys.stdin)
assert e['response'] == 200, f'Expected 200, got {e[\"response\"]}'
assert e['verb'] == 'GET', f'Expected GET, got {e[\"verb\"]}'
assert e['clientip'] == '192.168.1.10', f'Expected 192.168.1.10, got {e[\"clientip\"]}'
print('Field assertions: OK')
" 2>&1; then
echo "Field checks: PASSED"
else
echo "FAIL: Field assertions failed"
exit 1
fi
echo ""
echo "=== All tests PASSED ==="
Ce script :
- Lance Logstash dans un conteneur éphémère avec le pipeline de test
- Verifie que le bon nombre d'événements est produit
- Verifie qu'un événement a echoue au Grok (la ligne invalide)
- Verifie les champs d'un événement valide (type, valeur)
Intégration CI : GitHub Actions
yaml# .github/workflows/logstash-test.yml
name: Test Logstash Pipelines
on:
push:
paths:
- 'logstash/**'
pull_request:
paths:
- 'logstash/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate pipeline syntax
run: |
for conf in logstash/pipeline/*.conf; do
echo "Validating $conf..."
docker run --rm \
-v "$(pwd)/logstash/pipeline:/pipeline:ro" \
docker.elastic.co/logstash/logstash:8.17.0 \
bin/logstash --config.test_and_exit \
-f "/pipeline/$(basename $conf)"
done
- name: Run pipeline tests
run: |
chmod +x scripts/test.sh
./scripts/test.sh
La CI :
- Se déclenché quand un fichier dans
logstash/change - Valide la syntaxe de chaque
.conf - Execute le script de test avec des donnees d'echantillon
Si un test echoue, le PR est bloque. Plus de pipeline casse en prod.
Bonnes pratiques
Fichiers de test representatifs
Inclus dans tes fichiers de test :
- Des lignes normales (le cas standard)
- Des lignes avec des champs manquants
- Des lignes avec des caractères speciaux (accents, emojis, unicode)
- Des lignes volontairement invalides (pour tester la gestion d'erreur)
- Les edge cases de ta production (les formats rares que tu as deja rencontres)
Versionner les fichiers de test
Les fichiers de test sont dans le meme repo que les pipelines. Quand tu modifies un pattern Grok, tu mets à jour les fichiers de test dans le meme commit.
Un test par pipeline
Si tu as 3 pipelines (nginx, app, syslog), tu as 3 jeux de test avec 3 scripts. Pas un mega-test qui teste tout.
Sur paltemps.fr, chaque modification de pipeline passe par un PR avec les tests qui tournent en CI. Ca prend 30 secondes. Ca évité les catastrophes du vendredi soir.
Résumé
--config.test_and_exitvalide la syntaxe sans démarrer le pipeline- Teste avec des fichiers d'echantillon representatifs (cas normaux + erreurs + edge cases)
- Le script de test lance Logstash dans un conteneur éphémère et vérifié les outputs
- La CI valide automatiquement chaque changement de pipeline
- Inclus des lignes invalides dans les tests pour vérifier la gestion d'erreur
- Un jeu de test par pipeline, versionne avec le code
Precedent : 25 - Debugging | Suivant : 27 - Cas pratique : logs applicatifs