Schulprojekt: Demonstration eines Load Balancers mit verschiedenen Verteilungsstrategien. Alle Komponenten laufen als Docker-Container und starten mit einem einzigen Befehl.
- Architektur
- Container-Abhaengigkeiten
- Anfrage-Ablauf
- Schnellstart
- Ports & Zugriff
- Strategie wechseln
- Strategien im Vergleich
- Umgebungsvariablen
- Projektstruktur
Zeigt alle Komponenten und wie sie miteinander verbunden sind.
graph LR
subgraph HOST["Host-Rechner (Windows)"]
P80(["localhost:80"])
P8081(["localhost:8081"])
P8082(["localhost:8082"])
P8083(["localhost:8083"])
end
subgraph NET["Docker-Netzwerk: lb-network"]
SG["Stress-Generator\n10 Worker-Threads\nDelay 5-9s (zufaellig)"]
LB["Caddy Load Balancer\nStrategie: round_robin\n(wechselbar per Caddyfile)"]
B1["Backend 1\nFlask :8080"]
B2["Backend 2\nFlask :8080"]
B3["Backend 3\nFlask :8080"]
end
P80 -->|eingehend| LB
SG -->|HTTP GET| LB
LB -->|verteilt| B1
LB -->|verteilt| B2
LB -->|verteilt| B3
B1 --- P8081
B2 --- P8082
B3 --- P8083
Zeigt die depends_on-Kette aus docker-compose.yml — in dieser Reihenfolge starten die Container.
graph TD
B1["backend1"]
B2["backend2"]
B3["backend3"]
C["caddy\n(Load Balancer)"]
SG["stress-generator"]
C -->|"depends_on"| B1
C -->|"depends_on"| B2
C -->|"depends_on"| B3
SG -->|"depends_on"| C
style B1 fill:#4A90D9,color:#fff
style B2 fill:#4A90D9,color:#fff
style B3 fill:#4A90D9,color:#fff
style C fill:#2ECC71,color:#fff
style SG fill:#E67E22,color:#fff
Caddy startet erst, wenn alle drei Backends laufen. Der Stress-Generator startet erst, wenn Caddy bereit ist.
Zeigt den Weg einer einzelnen HTTP-Anfrage durch das System.
sequenceDiagram
actor Client
participant SG as Stress-Generator
participant LB as Caddy (Load Balancer)
participant B1 as Backend 1
participant B2 as Backend 2
participant B3 as Backend 3
Note over SG: Wartet 5-9s (zufaellig)
SG ->> LB : GET /
LB ->> B1 : GET / (1. Anfrage, round_robin)
B1 -->> LB : 200 {"server":"backend1", "requests_handled":1}
LB -->> SG : 200 OK
Note over SG: Wartet 5-9s (zufaellig)
SG ->> LB : GET /
LB ->> B2 : GET / (2. Anfrage, round_robin)
B2 -->> LB : 200 {"server":"backend2", "requests_handled":1}
LB -->> SG : 200 OK
Note over SG: Wartet 5-9s (zufaellig)
SG ->> LB : GET /
LB ->> B3 : GET / (3. Anfrage, round_robin)
B3 -->> LB : 200 {"server":"backend3", "requests_handled":1}
LB -->> SG : 200 OK
Note over LB: Health-Check alle 10s
LB ->> B1 : GET /health
B1 -->> LB : 200 {"status":"ok"}
LB ->> B2 : GET /health
B2 -->> LB : 200 {"status":"ok"}
LB ->> B3 : GET /health
B3 -->> LB : 200 {"status":"ok"}
Note over Client: manueller Zugriff
Client ->> LB : GET /
LB ->> B1 : GET /
B1 -->> LB : 200 OK
LB -->> Client : 200 OK
# Alle Container bauen und starten
docker compose up --build
# Im Hintergrund starten
docker compose up --build -d
# Logs des Stress-Generators live verfolgen
docker compose logs -f stress-generator
# Alle Container stoppen und entfernen
docker compose down| Adresse | Container | Beschreibung |
|---|---|---|
localhost:80 |
loadbalancer |
Load Balancer — Haupteingang |
localhost:8081 |
backend1 |
Direktzugriff auf Backend 1 |
localhost:8082 |
backend2 |
Direktzugriff auf Backend 2 |
localhost:8083 |
backend3 |
Direktzugriff auf Backend 3 |
Beispiel-Antwort eines Backend-Servers (GET localhost:8081):
{
"server": "backend1",
"requests_handled": 42,
"uptime_seconds": 183.5,
"timestamp": "2026-06-10T15:00:00Z"
}In caddy/Caddyfile genau eine lb_policy-Zeile aktiv lassen, alle anderen auskommentieren:
# Strategie 1: Round Robin — reihum, gleichmaessige Verteilung
lb_policy round_robin
# Strategie 2: Least Connections — Server mit den wenigsten offenen Verbindungen
# lb_policy least_conn
# Strategie 3: Random — zufaellige Auswahl
# lb_policy random
# Strategie 4: IP Hash — gleiche Client-IP landet immer beim gleichen Server
# lb_policy ip_hashDanach nur Caddy neu starten — kein Rebuild noetig:
docker compose restart caddy| Strategie | Funktionsprinzip | Einsatzgebiet |
|---|---|---|
round_robin |
Anfragen werden der Reihe nach verteilt (1, 2, 3, 1, 2 …) | Standard, gleichmaessige Last |
least_conn |
Naechste Anfrage geht zum Server mit den wenigsten offenen Verbindungen | Unterschiedlich lange Requests |
random |
Zufaellige Auswahl bei jeder Anfrage | Einfachste Variante, gut fuer Tests |
ip_hash |
Client-IP wird gehasht → gleiche IP, gleicher Server | Session-basierte Anwendungen |
Einstellbar in docker-compose.yml unter stress-generator > environment:
| Variable | Standard | Beschreibung |
|---|---|---|
TARGET_URL |
http://caddy:80 |
Zieladresse des Load Balancers |
WORKERS |
10 |
Anzahl paralleler Worker-Threads |
DELAY_MIN |
5 |
Minimale Wartezeit zwischen Anfragen (s) |
DELAY_MAX |
9 |
Maximale Wartezeit zwischen Anfragen (s) |
| Variable | Beispiel | Beschreibung |
|---|---|---|
SERVER_ID |
backend1 |
Anzeigename im Log |
LoadBalancerCaddy/
├── docker-compose.yml # Alle Services, Ports, Netzwerk
│
├── caddy/
│ └── Caddyfile # Strategie hier wechseln
│
├── backend/
│ ├── Dockerfile
│ ├── server.py # Flask-Server, antwortet mit JSON
│ └── requirements.txt
│
└── stress-generator/
├── Dockerfile
├── stress.py # 10 Worker-Threads, Statistik alle 15s
└── requirements.txt