
Chaos Monkey aber für Audio- und Video-Tests (webRTC und UDP)
Verteilte Chaos-Engineering-Plattform zum Lasttest von Videokonferenzsystemen. Simuliert 1500+ WebRTC-Teilnehmer mit H.264/Opus-Streams und injiziert Netzwerk-Chaos-Spitzen, um die Systemresilienz unter verschlechterten Bedingungen zu validieren.
Medienverarbeitungspipeline:
Steuerungsebene:
Teilnehmerpool:
participant_id % total_partitions = partition_idKubernetes-Autokonfiguration:
orchestrator-3 → PARTITION_ID=3base_port + (partition_id × 10000) + participant_indexUDP-Relay-Kette (nur Kubernetes):
Orchestrator-Pods (10×) → UDP :5000 → udp-relay-Pod (Python)
→ Längenpräfix-TCP :5001 → kubectl port-forward 15001:5001
→ tools/udp-relay (Go) → UDP :5002 → Ihr Empfänger
WebRTC-Infrastruktur:
Client-Integration:
Observability-Stack (optional):
/metrics-Endpunkt von allen Orchestrator-Podsprometheus.io/scrape: "true"Jeder virtuelle Teilnehmer erzeugt echte Medienströme:
Fünf Spitzentypen simulieren reale Netzwerkbedingungen:
Spitzen werden über die Testdauer mit konfigurierbaren Strategien verteilt:
Kubernetes-Bereitstellungen verwenden Teilnehmerpartitionierung für horizontale Skalierung:
participant_id % total_partitions == partition_idbase_port + (partition_id * 10000) + participant_indexAm besten geeignet für: Entwicklung, Debugging, Kleintests (1–100 Teilnehmer)
# Orchestrator starten
go run cmd/main.go
# In einem anderen Terminal: UDP-Empfänger starten
go run examples/go/udp_receiver.go 5002
# config/config.json bearbeiten und num_participants: 10 setzen
# Chaos-Test ausführen
go run tools/chaos-test/main.go -config config/config.json
Was passiert:
:8080127.0.0.1:5002Konfiguration (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
}
}
Am besten geeignet für: Isolierte Tests, CI/CD, mittlere Tests (100–500 Teilnehmer)
Voraussetzungen:
docker-compose installiert# Orchestrator-Container bauen und starten
./scripts/start_everything.sh build
# In einem anderen Terminal: UDP-Empfänger starten
go run examples/go/udp_receiver.go 5002
# config/config.json bearbeiten und num_participants: 100 setzen
# Chaos-Test ausführen (zielt auf Container)
go run tools/chaos-test/main.go -config config/config.json
Ressourcenlimits (docker-compose.yaml bearbeiten):
services:
orchestrator:
deploy:
resources:
limits:
cpus: "14.0"
memory: 6G # Für mehr Teilnehmer erhöhen
Skalierungsleitfaden:
| Docker-Speicher | Max. Teilnehmer |
|---|
Am besten geeignet für: Großtests (500–1500 Teilnehmer), horizontale Skalierung, Produktionsvalidierung
Voraussetzungen:
# Nix bietet: Go, Docker, kubectl, kind, ffmpeg
nix develop
# Oder direnv für automatische Aktivierung verwenden
echo "use flake" > .envrc
direnv allow
# Automatische Bereitstellung mit optimalen Einstellungen (erkennt Systemressourcen)
./scripts/start_everything.sh run -config config/config.json
# Oder benutzerdefinierte Mediendateien angeben
./scripts/start_everything.sh run --media=path/to/video.mp4 -config config/config.json
Was passiert:
kubectl port-forward für das UDP-Relay einOption A: UDP-Empfänger (Empfohlen für Kubernetes)
# Empfängt aggregierten Stream von allen 1500 Teilnehmern
go run ./examples/go/udp_receiver.go 5002
Option B: WebRTC-Empfänger (Mehrere Teilnehmer)
# Verbindung mit bis zu 150 Teilnehmern über WebRTC
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
Architektur-Fluss:
1500 Teilnehmer über 10 Pods
→ Jeder Pod: 150 Teilnehmer
→ Partitionierung nach participant_id % 10
→ Alle senden UDP an udp-relay:5000
→ UDP-Relay aggregiert → TCP :5001
→ kubectl port-forward 15001:5001
→ Lokales Relay wandelt TCP → UDP :5002
→ Ihr Empfänger erhält alle 1500 Streams
Hinweis: Das Skript start_everything.sh richtet automatisch ein:
# Image bauen und laden
docker build -t chaos-monkey-orchestrator:latest .
kind load docker-image chaos-monkey-orchestrator:latest
# Bereitstellen
kubectl apply -f k8s/orchestrator/orchestrator.yaml
kubectl apply -f k8s/udp-relay/udp-relay.yaml
# Auf Pods warten
kubectl wait --for=condition=ready pod -l app=orchestrator --timeout=300s
# Port-Forward für UDP-Relay
kubectl port-forward udp-relay 15001:5001 &
# Lokales TCP→UDP-Relay starten
go run tools/udp-relay/main.go &
# In einem anderen Terminal: Empfänger starten
go run ./examples/go/udp_receiver.go 5002
# In einem anderen Terminal: Chaos-Test ausführen
go run tools/chaos-test/main.go -config config/config.json
# Kubernetes-Ressourcen löschen
./scripts/cleanup.sh
# Oder gesamten Cluster löschen
kind delete cluster --name av-chaos-monkey
# Für Linux x86_64 bauen (am häufigsten)
nix build .#packages.x86_64-linux.av-chaos-monkey
# Für ARM64 bauen (Raspberry Pi, AWS Graviton)
nix build .#packages.aarch64-linux.av-chaos-monkey
# Für macOS Intel bauen
nix build .#packages.x86_64-darwin.av-chaos-monkey
# Für macOS Apple Silicon bauen
nix build .#packages.aarch64-darwin.av-chaos-monkey
# Binärdatei-Standort
./result/bin/main
# Test erstellen
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
}
}
# Test starten
POST /api/v1/test/{test_id}/start
# Metriken abrufen
GET /api/v1/test/{test_id}/metrics
# Test stoppen
POST /api/v1/test/{test_id}/stop
# SDP-Angebot abrufen
GET /api/v1/test/{test_id}/sdp/{participant_id}
# SDP-Antwort setzen
POST /api/v1/test/{test_id}/sdp/{participant_id}
{"sdp_answer": "v=0..."}
# Spike injizieren
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"}
}
{
"spike_distribution": {
"strategy": "even",
"min_spacing_seconds": 5,
"jitter_percent": 15,
"respect_min_offset": true
}
}
# Mitgelieferter Empfänger mit RTP-Parsing
go run examples/go/udp_receiver.go 5002
Ausgabe:
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
═══════════════════════════════════════════════════════════
PACKET STATISTICS
═══════════════════════════════════════════════════════════
Duration: 60s
Total Packets: 180000 (3000 pkt/s)
Total Bytes: 450 MB (60 Mbps)
Media Type Breakdown:
Video (H.264): 120000 packets (66.7%)
Audio (Opus): 60000 packets (33.3%)
Unique Streams (SSRCs): 1500
Unique Participants: 1500
# Einzelner Teilnehmer
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id>
# Mehrere Teilnehmer (bis zu 150)
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
# Beispiel mit tatsächlicher Test-ID
go run ./examples/go/webrtc_receiver.go http://localhost:8080 chaos_test_1770831684 150
Hinweis: WebRTC erfordert 1:1-Verbindungen. Für Kubernetes verwenden Sie den UDP-Empfänger, der alle Teilnehmer automatisch aggregiert.
RTP-Paketformat:
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Payload-Typen:
96: H.264-Video (RFC 6184)111: Opus-Audio (RFC 7587)Extraktion der Teilnehmer-ID:
// Extension bit set?
if (packet[0] & 0x10) != 0 {
offset := 12 + int(packet[0]&0x0F)*4 // Skip CSRC
extID := binary.BigEndian.Uint16(packet[offset:])
if extID == 1 {
participantID := binary.LittleEndian.Uint32(packet[offset+4:])
}
}
Pro Teilnehmer (1280x720@30fps + Opus):
# Verfügbar unter /metrics-Endpunkt
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
# Docker-Modus: Monitoring-Stack starten
docker-compose --profile monitoring up
# Kubernetes-Modus: Monitoring bereitstellen
kubectl apply -f k8s/monitoring/prometheus-rbac.yaml
kubectl apply -f k8s/monitoring/prometheus.yaml
kubectl apply -f k8s/monitoring/grafana.yaml
# Auf Grafana zugreifen
# Docker: http://localhost:3000
# Kubernetes: http://localhost:30030 (NodePort)
# Standard-Anmeldedaten: admin/admin
# Auf Prometheus zugreifen
# Docker: http://localhost:9091
# Kubernetes: http://localhost:30090 (NodePort)
Automatische Erkennung in Kubernetes:
prometheus.io/scrape: "true"/metrics von allen Pods# Test-Metriken abrufen
curl http://localhost:8080/api/v1/test/{test_id}/metrics | jq
# Ausgabe
{
"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
}
}
# UDP-Zielkonfiguration prüfen
kubectl logs orchestrator-0 | grep "UDP transmission enabled"
# Prüfen, ob UDP-Relay läuft
kubectl get pod udp-relay
# Port-Forward prüfen
ps aux | grep "kubectl port-forward"
# UDP-Konnektivität testen
nc -u -z localhost 5002
# TURN-Server prüfen
kubectl get svc coturn-lb
# ICE-Kandidaten prüfen
kubectl logs orchestrator-0 | grep "ICE"
# TURN-Konnektivität testen
turnutils_uclient -v -u webrtc -w webrtc123 <turn-server>:3478
# Teilnehmeranzahl pro Pod prüfen
kubectl exec orchestrator-0 -- curl -s http://localhost:8080/api/v1/test/{test_id}/metrics | jq '.participants | length'
# Teilnehmeranzahl reduzieren oder Pod-Anzahl erhöhen
go run tools/k8s-start/main.go -replicas 10 -participants 1000
# Docker-Speicher erhöhen (Docker Desktop)
# Einstellungen → Ressourcen → Speicher → 16GB
Ein einzelner UDP-Socket kann 3000+ gleichzeitige Streams ohne Kernel-Pufferüberlauf nicht verarbeiten. Lösungen:
setsockopt(SO_RCVBUF, 8MB)BSD 3-Clause License
Beiträge willkommen! Schlüsselbereiche:
| CPU-Kerne |
|---|
| 8 GB | ~100 | 4 |
| 16 GB | ~250 | 8 |
| 24 GB | ~400 | 12 |
| 32 GB | ~500 | 14 |
| Typ | Parameter | Wirkung |
|---|
rtp_packet_loss | loss_percentage (0–100) | Verwirft Pakete auf RTP-Ebene |
network_jitter | base_latency_ms, jitter_std_dev_ms | Fügt Verzögerungsvariation hinzu |
bitrate_reduce | new_bitrate_kbps | Drosselt Videokodierung |
frame_drop | drop_percentage (0–100) | Überspringt Videoframes |
bandwidth_limit | bandwidth_kbps | Deckelt Gesamtdurchsatz |
| Teilnehmer | Speicher | CPU | Bandbreite |
|---|
| 100 | 2GB | 2 Kerne | 250 Mbps |
| 500 | 6GB | 8 Kerne | 1,2 Gbps |
| 1000 | 12GB | 16 Kerne | 2,5 Gbps |
| 1500 | 18GB | 24 Kerne | 3,7 Gbps |