🎯 OBJECTIF
Comprendre comment :
🧠 MODÈLE MENTAL
Un jeu de tests synthétiques décrit ce qu'on pense que les utilisateurs font. Le trafic de prod décrit ce qu'ils font vraiment : les enchaînements bizarres, les payloads mal formés, les pics à 11h42, le magasin qui signale douze articles manquants d'affilée. Le traffic replay part de cette idée simple : la meilleure source de vérité pour tester un service, c'est le trafic qu'il a déjà reçu.
Il y a deux façons de s'en servir. En shadowing, on duplique le trafic en temps réel vers une seconde instance et on jette sa réponse : la prod ne voit rien, la copie encaisse la même charge et on compare après coup. En replay différé, on enregistre le trafic dans un fichier, puis on le rejoue plus tard, ailleurs, à la vitesse qu'on veut : c'est une cassette de la journée qu'on peut repasser sur un environnement remis à l'état de la veille.
GoReplay est l'outil de référence open source pour les deux. Sa particularité, et c'est ce qui le rend indolore à déployer : il n'est pas un proxy. Il écoute l'interface réseau à côté de l'application, comme un tcpdump qui comprendrait HTTP. Rien ne passe par lui, il ne peut donc pas casser la prod. Le prix à payer est qu'il ne voit que du HTTP en clair et qu'il ne sait rien de ce que le service fait derrière : les identifiants générés, les tokens qui expirent et les effets de bord restent ton problème. Toute la difficulté du replay est là, pas dans l'outil.
CAP_NET_RAW, pas de capture..gor — format texte de GoReplay : une ligne de métadonnées puis la requête HTTP brute, séparées par un marqueur.POST de création ne l'est généralement pas.Avant de choisir un outil, il faut choisir une famille. Elles ne répondent pas au même besoin.
flowchart LR
subgraph A["🔴 Shadowing temps réel"]
direction TB
A1[Trafic prod] --> A2[Copie immédiate]
A2 --> A3[Instance candidate]
A3 -.réponse jetée.-> A2
end
subgraph B["🟡 Replay différé"]
direction TB
B1[Trafic prod] --> B2[(Fichier de capture)]
B2 -->|plus tard, ailleurs,<br/>vitesse au choix| B3[Env cible]
end
subgraph C["🟢 Record & replay de tests"]
direction TB
C1[Trafic + appels sortants] --> C2[(Tests + mocks générés)]
C2 --> C3[CI, sans dépendances]
endmermaid| Famille | Question à laquelle elle répond | Outils typiques |
|---|---|---|
| Shadowing temps réel | « La nouvelle version tient-elle la charge et répond-elle pareil, maintenant ? » | GoReplay --output-http, Istio/Envoy mirror, nginx mirror, Diffy |
| Replay différé | « Que se passe-t-il si je rejoue la journée d'hier sur un env remis à l'état d'hier matin ? » | GoReplay --output-file / --input-file, mitmproxy, tcpreplay |
| Record & replay de tests | « Comment transformer ce trafic en tests reproductibles sans la vraie DB ? » | Keploy, Speedscale / proxymock, WireMock record |
🔑 Conclusion clé
Le shadowing teste une version contre le trafic d'aujourd'hui. Le replay différé teste un scénario contre un état passé. Le record & replay fabrique des tests à partir du trafic. GoReplay couvre les deux premiers, pas le troisième.

GoReplay (binaire gor, écrit en Go) se place à côté du service et lit les paquets de l'interface réseau via libpcap. Il reconstitue les requêtes HTTP, leur applique filtres, réécritures et middleware, puis les envoie vers une ou plusieurs sorties. Le service d'origine ne sait même pas qu'il est écouté.
Toute la CLI se lit comme un pipeline input → output :
flowchart LR
IR["--input-raw :8080<br/>(sniff réseau)"] --> F["Filtres<br/>--http-allow-*"]
IF["--input-file cassette.gor<br/>(replay différé)"] --> F
IT["--input-tcp :28020<br/>(reçu d'un autre gor)"] --> F
F --> R["Réécriture<br/>--http-header, --http-rewrite-url"]
R --> M["Middleware<br/>(programme externe)"]
M --> OH["--output-http https://env-cible"]
M --> OF["--output-file cassette.gor"]
M --> OT["--output-tcp agregateur:28020"]
M --> OS["--output-stdout"]mermaid# 1. Voir ce qui passe (debug)
sudo gor --input-raw :8080 --output-stdout
# 2. Shadowing temps réel : chaque requête reçue en prod est copiée vers staging
sudo gor --input-raw :8080 --output-http "https://staging.example.com"
# 3. Enregistrer aujourd'hui, rejouer demain
sudo gor --input-raw :8080 --output-file "requests-%Y-%m-%d.gor"
gor --input-file "requests-2026-09-10.gor" --output-http "https://env-cible.example.com"bashLes entrées et sorties se combinent : on peut à la fois enregistrer dans un fichier et forwarder vers staging, ou lire un fichier et l'envoyer vers deux environnements.
| Flag | Rôle |
|---|---|
--input-raw :8080 |
Sniffer le port 8080 sur toutes les interfaces. Nécessite root ou cap_net_raw. |
--input-raw-track-response |
Capturer aussi les réponses d'origine (utile pour comparer ou pour le middleware). |
--input-raw-engine libpcap|raw_socket|pcap_file|vxlan |
Moteur de capture. vxlan permet de recevoir du VPC traffic mirroring AWS. |
--output-http URL |
Rejouer vers un serveur. Suffixe |10% pour n'envoyer que 10 % du trafic. |
--output-file path |
Enregistrer. %Y-%m-%d et .gz supportés. |
--input-file path |
Rejouer un fichier. Suffixe |200% pour la vitesse. Glob accepté. |
--input-file-loop |
Rejouer le fichier en boucle (load test). |
--output-tcp host:port / --input-tcp :port |
Chaîner deux gor (agrégation multi-serveurs). |
--middleware "cmd" |
Brancher un programme qui transforme requêtes et réponses. |
--prettify-http |
Décompresser et dé-chunker pour lire le trafic. |
⚠️ Ce que GoReplay ne voit pas
Sans sudo, donner la capability une fois pour toutes au binaire :
sudo setcap "cap_net_raw,cap_net_admin+eip" /usr/local/bin/gorbash.gor — une cassette avec le timingLe format est du texte brut, volontairement simple. Chaque requête est précédée d'une ligne de métadonnées et les entrées sont séparées par un marqueur qu'on ne risque pas de trouver dans un corps HTTP : 🐵🙈🙉.
Exemple tiré d'une appli de préparation de commandes click & collect : un opérateur du magasin 1234 signale un article manquant sur une commande en cours de préparation.
1 a4c3f9e2b1d04c7e8f6a5b3c2d1e0f9a 1757491201123456789
POST /api/pickings/7c1e.../missing-items HTTP/1.1
Host: bff.example.com
X-Store-Id: 1234
Content-Type: application/json
{"sku":"A-4471","quantity":2,"reason":"NOT_FOUND"}
🐵🙈🙉
2 a4c3f9e2b1d04c7e8f6a5b3c2d1e0f9a 1757491201187654321 64198532
HTTP/1.1 200 OK
Content-Type: application/json
{"id":"7c1e...","status":"IN_PROGRESS","missingItems":1}
🐵🙈🙉
La ligne de métadonnées : [type] [request_id] [timestamp_ns] [latence].
1 requête, 2 réponse d'origine, 3 réponse rejouée.Au replay, GoReplay respecte les écarts entre timestamps. Une requête à 09:00
puis une à 09:00 seront rejouées avec 7 secondes d'intervalle. Le suffixe de vitesse change le facteur :gor --input-file "cassette.gor" --output-http "https://cible" # timing d'origine
gor --input-file "cassette.gor|200%" --output-http "https://cible" # 2x plus vite
gor --input-file "cassette.gor|0" --output-http "https://cible" # sans attente, à fondbash--input-file accepte un glob. GoReplay lit les fichiers en parallèle et trie les requêtes par timestamp entre les fichiers en streaming, sans tout charger en mémoire :
gor --input-file "captures/pod-*.gor" --output-http "https://cible"bashCompression : si l'extension finit par .gz, lecture et écriture sont gzippées automatiquement.
🔑 Conclusion clé
Le .gor n'est pas une liste de requêtes, c'est une chronologie. Le replay reproduit les creux et les pics de la journée capturée, ce qu'aucun outil de charge synthétique ne sait faire aussi fidèlement.
Cas concret : rejouer un seul magasin identifié par un header, et rien d'autre.
sudo gor --input-raw :8080 \
--http-allow-header "X-Store-Id:^1234$" \
--output-file "store-1234-%Y-%m-%d.gor"bashLes filtres sont des regex et se cumulent :
| Flag | Effet | Exemple |
|---|---|---|
--http-allow-header "Nom:regex" |
Ne garder que les requêtes dont le header matche | --http-allow-header "api-version:^1\.0\d" |
--http-disallow-header "Nom:regex" |
Exclure. Utile pour ignorer son propre trafic rejoué | --http-disallow-header "User-Agent: Replayed by Gor" |
--http-allow-url regex |
Ne garder qu'un chemin | --http-allow-url ^/api/pickings |
--http-disallow-url regex |
Exclure un chemin (health checks, metrics) | --http-disallow-url ^/actuator |
--http-allow-method M |
Ne garder que certaines méthodes (répétable) | --http-allow-method POST --http-allow-method PUT |
Puis, au replay, on réécrit ce qui ne peut pas être identique sur l'env cible :
| Flag | Effet | Exemple |
|---|---|---|
--http-header "Nom: valeur" |
Écrase ou ajoute un header | --http-header "Authorization: Bearer <token-cible>" |
--http-set-param k=v |
Écrase un query param | --http-set-param api_key=test |
--http-rewrite-url pattern:remplacement |
Réécrit le chemin avec groupes de capture | --http-rewrite-url /v1/user/([^\/]+)/ping:/v2/user/$1/ping |
--http-original-host |
Conserve le header Host d'origine au lieu de celui de la cible |
|
--output-http "url|10%" |
Ne rejoue qu'un pourcentage | montée en charge progressive |
gor --input-file "store-1234-2026-09-10.gor" \
--http-disallow-url "^/actuator" \
--http-header "Authorization: Bearer eyJhbGciOi..." \
--http-header "User-Agent: Replayed by Gor" \
--output-http "https://bff.env-cible.example.com"bashLes flags font des réécritures statiques. Dès qu'il faut une décision par requête (remapper un id, choisir un token selon l'utilisateur, corréler avec la réponse précédente), on branche un middleware : n'importe quel exécutable qui lit stdin et écrit stdout.
sequenceDiagram
participant G as gor
participant M as Middleware (stdin/stdout)
participant C as Env cible
G->>M: type 1 · requête d'origine (hex)
M-->>G: requête modifiée (ou rien = drop)
G->>C: requête rejouée
C-->>G: réponse
G->>M: type 3 · réponse rejouée (hex)
Note over M: peut mémoriser un id / token<br/>renvoyé par la cible
G->>M: type 2 · réponse d'origine (si --input-raw-track-response)mermaidLe protocole : chaque message est une ligne hex-encodée. Une fois décodée, c'est l'en-tête [type] [request_id] [timestamp] [latence], un \n, puis le payload HTTP brut. Le middleware doit réémettre les réponses telles quelles et peut modifier ou ne pas réémettre les requêtes (ne rien écrire = la requête est supprimée du replay).
Squelette en Python, qui remplace le token et remappe l'id d'une préparation créée côté cible :
#!/usr/bin/env python3
import sys, re, binascii
id_map = {} # id prod -> id env cible
pending = {} # request_id -> True si c'était un POST de création
for line in sys.stdin:
raw = binascii.unhexlify(line.strip())
header, _, payload = raw.partition(b"\n")
ptype, req_id, *_ = header.split(b" ")
if ptype == b"1": # requête d'origine
payload = re.sub(rb"Authorization: .*\r\n",
b"Authorization: Bearer TOKEN_CIBLE\r\n", payload)
for prod_id, target_id in id_map.items(): # remap des ids connus
payload = payload.replace(prod_id, target_id)
if payload.startswith(b"POST /api/pickings HTTP"):
pending[req_id] = True
elif ptype == b"3" and req_id in pending: # réponse de la cible
m = re.search(rb'"id":"([0-9a-f-]{36})"', payload)
if m:
id_map[b"<id-prod-correspondant>"] = m.group(1) # à corréler avec la réponse d'origine (type 2)
sys.stdout.write(binascii.hexlify(header + b"\n" + payload).decode() + "\n")
sys.stdout.flush()
gor --input-file "cassette.gor" --middleware "./remap.py" --output-http "https://cible"bash🔑 Conclusion clé
Le middleware est le seul endroit où le replay peut apprendre de l'env cible : les ids et tokens qu'elle renvoie. C'est ce qui sépare un replay « qui envoie les mêmes octets » d'un replay « qui rejoue le même scénario ».
Trois façons de capturer, selon ce qu'on contrôle.
flowchart TB
subgraph Prod["Cluster prod"]
subgraph P1["Pod BFF 1"]
A1[app :8080]
S1[sidecar gor]
end
subgraph P2["Pod BFF 2"]
A2[app :8080]
S2[sidecar gor]
end
subgraph P3["Pod BFF 3"]
A3[app :8080]
S3[sidecar gor]
end
AG["Agrégateur gor<br/>--input-tcp :28020<br/>--output-file cassette.gor"]
S1 -- "--output-tcp" --> AG
S2 -- "--output-tcp" --> AG
S3 -- "--output-tcp" --> AG
end
AG --> F[("cassette.gor<br/>ordonnée par timestamp")]
F -->|"J+1"| T["Env cible<br/>gor --input-file --output-http"]mermaidLe sidecar partage le namespace réseau du pod, donc il voit le port de l'app en clair. Chaque sidecar envoie ce qu'il capture en TCP vers un agrégateur unique qui écrit le fichier. Pas de fusion manuelle, un seul fichier déjà ordonné.
# extrait du Deployment du BFF
containers:
- name: bff
image: registry/bff:1.2.3
ports: [{ containerPort: 8080 }]
- name: gor
image: buger/goreplay:latest
args:
- --input-raw
- ":8080"
- --http-allow-header
- "X-Store-Id:^1234$"
- --http-disallow-url
- "^/actuator"
- --output-tcp
- "gor-aggregator.tooling.svc:28020"
securityContext:
capabilities:
add: ["NET_RAW", "NET_ADMIN"]yaml# sur le pod agrégateur
gor --input-tcp :28020 --output-file "/captures/store-1234-%Y-%m-%d.gor.gz"bashk8s://Les versions récentes savent capturer depuis le nœud en ciblant des pods via l'API Kubernetes. Un DaemonSet tourne sur chaque nœud, avec un ServiceAccount qui a get / list / watch sur pods, deployments et daemonsets.
gor --input-raw "k8s://prod/deployment/bff:8080" --output-tcp "gor-aggregator:28020"bashSélecteurs disponibles : k8s://[ns/]pod/nom, k8s://[ns/]deployment/nom, k8s://[ns/]daemonset/nom, k8s://[ns/]labelSelector/app=bff, k8s://[ns/]fieldSelector/.... Sans namespace, tous les namespaces. Avantage : aucune modification des Deployments applicatifs.
Un seul point de capture, mais le pod ingress voit tout le cluster et, souvent, du TLS. À réserver si l'ingress parle en clair vers l'amont et si le filtrage par header suffit à isoler ce qu'on veut.
⚠️ Deux contraintes non négociables
gor, la capture échoue silencieusement ou refuse de démarrer. C'est l'erreur n°1 en sidecar.GoReplay renvoie les mêmes octets. Le scénario, lui, dépend de l'état du serveur. Voici ce qui casse, dans l'ordre où on le découvre.
flowchart LR
R["Requête rejouée<br/>(octets identiques)"] --> A{Token<br/>valide ?}
A -- non --> A1["401 sur tout<br/>→ --http-header Authorization"]
A -- oui --> B{Ressource<br/>/id existe ?}
B -- non --> B1["404<br/>→ dump de la veille + remap d'ids"]
B -- oui --> C{Déjà<br/>rejouée ?}
C -- oui --> C1["Doublon<br/>→ un seul run, ou idempotence"]
C -- non --> D{Dépend de<br/>la date du jour ?}
D -- oui --> D1["Réponses vides<br/>→ shift de date ou données à J-1"]
D -- non --> OK["✅ Fidèle"]mermaid| Piège | Symptôme | Parade |
|---|---|---|
| Auth expirée | 401 partout dès le lendemain, tokens signés pour la prod | --http-header "Authorization: ..." avec un compte de test, ou middleware pour un token par utilisateur. On perd l'identité réelle de l'opérateur. |
| Ids générés côté serveur | POST de création puis PUT /{id} : l'id de prod n'existe pas sur la cible, 404 |
Restaurer un dump de la cible à l'état de la veille au matin (les ids existants matchent). Pour les objets créés en journée, middleware qui remappe via la réponse de la cible (section 6). |
| Non-idempotence | Deux replays = deux signalements d'article manquant, deux commandes marquées prêtes | Un seul run par dump restauré. Ou filtrer sur --http-allow-method GET pour un test de lecture pure. |
| Décalage de dates | Le front filtre sur « aujourd'hui », la cassette contient hier : listes vides | Injecter les données d'entrée (événements Kafka, imports) la veille sur la cible, puis rejouer le lendemain : le décalage relatif est conservé. Sinon, shift de date en middleware. |
| Effets de bord aval | Le service cible publie sur Kafka, appelle un ERP, envoie des mails ou SMS | Vérifier que les consommateurs de l'env cible acceptent ce trafic, ou mocker les sorties (là, Speedscale/Keploy sont plus adaptés). |
| État initial incomplet | Un BFF agrège plusieurs backends : le dump ne couvre qu'un service | Le dump doit couvrir tout ce que le service touche, pas seulement sa propre base. |
| Health checks et probes | La cassette est polluée par /actuator/health toutes les 5 s |
--http-disallow-url "^/actuator" à la capture. |
🔑 Conclusion clé
Un replay est fidèle si, et seulement si, l'env cible est dans le même état de départ que la prod au début de la cassette. Le dump avant n'est pas une précaution, c'est la moitié de la méthode.
Si le service mesh est déjà là, une VirtualService suffit. Le trafic est dupliqué vers un second subset en fire and forget, la réponse du miroir est jetée, et Istio ajoute -shadow au header Host pour que la cible sache qu'elle reçoit une copie.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bff
spec:
hosts: [bff]
http:
- route:
- destination: { host: bff, subset: v1 }
weight: 100
mirror: { host: bff, subset: v2 }
mirrorPercentage: { value: 100.0 }yamlQuand : shadowing temps réel d'une nouvelle version. Limite : pas d'enregistrement, pas de replay différé, pas de filtrage par header sans une règle match dédiée. nginx propose l'équivalent avec la directive mirror.

Diffy (né chez Twitter) est un proxy qui envoie chaque requête vers trois instances : primary et secondary font tourner le code connu, candidate le nouveau. Les différences primary ↔ secondary mesurent le bruit (timestamps, ids aléatoires, ordre non déterministe). Seules les différences candidate ↔ primary qui dépassent ce bruit sont des régressions.

Quand : valider qu'une refonte (migration de framework, réécriture d'un service) répond pareil. Il se nourrit très bien d'un gor --output-http http://diffy:8880. Limite : lecture seule en pratique, trois instances à faire tourner, projet peu actif.
mitmdump -w flows.mitm # enregistrer (le client passe par le proxy)
mitmdump -C flows.mitm # client-side replay : rejouer les requêtes vers le serveur
mitmdump -S flows.mitm # server-side replay : répondre à la place du serveurbashQuand : on contrôle le client (tests, mobile, poste de dev), on a besoin du TLS ou du server-side replay (rejouer les réponses pour mocker un backend). Scriptable en Python avec des addons. Limite : c'est un proxy, il faut router le client vers lui. Impraticable pour du trafic prod entrant à l'échelle.
tcpdump -i eth0 -w capture.pcap port 8080
tcpreplay -i eth0 --multiplier=2 capture.pcapbashRejoue un .pcap au niveau 2-4, octet pour octet, sans rien savoir du protocole. Quand : tester un firewall, un IDS, une pile réseau. Limite : aucune réécriture applicative, les sessions TCP rejouées ne sont pas de vraies connexions avec le serveur cible. Pas fait pour tester une API.

Keploy capture au niveau noyau (eBPF) le trafic entrant et les appels sortants (Postgres, MongoDB, Redis, Kafka, HTTP). Il en fait des cas de test avec les mocks des dépendances, rejouables en CI sans base ni broker. Langage-agnostique.
Quand : transformer un flux réel en suite de tests d'intégration reproductibles. Limite : orienté tests de dev/CI, pas replay d'une journée de prod à l'échelle ; nécessite eBPF donc Linux récent et privilèges.
Sidecar proxy installé par un Operator, capture entrant et sortant, mocke les dépendances, et sait décaler les timestamps des données sensibles à la date. Version gratuite proxymock pour le poste de dev. Quand : on veut le replay + mocks + charge dans un produit intégré, avec du support. Limite : commercial, lock-in.
WireMock en mode proxy enregistre les échanges vers un backend et les rejoue comme stubs. C'est du server-side replay côté Java, très utilisé en tests. Pas un outil de replay de trafic entrant.
Un topic Kafka est déjà une cassette : les messages restent pendant la rétention avec leur timestamp. Pour rejouer la journée d'hier à un consumer, il suffit de repositionner son groupe :
kafka-consumer-groups.sh --bootstrap-server kafka:9092 \
--group picking-service --topic order-placed \
--reset-offsets --to-datetime 2026-09-10T00:00:00.000 --executebashAutres scénarios : --to-earliest, --to-latest, --by-duration PT24H, --shift-by -1000, --to-offset. Toujours faire un --dry-run d'abord, et arrêter le consumer pendant le reset (le groupe doit être inactif). Entre deux clusters, copier les messages avec MirrorMaker 2 ou un petit consumer/producer qui filtre et respecte les écarts de timestamps.
| Outil | Type | Enregistre ? | Replay différé | TLS | Filtre header | Réécriture | Mocks sortants | Licence |
|---|---|---|---|---|---|---|---|---|
| GoReplay | Sniffer | ✅ .gor |
✅ timing d'origine | ❌ (capturer en clair) | ✅ regex | ✅ flags + middleware | ❌ | Open source |
| Istio / Envoy mirror | Mesh | ❌ | ❌ | ✅ | via match |
❌ | ❌ | Open source |
| Diffy | Proxy comparateur | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | Open source |
| mitmproxy | Proxy | ✅ .mitm |
✅ client / server | ✅ | via addon | ✅ Python | ✅ server replay | Open source |
| tcpreplay | Paquets L2-L4 | via tcpdump | ✅ | opaque | ❌ | ❌ | ❌ | Open source |
| Keploy | eBPF | ✅ tests | ✅ en CI | ✅ | ❌ | ✅ | ✅ | Open source |
| Speedscale | Sidecar proxy | ✅ | ✅ | ✅ | ✅ | ✅ + shift de dates | ✅ | Commercial |
| Kafka reset-offsets | Natif broker | déjà dans le topic | ✅ à une date | n/a | par contenu (script) | ❌ | ❌ | Open source |
Scénario : une enseigne propose du click & collect. Les clients commandent en ligne, chaque commande arrive au service picking par un événement Kafka order-placed qui porte le magasin de retrait. En magasin, les préparateurs utilisent une appli mobile qui passe par un BFF, avec le header X-Store-Id, pour démarrer une préparation, signaler un article manquant et marquer la commande prête. On veut simuler la journée du magasin 1234 sur un env de test, comme une copie de ce qui s'est passé hier, avec un état de départ contrôlé.
flowchart LR
Cli[Clients web] -->|order-placed| K[(Kafka)]
K --> PK[Service picking]
Op[Préparateurs<br/>magasin 1234] -->|X-Store-Id: 1234| BFF[BFF]
BFF --> PK
BFF --> ST[Service stock]
PK -->|order-ready| N[Notifications client]mermaidDeux canaux d'entrée, donc deux choses à rejouer : les commandes (Kafka) et les gestes des préparateurs (HTTP). Les gestes n'ont de sens que si les commandes existent déjà.
sequenceDiagram
participant P as Prod (3 pods BFF + sidecars gor)
participant AG as Agrégateur gor
participant K as Kafka cible
participant DB as Bases env cible
participant T as BFF env cible
Note over P,AG: Jour J
P->>AG: --output-tcp (filtré X-Store-Id: 1234)
AG->>AG: store-1234-J.gor.gz
Note over DB,K: Soir du jour J
DB->>DB: 1. pg_dump / snapshot de TOUTES les bases touchées (picking, stock)
K->>K: 2. injecter les commandes order-placed du jour J (magasin 1234)
Note over T: Jour J+1
AG->>T: 3. gor --input-file store-1234-J.gor.gz<br/>--http-header Authorization<br/>--output-http
T-->>AG: réponses rejouées (type 3)
DB->>DB: 4. extrait cible, comparé à l'extrait prod de fin de J<br/>sur clé métier, pas sur ids ni timestampsmermaid1. Capture (jour J), sur chaque pod BFF
gor --input-raw :8080 \
--http-allow-header "X-Store-Id:^1234$" \
--http-disallow-url "^/actuator" \
--output-tcp "gor-aggregator.tooling.svc:28020"bash2. Agrégation, un seul fichier
gor --input-tcp :28020 --output-file "/captures/store-1234-%Y-%m-%d.gor.gz"bash3. Dump et commandes du jour (soir du jour J)
pg_dump -Fc picking > picking-before-replay.dump
pg_dump -Fc stock > stock-before-replay.dump
kafka-consumer-groups.sh --bootstrap-server kafka-cible:9092 --group picking-service \
--topic order-placed --reset-offsets --to-datetime 2026-09-10T00:00:00.000 --dry-runbashSi les clusters Kafka sont séparés, remplacer le reset par une copie des messages order-placed du magasin 1234 vers le topic de l'env cible.
4. Replay (jour J+1), timing d'origine
gor --input-file "/captures/store-1234-2026-09-10.gor.gz" \
--http-header "Authorization: Bearer ${TOKEN_ENV_CIBLE}" \
--http-header "User-Agent: Replayed by Gor" \
--output-http "https://bff.env-cible.example.com" \
--output-file "/captures/replayed-2026-09-11.gor"bashLe second --output-file garde les réponses rejouées (type 3) : c'est la matière première de la comparaison.
5. Comparer l'état final de la cible avec l'état prod de fin de journée J, en joignant sur la clé métier (numéro de commande + SKU) et en ignorant les ids techniques des préparations et les instants de création, qui diffèrent forcément. Ce qu'on attend : mêmes commandes prêtes, mêmes articles signalés manquants, mêmes quantités.
🔑 Conclusion clé
Le replay HTTP ne suffit jamais seul : ce qui entre par d'autres canaux (Kafka, batchs, imports) doit être rejoué aussi, et avant, pour que les gestes utilisateurs rejoués aient quelque chose sur quoi agir.
⚡ TL;DR — chaque concept en une ligne
GoReplay ✓ Sniffer HTTP open source, pas un proxy : écoute le port à côté de l'app, copie le trafic vers un env (shadowing) ou un fichier (replay différé), avec filtres regex, réécritures et middleware. ⚠ HTTP/1.x en clair uniquement, trafic entrant uniquement, rien sur l'état du serveur : auth, ids et idempotence restent à gérer.
Fichier .gor
✓ Texte brut, une ligne de métadonnées type id timestamp par entrée, séparateur 🐵🙈🙉. Le replay respecte les écarts de timestamps, glob multi-fichiers trié en streaming, gzip natif.
⚠ Le suffixe |0 rejoue sans attente : parfait pour la charge, inutilisable pour « simuler une journée ».
Filtres et réécritures
✓ --http-allow-header "X-Store-Id:^1234$" isole un tenant à la capture, --http-header "Authorization: ..." remplace le token au replay, |10% dose le volume.
⚠ Les flags sont statiques ; toute décision par requête passe par un middleware.
Middleware
✓ Exécutable stdin/stdout, messages hex [1|2|3] id timestamp, peut modifier ou supprimer une requête et apprendre des réponses de la cible (remap d'ids, tokens).
⚠ Doit toujours réémettre les réponses ; un middleware lent ralentit tout le pipeline.
Kubernetes
✓ Sidecar avec NET_RAW + --output-tcp vers un agrégateur --input-tcp = un seul fichier ordonné pour N pods. DaemonSet k8s://ns/deployment/nom:port sans toucher aux Deployments.
⚠ TLS terminé avant le point de capture, sinon le sidecar ne voit que du chiffré.
Shadowing Istio / Envoy mirror
✓ Une VirtualService avec mirror + mirrorPercentage, réponse jetée, header Host suffixé -shadow.
⚠ Temps réel seulement, pas d'enregistrement ni de replay différé.
Diffy ✓ Proxy vers primary / secondary / candidate ; le bruit primary ↔ secondary calibre ce qui est une vraie régression. ⚠ Trois instances, lecture seule en pratique, projet peu actif.
mitmproxy
✓ -w enregistre, -C rejoue côté client, -S rejoue côté serveur, TLS et addons Python.
⚠ Proxy : le client doit passer par lui, inadapté au trafic prod entrant.
Keploy / Speedscale ✓ Capturent entrant et sortant, génèrent tests et mocks des dépendances (eBPF pour Keploy, sidecar Operator pour Speedscale qui sait aussi décaler les dates). ⚠ Keploy vise la CI, pas la journée de prod ; Speedscale est commercial.
Kafka reset-offsets
✓ --reset-offsets --to-datetime ... --execute rejoue un topic à un consumer depuis une date, le topic est déjà la cassette.
⚠ Consumer arrêté pendant le reset, --dry-run d'abord, et entre deux clusters il faut copier les messages.
🎓 À retenir
--input-file rejoue les écarts d'origine, le suffixe de vitesse les compresse. Plusieurs fichiers sont fusionnés par timestamp.--http-header remplace ce qui ne peut pas être identique (token, host), le middleware fait le reste.--output-tcp / --input-tcp, ou DaemonSet k8s://. Ne jamais oublier NET_RAW.reset-offsets --to-datetime, et avant le replay HTTP, pour que les gestes rejoués trouvent leurs données..gor, suffixes de vitesse, glob, gzip--http-allow-header, --http-allow-url, --http-allow-method--http-header, --http-set-param, --http-rewrite-url--input-raw, moteurs, capabilities, TLSk8s://mirror, mirrorPercentage, suffixe -shadowkafka-consumer-groups.sh --reset-offsets