Ubuntu 24.04 vs. Debian 13: Docker Performance Benchmark
Ist Debian wirklich schlanker und schneller als Ubuntu? Ich habe beide Betriebssysteme als Docker-Host mit einem Benchmark-Skript gegeneinander antreten lassen.
Inhaltsverzeichnis
Wer regelmĂ€Ăig mit Linux-Servern arbeitet, kennt die grundlegende Frage bei jeder Neuinstallation: Ubuntu oder Debian?
Die Wahl fĂ€llt oft schwer. Auf der einen Seite steht Ubuntu, der moderne und weitverbreitete Allrounder mit LTS-Support. Auf der anderen Seite Debian, das fĂŒr seine unerschĂŒtterliche StabilitĂ€t und seinen Minimalismus bekannt ist.
Speziell, wenn man fast ausschlieĂlich auf Docker setzt, könnte man meinen, die Entscheidung sei zweitrangig. Sobald der Docker Daemon lĂ€uft, interagiert man ohnehin nur noch mit Containern und Images. Die Abstraktionsebene von Docker macht das darunterliegende Betriebssystem fĂŒr den Entwickler gefĂŒhlt unsichtbar.
Aber ist das wirklich so? Gibt es messbare Unterschiede in der Performance, im Ressourcenverbrauch oder im Netzwerk-Stack, die eines der Systeme zu einem objektiv besseren Docker-Host machen?
Ich habe versucht das zu testen mit einem automatisiertes Bash-Skript, um auf identischer Hardware (Hetzner Cloud) Ubuntu 24.04 LTS und Debian 13 (Trixie/Testing) gegeneinander antreten zu lassen.
Das Test-Szenario
Um faire Bedingungen zu schaffen, habe ich das Benchmark-Skript auf vier Servern laufen lassen:
- Shared vCPU (klein): Hetzner CX22 (Intel) â Ubuntu 24.04
- Shared vCPU (klein): Hetzner CX22 (Intel) â Debian 13
- Dedicated vCPU (groĂ): Hetzner CCX13 (AMD) â Ubuntu 24.04
- Dedicated vCPU (groĂ): Hetzner CCX13 (AMD) â Debian 13
Alle Server wurden frisch installiert, mit den neuesten Updates versorgt und haben Docker in der Version 28.4.0 erhalten.
Das Benchmark-Skript
Um die Tests reproduzierbar zu machen, habe ich ein Skript genutzt, das 8 verschiedene Szenarien durchlÀuft. Das Skript nutzt Tools wie sysbench, iperf3 und fio direkt in Docker-Containern, um die Performance der Container-Umgebung zu messen.
Hier ist das Skript, falls du deine eigenen Server testen möchtest:
#!/bin/bash
# Docker Host Benchmark Script
# Vergleich: Ubuntu 24.04 vs Debian 13
set -e
# Farben fĂŒr Output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Absolute Pfade definieren, um 'cd'-Probleme zu vermeiden
SCRIPT_DIR=$(pwd)
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RESULT_DIR_NAME="benchmark_results_${TIMESTAMP}"
RESULT_DIR="${SCRIPT_DIR}/${RESULT_DIR_NAME}"
RESULT_FILE="${RESULT_DIR}/results.json"
LOG_FILE="${RESULT_DIR}/benchmark.log"
# Funktionen
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
}
info() {
echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_FILE"
}
# Initialisierung
init_benchmark() {
mkdir -p "$RESULT_DIR"
log "Initialisiere Benchmark Suite..."
if command -v lsb_release &> /dev/null; then
OS_NAME=$(lsb_release -ds)
else
OS_NAME=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
fi
KERNEL_VERSION=$(uname -r)
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
CPU_MODEL=$(lscpu | grep "Model name" | cut -d':' -f2 | xargs)
CPU_CORES=$(nproc)
TOTAL_RAM=$(free -h | grep Mem | awk '{print $2}')
cat > "$RESULT_FILE" <<EOF
{
"system_info": {
"os": "$OS_NAME",
"kernel": "$KERNEL_VERSION",
"docker_version": "$DOCKER_VERSION",
"cpu_model": "$CPU_MODEL",
"cpu_cores": $CPU_CORES,
"total_ram": "$TOTAL_RAM",
"timestamp": "$TIMESTAMP"
},
"tests": {}
}
EOF
log "System: $OS_NAME"
log "Kernel: $KERNEL_VERSION"
log "Docker: $DOCKER_VERSION"
log "CPU: $CPU_MODEL ($CPU_CORES Cores)"
log "RAM: $TOTAL_RAM"
}
# Images vorladen
prewarm_images() {
log "=== Vorbereitung: Docker Images herunterladen ==="
info "Lade benötigte Images, um Download-Zeit aus den Benchmarks auszuschlieĂen..."
local images=(
"nginx:alpine"
"severalnines/sysbench"
"xridge/fio"
"networkstatic/iperf3"
"node:20-alpine"
"postgres:16-alpine"
"redis:7-alpine"
)
for img in "${images[@]}"; do
info "Pulle $img..."
docker pull "$img" > /dev/null
done
log "Alle Images vorgeladen."
}
# Test 0: Idle Resources (Baseline)
test_idle_resources() {
log "=== Test 0: Leerlauf-Ressourcenverbrauch (Baseline) ==="
sleep 5
local ram_used_mb=$(free -m | grep Mem | awk '{print $3}')
local disk_used_gb=$(df -h / | tail -1 | awk '{print $3}')
log "RAM used (Idle): ${ram_used_mb} MB"
log "Disk used (Idle): ${disk_used_gb}"
local temp_json=$(jq ".tests.idle_resources = {
\"ram_used_mb\": $ram_used_mb,
\"disk_used_gb\": \"$disk_used_gb\"
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 1: Container Start Performance
test_container_start() {
log "=== Test 1: Container Start Performance ==="
local iterations=100
local start_time=$(date +%s.%N)
for i in $(seq 1 $iterations); do
docker run --rm nginx:alpine echo "Container $i" > /dev/null 2>&1
done
local end_time=$(date +%s.%N)
local total_time=$(echo "$end_time - $start_time" | bc)
local avg_time=$(echo "scale=4; $total_time / $iterations" | bc)
log "Total Zeit fĂŒr $iterations Starts: ${total_time}s"
log "Durchschnitt pro Container: ${avg_time}s"
local temp_json=$(jq ".tests.container_start = {
\"total_time_seconds\": $total_time,
\"average_time_seconds\": $avg_time,
\"iterations\": $iterations
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 2: CPU Performance
test_cpu_performance() {
log "=== Test 2: CPU Performance ==="
info "Single-Thread Test..."
local single_output=$(docker run --rm severalnines/sysbench \
sysbench cpu --cpu-max-prime=20000 --threads=1 run 2>&1)
local single_events=$(echo "$single_output" | grep "events per second" | awk '{print $4}')
local single_time=$(echo "$single_output" | grep "total time:" | awk '{print $3}' | tr -d 's')
info "Multi-Thread Test (2 Threads)..."
local multi_output=$(docker run --rm severalnines/sysbench \
sysbench cpu --cpu-max-prime=20000 --threads=2 run 2>&1)
local multi_events=$(echo "$multi_output" | grep "events per second" | awk '{print $4}')
local multi_time=$(echo "$multi_output" | grep "total time:" | awk '{print $3}' | tr -d 's')
log "Single-Thread: ${single_events} events/s"
log "Multi-Thread: ${multi_events} events/s"
local temp_json=$(jq ".tests.cpu_performance = {
\"single_thread\": {
\"events_per_second\": $single_events,
\"total_time_seconds\": $single_time
},
\"multi_thread\": {
\"events_per_second\": $multi_events,
\"total_time_seconds\": $multi_time
}
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 3: I/O Performance
test_io_performance() {
log "=== Test 3: I/O Performance ==="
local fio_dir="$(pwd)/fio-test"
mkdir -p "$fio_dir"
info "Random Write Test (4k Blöcke, 1GB Datei)..."
# --- Verwende xridge/fio image ---
local fio_output=$(docker run --rm -v "$fio_dir":/data xridge/fio \
--name=random-write --ioengine=libaio --iodepth=16 \
--rw=randwrite --bs=4k --direct=1 --size=1G \
--numjobs=1 --runtime=30 --time_based \
--group_reporting --directory=/data \
--output-format=json 2>&1)
local iops=$(echo "$fio_output" | jq '.jobs[0].write.iops')
local bw_kib=$(echo "$fio_output" | jq '.jobs[0].write.bw')
local bw_mib=$(echo "scale=2; $bw_kib / 1024" | bc)
log "IOPS: ${iops}"
log "Bandwidth: ${bw_mib} MiB/s"
rm -rf "$fio_dir"
local temp_json=$(jq ".tests.io_performance = {
\"iops\": $iops,
\"bandwidth_mibs\": $bw_mib
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 4: Network Performance
test_network_performance() {
log "=== Test 4: Network Performance (Container-to-Container on same Host) ==="
info "Dieser Test misst die Performance der Docker-Bridge und des Kernels."
info "Starte iperf3 Server..."
docker run -d --name iperf-server --rm networkstatic/iperf3 -s > /dev/null 2>&1
sleep 3
local server_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' iperf-server)
info "Starte iperf3 Client..."
local iperf_output=$(docker run --rm networkstatic/iperf3 -c "$server_ip" -t 10 2>&1)
local bandwidth=$(echo "$iperf_output" | grep "sender" | tail -1 | awk '{print $(NF-2)}')
local unit=$(echo "$iperf_output" | grep "sender" | tail -1 | awk '{print $(NF-1)}')
docker stop iperf-server > /dev/null 2>&1
log "Bandwidth: ${bandwidth} ${unit}"
local temp_json=$(jq ".tests.network_performance = {
\"bandwidth\": \"$bandwidth $unit\"
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 5: Memory Performance
test_memory_performance() {
log "=== Test 5: Memory Performance ==="
local mem_output=$(docker run --rm severalnines/sysbench \
sysbench memory --memory-block-size=1M \
--memory-total-size=10G --threads=1 run 2>&1)
local operations=$(echo "$mem_output" | grep "Total operations" | awk '{print $3}')
local ops_per_sec=$(echo "$mem_output" | grep "Total operations" | sed -E 's/.*\(//;s/ per second\)//')
local throughput=$(echo "$mem_output" | grep "MiB/sec" | awk '{print $1}')
log "Throughput: ${throughput} MiB/sec"
log "Ops/sec: ${ops_per_sec}"
local temp_json=$(jq ".tests.memory_performance = {
\"total_operations\": $operations,
\"ops_per_second\": $ops_per_sec,
\"throughput_mibs\": $throughput
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 6: Build Performance
test_build_performance() {
log "=== Test 6: Build Performance ==="
local build_dir="/tmp/docker-build-test"
mkdir -p "$build_dir"
cd "$build_dir"
cat > Dockerfile <<'EOF'
FROM node:20-alpine
WORKDIR /app
RUN npm install -g npm@latest
RUN apk add --no-cache python3 make g++
RUN echo '{"name":"test","version":"1.0.0"}' > package.json
RUN npm install express
RUN echo 'console.log("test");' > index.js
EOF
info "Build Test (3 DurchlÀufe)..."
local total_time=0
for i in {1..3}; do
docker image rm test-build:$i 2>/dev/null || true
local start=$(date +%s.%N)
docker build --no-cache -t test-build:$i . > /dev/null 2>&1
local end=$(date +%s.%N)
local duration=$(echo "$end - $start" | bc)
total_time=$(echo "$total_time + $duration" | bc)
info "Durchlauf $i: ${duration}s"
done
local avg_time=$(echo "scale=4; $total_time / 3" | bc)
log "Durchschnittliche Build-Zeit: ${avg_time}s"
docker image rm test-build:1 test-build:2 test-build:3 2>/dev/null || true
cd - > /dev/null
rm -rf "$build_dir"
local temp_json=$(jq ".tests.build_performance = {
\"average_build_time_seconds\": $avg_time,
\"iterations\": 3
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 7: Container Density
test_container_density() {
log "=== Test 7: Container Density Test ==="
local container_count=50
info "Starte $container_count Container..."
for i in $(seq 1 $container_count); do
docker run -d --name nginx-$i --memory=50m nginx:alpine > /dev/null 2>&1
echo -ne "\rGestartet: $i/$container_count"
done
echo ""
sleep 10
local mem_usage=$(free | grep Mem | awk '{printf "%.2f", ($3/$2) * 100}')
local load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
log "Memory Usage (Host): ${mem_usage}%"
log "Load Average (1min): ${load_avg}"
info "Stoppe Container..."
docker rm -f $(seq -f "nginx-%g" 1 $container_count) > /dev/null 2>&1
local temp_json=$(jq ".tests.container_density = {
\"container_count\": $container_count,
\"memory_usage_percent\": $mem_usage,
\"load_average\": $load_avg
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Test 8: Docker Compose Performance
test_compose_performance() {
log "=== Test 8: Docker Compose Stack Performance ==="
local compose_dir="/tmp/compose-test"
mkdir -p "$compose_dir"
cd "$compose_dir"
cat > docker-compose.yml <<'EOF'
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: test
redis:
image: redis:7-alpine
app:
image: nginx:alpine
depends_on:
- db
- redis
EOF
info "Compose Test (10 Zyklen Up/Down)..."
local start_time=$(date +%s.%N)
for i in $(seq 1 10); do
docker compose up -d > /dev/null 2>&1
docker compose down > /dev/null 2>&1
echo -ne "\rZyklus: $i/10"
done
echo ""
local end_time=$(date +%s.%N)
local total_time=$(echo "$end_time - $start_time" | bc)
local avg_time=$(echo "scale=4; $total_time / 10" | bc)
log "Durchschnitt pro Zyklus: ${avg_time}s"
cd - > /dev/null
rm -rf "$compose_dir"
local temp_json=$(jq ".tests.compose_performance = {
\"average_cycle_time_seconds\": $avg_time,
\"iterations\": 10
}" "$RESULT_FILE")
echo "$temp_json" > "$RESULT_FILE"
}
# Cleanup
cleanup() {
log "RĂ€ume auf..."
docker system prune -af > /dev/null 2>&1 || true
}
# Zusammenfassung erstellen
create_summary() {
log "=== Erstelle Zusammenfassung ==="
local summary_file="${RESULT_DIR}/summary.txt"
cat > "$summary_file" <<EOF
Docker Host Benchmark Zusammenfassung
======================================
System: $(jq -r '.system_info.os' "$RESULT_FILE")
Kernel: $(jq -r '.system_info.kernel' "$RESULT_FILE")
Docker: $(jq -r '.system_info.docker_version' "$RESULT_FILE")
Timestamp: $TIMESTAMP
Test-Ergebnisse:
-----------------
0. Baseline (Idle):
- RAM used: $(jq -r '.tests.idle_resources.ram_used_mb' "$RESULT_FILE") MB
- Disk used: $(jq -r '.tests.idle_resources.disk_used_gb' "$RESULT_FILE")
1. Container Start Performance:
- Durchschnitt: $(jq -r '.tests.container_start.average_time_seconds' "$RESULT_FILE")s
2. CPU Performance:
- Single-Thread: $(jq -r '.tests.cpu_performance.single_thread.events_per_second' "$RESULT_FILE") events/s
- Multi-Thread: $(jq -r '.tests.cpu_performance.multi_thread.events_per_second' "$RESULT_FILE") events/s
3. I/O Performance (Random Write 4k):
- IOPS: $(jq -r '.tests.io_performance.iops' "$RESULT_FILE")
- Bandwidth: $(jq -r '.tests.io_performance.bandwidth_mibs' "$RESULT_FILE") MiB/s
4. Network Performance (Internal):
- Bandwidth: $(jq -r '.tests.network_performance.bandwidth' "$RESULT_FILE")
5. Memory Performance:
- Throughput: $(jq -r '.tests.memory_performance.throughput_mibs' "$RESULT_FILE") MiB/s
- Ops/sec: $(jq -r '.tests.memory_performance.ops_per_second' "$RESULT_FILE")
6. Build Performance (No Cache):
- Durchschnitt: $(jq -r '.tests.build_performance.average_build_time_seconds' "$RESULT_FILE")s
7. Container Density (50 Container):
- Memory Usage: $(jq -r '.tests.container_density.memory_usage_percent' "$RESULT_FILE")%
- Load Average: $(jq -r '.tests.container_density.load_average' "$RESULT_FILE")
8. Compose Performance:
- Durchschnitt: $(jq -r '.tests.compose_performance.average_cycle_time_seconds' "$RESULT_FILE")s
Detaillierte Ergebnisse (JSON): $RESULT_FILE
EOF
cat "$summary_file"
log "Zusammenfassung gespeichert in: $summary_file"
}
# Hauptprogramm
main() {
clear
echo -e "${BLUE}"
cat <<'EOF'
âââââââââââââââââââââââââââââââââââââââââ
â Docker Host Benchmark Suite â
â Ubuntu 24.04 vs Debian 13 â
âââââââââââââââââââââââââââââââââââââââââ
EOF
echo -e "${NC}"
if ! command -v docker &> /dev/null; then error "Docker ist nicht installiert!"; exit 1; fi
if ! command -v jq &> /dev/null; then warn "jq nicht gefunden. Installiere..."; sudo apt-get update >/dev/null && sudo apt-get install -y jq; fi
if ! command -v bc &> /dev/null; then warn "bc nicht gefunden. Installiere..."; sudo apt-get install -y bc; fi
init_benchmark
warn "Dieser Benchmark dauert ca. 15-25 Minuten."
warn "Bitte stelle sicher, dass KEINE anderen Container laufen!"
read -p "Fortfahren? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 0; fi
prewarm_images
test_idle_resources
test_container_start
test_cpu_performance
test_io_performance
test_network_performance
test_memory_performance
test_build_performance
test_container_density
test_compose_performance
cleanup
create_summary
log "${GREEN}Benchmark abgeschlossen!${NC}"
log "Ergebnisse gespeichert in: $RESULT_DIR"
}
main "$@"
(Hinweis: Du kannst das vollstĂ€ndige Skript einfach kopieren und als benchmark.sh speichern. Vergiss nicht, es mit chmod +x benchmark.sh ausfĂŒhrbar zu machen und jq sowie bc zu installieren.)
Was wird getestet?
Das Skript fĂŒhrt folgende Tests durch:
- Idle Resources: Wie viel RAM und Speicher braucht das leere OS?
- Container Start: Wie schnell können 100 Container gestartet und gestoppt werden?
- CPU Performance: Berechnung von Primzahlen (Single- & Multi-Thread) via Sysbench.
- I/O Performance: Stress-Test fĂŒr die Festplatte (4k Random Writes).
- Network: Interne Bandbreite zwischen zwei Containern (Bridge-Network).
- Memory: Lese- und Schreibgeschwindigkeit des RAMs.
- Build Performance: Zeitmessung fĂŒr einen
docker buildeiner Node.js App (ohne Cache). - Container Density: Starten von 50 Nginx-Containern parallel (RAM-Verbrauch).
- Docker Compose: Up/Down Zyklen eines Stacks (Postgres, Redis, App).
Die Ergebnisse
Die Ergebnisse waren nicht eindeutig und zeigten, dass die Wahl der Hardware einen massiven Einfluss darauf hat, welches Betriebssystem âbesserâ performt.
Szenario 1: Shared vCPU (Kleiner Server)
| Metrik | Ubuntu 24.04 | Debian 13 | Differenz |
|---|---|---|---|
| Container Start | 0.43s | 0.46s | Ubuntu -6.4% |
| CPU Multi-Thread | 669 ev/s | 713 ev/s | Debian +6.2% |
| Disk IOPS | 29.074 | 30.914 | Debian +5.9% |
| Build Zeit (NodeJS) | 26.0s | 24.3s | Debian -7.3% |
| RAM Usage (50 Container) | 44.96% | 41.87% | Debian effizienter |
Besonders bei der Arbeitsspeicher-Effizienz und der Build-Zeit konnte Debian punkten. Ubuntu war zwar beim Starten von leeren Containern minimal schneller, verlor aber bei fast allen Last-Tests.
Szenario 2: Dedicated vCPU (GroĂer Server)
Wechselt man auf dedizierte AMD EPYC Hardware, dreht sich das Bild. Hier dominiert Ubuntu und gewann 7 von 8 Tests. Was hier besonders auffÀllt, ist die I/O Performance. Ubuntu scheint mit den NVMe-Treibern oder dem Dateisystem auf dieser Architektur deutlich besser umzugehen und erreicht fast die doppelte Schreibgeschwindigkeit im Vergleich zu Debian.
| Metrik | Ubuntu 24.04 | Debian 13 | Differenz |
|---|---|---|---|
| Container Start | 0.22s | 0.47s | Ubuntu -51% đ |
| CPU Multi-Thread | 1798 ev/s | 1804 ev/s | Unentschieden |
| Disk IOPS | 57.587 | 29.541 | Ubuntu +94% đ |
| Build Zeit (NodeJS) | 13.5s | 17.1s | Ubuntu -21% |
| Docker Compose Zyklus | 11.08s | 11.58s | Ubuntu -4.3% |
Fazit: Welches OS fĂŒr Docker?
Das Ergebnis ist nicht eindeutig, wie man vielleicht hofft. Am Ende ist Ubuntu jedoch definitiv nicht die schlechtere Wahl anhand dieser Ergebnisse.
Hinweis: Dies ist eine Momentaufnahme. In vorherigen Tests hatte ich DurchlÀufe, bei denen Ubuntu auch auf den kleinen Servern öfter gewann.
Die oft zitierte âBloatwareâ von Ubuntu fĂ€llt im reinen Docker-Betrieb auf moderner Hardware kaum ins Gewicht.
FAQs
Wie fĂŒhre ich das Benchmark-Skript aus?
Lade das Skript auf deinen Server, mache es mit `chmod +x skriptname.sh` ausfĂŒhrbar und starte es mit `./skriptname.sh`. Stelle sicher, dass `jq` und `bc` installiert sind (`apt install jq bc`).
Beeinflusst Docker die Performance des OS?
Docker hat einen sehr geringen Overhead, da es keine vollstÀndige Virtualisierung ist, sondern Kernel-Features (Namespaces, Cgroups) nutzt. Dennoch gibt es Unterschiede im Netzwerk-Stack (Bridge/Overlay) und im Storage-Driver (overlay2).
Kann ich das Skript auch auf einem Raspberry Pi nutzen?
Ja, das Skript prĂŒft zwar die Architektur nicht explizit ab, nutzt aber Standard-Docker-Images (nginx:alpine, sysbench), die meist auch als Multi-Arch (ARM64) verfĂŒgbar sind. Die Ergebnisse sind dann natĂŒrlich nur mit anderen Pis vergleichbar.
Sind die Ergebnisse allgemeingĂŒltig?
Nein, Benchmarks sind immer Momentaufnahmen. Die Performance hÀngt stark vom Hoster, der Auslastung des Host-Systems (bei vServern) und der genauen Kernel-Version ab.