
Chaos Monkey mais pour les tests audio vidéo (webRTC et UDP)
Plateforme d'ingénierie du chaos distribuée pour les tests de charge des systèmes de visioconférence. Simule plus de 1500 participants WebRTC avec des flux H.264/Opus et injecte des pics de chaos réseau pour valider la résilience du système dans des conditions dégradées.
Pipeline de traitement média :
Plan de contrôle :
Pool de participants :
participant_id % total_partitions = partition_idAuto-configuration Kubernetes :
orchestrator-3 → PARTITION_ID=3base_port + (partition_id × 10000) + participant_indexChaîne de relais UDP (Kubernetes uniquement) :
Pods Orchestrator (10×) → UDP :5000 → pod udp-relay (Python)
→ TCP avec préfixe de longueur :5001 → kubectl port-forward 15001:5001
→ tools/udp-relay (Go) → UDP :5002 → Votre récepteur
Infrastructure WebRTC :
Intégration client :
Pile d'observabilité (optionnelle) :
/metrics de tous les pods orchestrator toutes les 5 secondesprometheus.io/scrape: "true"Chaque participant virtuel génère des flux média réels :
Cinq types de pics simulent des conditions réseau réelles :
Les pics sont répartis sur la durée du test selon des stratégies configurables :
Les déploiements Kubernetes utilisent le partitionnement des participants pour la mise à l'échelle horizontale :
participant_id % total_partitions == partition_idbase_port + (partition_id * 10000) + participant_indexIdéal pour : Développement, débogage, tests à petite échelle (1-100 participants)
# Démarrer l'orchestrateur
go run cmd/main.go
# Dans un autre terminal : Démarrer le récepteur UDP
go run examples/go/udp_receiver.go 5002
# Modifier config/config.json pour définir num_participants: 10
# Lancer le test de chaos
go run tools/chaos-test/main.go -config config/config.json
Ce qui se passe :
:8080127.0.0.1:5002Configuration (config/config.json) :
{
"base_url": "http://localhost:8080",
"media_path": "public/rick-roll.mp4",
"num_participants": 10,
"duration_seconds": 300,
"spikes": {
"count": 20,
"interval_seconds": 5,
"types": { "rtp_packet_loss": {...}, "network_jitter": {...} }
},
"spike_distribution": {
"strategy": "random",
"min_spacing_seconds": 5,
"jitter_percent": 15
}
}
Idéal pour : Tests isolés, CI/CD, tests à moyenne échelle (100-500 participants)
Prérequis :
docker-compose installé# Construire et démarrer le conteneur orchestrateur
./scripts/start_everything.sh build
# Dans un autre terminal : Démarrer le récepteur UDP
go run examples/go/udp_receiver.go 5002
# Modifier config/config.json pour définir num_participants: 100
# Lancer le test de chaos (cible le conteneur)
go run tools/chaos-test/main.go -config config/config.json
Limites de ressources (modifier docker-compose.yaml) :
services:
orchestrator:
deploy:
resources:
limits:
cpus: "14.0"
memory: 6G # Augmenter pour plus de participants
Guide de mise à l'échelle :
| Mémoire Docker | Participants max | Cœurs CPU |
|---|---|---|
| 8 Go | ~100 | 4 |
| 16 Go | ~250 | 8 |
| 24 Go | ~400 | 12 |
| 32 Go | ~500 | 14 |
Idéal pour : Tests à grande échelle (500-1500 participants), mise à l'échelle horizontale, validation de production
Prérequis :
# Nix fournit : Go, Docker, kubectl, kind, ffmpeg
nix develop
# Ou utiliser direnv pour l'activation automatique
echo "use flake" > .envrc
direnv allow
# Déploiement automatique avec des paramètres optimaux (détecte les ressources système)
./scripts/start_everything.sh run -config config/config.json
# Ou spécifier des fichiers média personnalisés
./scripts/start_everything.sh run --media=path/to/video.mp4 -config config/config.json
Ce qui se passe :
kubectl port-forward pour le relais UDPOption A : Récepteur UDP (recommandé pour Kubernetes)
# Reçoit le flux agrégé des 1500 participants
go run ./examples/go/udp_receiver.go 5002
Option B : Récepteur WebRTC (plusieurs participants)
# Se connecte à jusqu'à 150 participants via WebRTC
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
Flux de l'architecture :
1500 Participants répartis sur 10 pods
→ Chaque pod : 150 participants
→ Partitionnement par participant_id % 10
→ Tous envoient UDP vers udp-relay:5000
→ Relais UDP agrège → TCP :5001
→ kubectl port-forward 15001:5001
→ Relais local convertit TCP → UDP :5002
→ Votre récepteur reçoit les 1500 flux
Remarque : Le script start_everything.sh configure automatiquement :
# Construire et charger l'image
docker build -t chaos-monkey-orchestrator:latest .
kind load docker-image chaos-monkey-orchestrator:latest
# Déployer
kubectl apply -f k8s/orchestrator/orchestrator.yaml
kubectl apply -f k8s/udp-relay/udp-relay.yaml
# Attendre les pods
kubectl wait --for=condition=ready pod -l app=orchestrator --timeout=300s
# Port-forward pour le relais UDP
kubectl port-forward udp-relay 15001:5001 &
# Démarrer le relais local TCP→UDP
go run tools/udp-relay/main.go &
# Dans un autre terminal : Démarrer le récepteur
go run ./examples/go/udp_receiver.go 5002
# Dans un autre terminal : Lancer le test de chaos
go run tools/chaos-test/main.go -config config/config.json
# Supprimer les ressources Kubernetes
./scripts/cleanup.sh
# Ou supprimer tout le cluster
kind delete cluster --name av-chaos-monkey
# Compiler pour Linux x86_64 (le plus courant)
nix build .#packages.x86_64-linux.av-chaos-monkey
# Compiler pour ARM64 (Raspberry Pi, AWS Graviton)
nix build .#packages.aarch64-linux.av-chaos-monkey
# Compiler pour macOS Intel
nix build .#packages.x86_64-darwin.av-chaos-monkey
# Compiler pour macOS Apple Silicon
nix build .#packages.aarch64-darwin.av-chaos-monkey
# Emplacement du binaire
./result/bin/main
# Créer un test
POST /api/v1/test/create
{
"test_id": "optional_id",
"num_participants": 100,
"video": {...},
"audio": {...},
"duration_seconds": 600,
"spikes": [...],
"spike_distribution": {
"strategy": "even",
"min_spacing_seconds": 5,
"jitter_percent": 15
}
}
# Démarrer le test
POST /api/v1/test/{test_id}/start
# Obtenir les métriques
GET /api/v1/test/{test_id}/metrics
# Arrêter le test
POST /api/v1/test/{test_id}/stop
# Obtenir l'offre SDP
GET /api/v1/test/{test_id}/sdp/{participant_id}
# Définir la réponse SDP
POST /api/v1/test/{test_id}/sdp/{participant_id}
{"sdp_answer": "v=0..."}
# Injecter un pic
POST /api/v1/test/{test_id}/spike
{
"spike_id": "unique_id",
"type": "rtp_packet_loss",
"duration_seconds": 30,
"participant_ids": [1001, 1002],
"params": {"loss_percentage": "15"}
}
| Type | Paramètres | Effet |
|---|---|---|
rtp_packet_loss | loss_percentage (0-100) | Supprime les paquets au niveau RTP |
network_jitter | base_latency_ms, jitter_std_dev_ms | Ajoute une variation de latence |
bitrate_reduce | new_bitrate_kbps | Limite le codage vidéo |
frame_drop | drop_percentage (0-100) | Saute des trames vidéo |
bandwidth_limit | bandwidth_kbps | Plafonne le débit total |
{
"spike_distribution": {
"strategy": "even",
"min_spacing_seconds": 5,
"jitter_percent": 15,
"respect_min_offset": true
}
}
# Récepteur fourni avec analyse RTP
go run examples/go/udp_receiver.go 5002
Sortie :
Listening for RTP packets on UDP port 0.0.0.0:5002
Packet #100 from 127.0.0.1:xxxxx:
Participant ID: 1001
Payload Type: 96 (H.264 video)
Sequence: 1234
Timestamp: 90000
SSRC: 1001000
Payload Size: 1200 bytes
═══════════════════════════════════════════════════════════
STATISTIQUES DES PAQUETS
═══════════════════════════════════════════════════════════
Durée : 60s
Paquets totaux : 180000 (3000 pkt/s)
Octets totaux : 450 Mo (60 Mb/s)
Répartition par type de média :
Vidéo (H.264) : 120000 paquets (66,7 %)
Audio (Opus) : 60000 paquets (33,3 %)
Flux uniques (SSRC) : 1500
Participants uniques : 1500
# Participant unique
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id>
# Participants multiples (jusqu'à 150)
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
# Exemple avec un ID de test réel
go run ./examples/go/webrtc_receiver.go http://localhost:8080 chaos_test_1770831684 150
Remarque : WebRTC nécessite des connexions 1:1. Pour Kubernetes, utilisez le récepteur UDP qui agrège automatiquement tous les participants.
Format du paquet RTP :
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Extension ID=1 | Length=4 | Participant ID (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| H.264/Opus Payload |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Types de charge utile :
96 : Vidéo H.264 (RFC 6184)111 : Audio Opus (RFC 7587)Extraction de l'identifiant participant :
// Bit d'extension défini ?
if (packet[0] & 0x10) != 0 {
offset := 12 + int(packet[0]&0x0F)*4 // Ignorer CSRC
extID := binary.BigEndian.Uint16(packet[offset:])
if extID == 1 {
participantID := binary.LittleEndian.Uint32(packet[offset+4:])
}
}
| Participants | Mémoire | CPU | Bande passante |
|---|---|---|---|
| 100 | 2 Go | 2 cœurs | 250 Mb/s |
| 500 | 6 Go | 8 cœurs | 1,2 Gb/s |
| 1000 | 12 Go | 16 cœurs | 2,5 Gb/s |
| 1500 | 18 Go | 24 cœurs | 3,7 Gb/s |
Par participant (1280x720@30 ips + Opus) :
# Exposées sur le point de terminaison /metrics
av_chaos_monkey_participants_total
av_chaos_monkey_packets_sent_total
av_chaos_monkey_bytes_sent_total
av_chaos_monkey_spikes_active
av_chaos_monkey_packet_loss_percent
av_chaos_monkey_jitter_ms
# Mode Docker : Démarrer la pile de surveillance
docker-compose --profile monitoring up
# Mode Kubernetes : Déployer la surveillance
kubectl apply -f k8s/monitoring/prometheus-rbac.yaml
kubectl apply -f k8s/monitoring/prometheus.yaml
kubectl apply -f k8s/monitoring/grafana.yaml
# Accéder à Grafana
# Docker : http://localhost:3000
# Kubernetes : http://localhost:30030 (NodePort)
# Identifiants par défaut : admin/admin
# Accéder à Prometheus
# Docker : http://localhost:9091
# Kubernetes : http://localhost:30090 (NodePort)
Auto-découverte Kubernetes :
prometheus.io/scrape: "true"/metrics de tous les pods toutes les 5 secondes# Obtenir les métriques du test
curl http://localhost:8080/api/v1/test/{test_id}/metrics | jq
# Sortie
{
"aggregate": {
"total_frames_sent": 45000,
"total_packets_sent": 180000,
"total_bitrate_kbps": 250000,
"avg_jitter_ms": 12.5,
"avg_packet_loss": 2.3,
"avg_mos_score": 4.1
}
}
# Vérifier la configuration de la cible UDP
kubectl logs orchestrator-0 | grep "UDP transmission enabled"
# Vérifier que le relais UDP est en cours d'exécution
kubectl get pod udp-relay
# Vérifier le port-forward
ps aux | grep "kubectl port-forward"
# Tester la connectivité UDP
nc -u -z localhost 5002
# Vérifier le serveur TURN
kubectl get svc coturn-lb
# Vérifier les candidats ICE
kubectl logs orchestrator-0 | grep "ICE"
# Tester la connectivité TURN
turnutils_uclient -v -u webrtc -w webrtc123 <turn-server>:3478
# Vérifier le nombre de participants par pod
kubectl exec orchestrator-0 -- curl -s http://localhost:8080/api/v1/test/{test_id}/metrics | jq '.participants | length'
# Réduire les participants ou augmenter le nombre de pods
go run tools/k8s-start/main.go -replicas 10 -participants 1000
# Augmenter la mémoire Docker (Docker Desktop)
# Paramètres → Ressources → Mémoire → 16 Go
Un seul socket UDP ne peut pas gérer plus de 3 000 flux simultanés sans débordement du tampon du noyau. Solutions :
setsockopt(SO_RCVBUF, 8 Mo)Licence BSD 3-Clause
Les contributions sont les bienvenues ! Domaines clés :