Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
Comprehensive guide to modern container image building tools, patterns, and security hardening techniques for production deployments.
Sie kennen die Symptome:
vm.swappiness zu aggressiv eingestellt istkernel.shm* nicht auf den Shared Memory abgestimmt istModerne Infrastruktur-Layer führen unzählige einstellbare Parameter ein: Kernel-Parameter, Docker-Daemon-Einstellungen, Container-Limits, Kubernetes-Ressourcenrichtlinien und workload-spezifische Anpassungen. Diese korrekt zu konfigurieren, macht den Unterschied zwischen einem System, das die 10-fache Last bewältigt, und einem, das bei Skalierung zusammenbricht.
Dieser Guide kodifiziert die Tuning-Patterns aus der Produktionserfahrung, organisiert nach Isolation-Layer und ergänzt durch workload-spezifische Cheat-Sheets.
Ressourcensteuerungen operieren auf verschiedenen Ebenen, jede mit eigenen Parametern und Trade-offs:
Jede Ebene kann die darüber liegende Ebene überschreiben, aber Standardwerte fließen fehlerhaft nach unten, wenn sie nicht bewusst konfiguriert werden.
| Parameter | Standard | Produktion | Zweck |
|---|---|---|---|
net.core.somaxconn | 128 | 4096 | Max. ausstehende SYN-Queue |
net.core.netdev_max_backlog | 1000 | 5000 | Max. Pakete im NIC-Backlog |
net.ipv4.tcp_max_syn_backlog | 1024 | 8192 | Max. SYN-Requests |
net.ipv4.tcp_tw_reuse | 0 | 1 | TIME_WAIT Sockets wiederverwenden |
net.ipv4.ip_local_port_range | 32768-60999 | 1024-65535 | Ephemeral Port Range |
net.ipv4.tcp_fin_timeout | 60 | 30 | FIN-Timeout in Sekunden |
net.ipv4.tcp_keepalive_time | 7200 | 600 | Keepalive Idle in Sekunden |
net.ipv4.tcp_keepalive_intvl | 75 | 30 | Keepalive Probe Intervall |
net.ipv4.tcp_keepalive_probes | 9 | 5 | Keepalive Probe Count |
# /etc/sysctl.d/99-production.conf
net.core.somaxconn = 4096
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# Security
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
Sofort anwenden:
sudo sysctl -p /etc/sysctl.d/99-production.conf
| Parameter | Standard | Produktion | Benchmark-Leitfaden |
|---|---|---|---|
vm.swappiness | 60 | 1-10 (DB), 30 (App) | Niedriger für speichersensitive Workloads |
vm.overcommit_memory | 0 | 1 (K8s Nodes) | Allocator-Overcommit erlauben (kubelet berechnet neu) |
vm.overcommit_ratio | 50 | 100 | Prozentsatz von RAM+Swap für Overcommit |
vm.dirty_ratio | 30 | 20 | % Speicher, ab dem Dirty Pages blockieren |
vm.dirty_background_ratio | 10 | 5 | % Speicher, ab dem Background Writeback startet |
vm.min_free_kbytes | 65536 | 1-2% des RAMs | Notfallreserve gegen OOM |
# /etc/sysctl.d/99-memory.conf
vm.swappiness = 10 # Don't swap unless critical
vm.overcommit_memory = 1 # K8s nodes expect this
vm.overcommit_ratio = 100 # Full overcommit
vm.dirty_ratio = 20 # Start blocking writes at 20%
vm.dirty_background_ratio = 5 # Background writeback at 5%
vm.min_free_kbytes = 1048576 # 1GB reserve on 64GB node
# /etc/sysctl.d/99-fs.conf
fs.file-max = 2097152 # Global open file limit
fs.inotify.max_user_instances = 1024
fs.inotify.max_user_watches = 819200 # For etcd, collectd
# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 65535
* hard nproc 65535
root soft nofile 1048576
root hard nofile 1048576
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 1048576, "Soft": 65535 },
"nproc": { "Name": "nproc", "Hard": 65535, "Soft": 4096 }
},
"live-restore": true,
"userns-remap": "default",
"no-new-privileges": false,
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 10,
"data-root": "/var/lib/docker"
}
Die Standard-Ressourcenlimits von Docker sind großzügig bemessen, können jedoch zu „Noisy Neighbors“ führen:
| Ressource | Standard | Empfohlen | Wann zu überschreiben |
|---|---|---|---|
--pids-limit | (keine) | 100-500 pro Container | Apps mit hoher Fork-Tendenz benötigen mehr |
--memory-swap | unbegrenzt | memory × 2 | Mit --memory-swap -1 deaktivieren, um Swapping auf die Festplatte bei latenzsensitiven DBs zu verhindern |
--memory-reservation | entspricht Limit | 70-80 % des Limits | Verhindert „Burst then Die“-Muster |
--kernel-memory | unbegrenzt | 100-200Mi | Steuert nicht-residente Seitentabellen; niedrige Standardwerte führen bei hohen Verbindungszahlen zu Container-Abstürzen |
security-opt no-new-privileges | false | true (empfohlen) | Blockiert setuid Escalation |
docker run -d \
--name myapp \
--memory=512m \
--memory-reservation=400m \
--memory-swap=-1 \
--pids-limit=200 \
--ulimit nofile=65535:65535 \
--ulimit nproc=4096:8192 \
--security-opt no-new-privileges \
--security-opt seccomp=default.json \
myapp:latest
Kubernetes weist QoS-Klassen basierend auf requests und limits zu:
| QoS-Klasse | Anforderungen | Eviction-Priorität |
|---|---|---|
| Guaranteed | requests.cpu == limits.cpu <br/> requests.memory == limits.memory | Zuletzt evakuiert |
| Burstable | Beliebige requests gesetzt <br/> requests != limits | Mittel |
| BestEffort | Keine requests <br/> Keine limits | Zuerst evakuiert |
# Guaranteed (best for DBs, latency-critical services)
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "2"
memory: "8Gi"
# Burstable (typical for stateless services)
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "4Gi"
# ❌ BestEffort (avoid in production)
resources: {} # or omit entirely
Produktionsregel: Setzen Sie immer requests. Weisen Sie zumindest requests.memory = limits.memory / 2 für Workloads zu, die nicht latenzkritisch sind.
Unter cgroup v2 ist das memory.swap-Accounting aktiviert. Stellen Sie die Kubelet-Konfiguration sicher:
# /var/lib/kubelet/config.yaml
memorySwap:
swapBehavior: NoSwap # NoSwap, LimitedSwap, UnlimitedSwap
limits.memory wird für Swap ignoriertapiVersion: v1
kind: Pod
metadata:
name: hugepages-pod
spec:
containers:
- name: app
image: myapp:latest
resources:
limits:
Hugepages-2Mi: 512Mi # 256 × 2Mi pages
memory: "1Gi"
volumeMounts:
- mountPath: /hugepages
name: hugepage
volumes:
- name: hugepage
emptyDir:
medium: HugePages
Use for: databases (PostgreSQL, MySQL), DPDK applications, in-memory caches.
# /var/lib/kubelet/config.yaml
cpuManagerPolicy: static # none (Standard), static
cpuManagerReconcilePeriod: 5s
requests.cpu = integer cores get exclusive cores. Ideal for low-latency workloads.# Pod, der exklusive CPU 0-1 anfordert
resources:
requests:
cpu: "2" # Muss eine Ganzzahl sein
limits:
cpu: "2"
# /var/lib/kubelet/config.yaml
topologyManagerPolicy: best-effort # none, best-effort, restricted, single-numa-node
# Pods, die ausgerichtete Ressourcen anfordern
resources:
requests:
cpu: "4"
memory: "8Gi"
cpu: "4" # Topology Manager richtet dies auf demselben NUMA-Knoten aus
devices.kubernetes.com/mock: "1" # Device Affinity
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
maxLimitRequestRatio:
cpu: "10"
memory: "4"
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-tuning
data:
# Postgres erwartet Shared-Memory-Segmente, die mit dem Kernel abgestimmt sind
# Passen Sie kernel.shm* an, wenn Sie auf einer Bare-Metal-VM bereitstellen
# /etc/sysctl.d/99-postgres.conf:
# kernel.shmmax = 4294967296 # 4GB
# kernel.shmall = 1048576 # 4GB pages
# kernel.sem = 250 32000 100 128
postgresql.conf: |
shared_buffers = 4GB # 25% des RAM
effective_cache_size = 12GB # 75% des RAM
work_mem = 64MB # Sortierspeicher pro Operation
maintenance_work_mem = 1GB
max_connections = 200
wal_buffers = 128MB
checkpoint_completion_target = 0.9
---
apiVersion: v1
kind: Pod
metadata:
name: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi" # OOM-Ziel ist 2× requests
securityContext:
fsGroup: 999 # Behebt Berechtigungsprobleme auf /var/run/postgresql
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: data
- mountPath: /var/run/postgresql
name: run
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-pvc
- name: run
emptyDir: {}
resources:
requests:
cpu: "500m"
memory: "4Gi"
limits:
cpu: "2"
memory: "8Gi"
# In redis.conf:
# maxmemory 6gb
# maxmemory-policy allkeys-lru
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "1"
memory: "512Mi"
# In nginx.conf:
# worker_processes auto;
# worker_connections 4096; # Offene Datei-Limits beachten
# worker_rlimit_nofile 65535;
# multi_accept on;
# access_log off; # Bei hohem Durchsatz an syslog leiten
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "8"
memory: "32Gi"
# In server.properties:
# log.dirs = /kafka/data
# num.network.threads = 8
# num.io.threads = 16
# socket.send.buffer.bytes = 102400
# socket.receive.buffer.bytes = 102400
# log.flush.interval.messages = 10000
# log.flush.interval.ms = 1000
# num Partitions: 2× Broker-Anzahl
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi"
# In jvm.options:
# -Xms8g
# -Xmx8g # Heap darf 50 % des Pod-Speichers nicht überschreiten
# In elasticsearch.yml:
# bootstrap.memory_lock: true
# indices.queries.cache.size: 10%
# indices.fielddata.cache.size: 20%
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
# JVM args:
# -Xms512m # Start mit 512MB Heap
# -Xmx1536m # Heap auf 50 % des Pod-Speichers begrenzen (Non-Heap Overhead)
# -XX:+UseG1GC
# -XX:MaxGCPauseMillis=200
# -XX:+PrintGCDetails
# -XX:+PrintGCDateStamps
# -Xloggc:/logs/gc.log
apiVersion: v1
kind: Pod
metadata:
name: ml-worker
spec:
runtimeClassName: nvidia # Für NVIDIA GPU-Unterstützung
containers:
- name: ml-app
image: pytorch:24.01-cuda12.1
resources:
requests:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: 1
limits:
cpu: "8"
memory: "32Gi"
nvidia.com/gpu: 1
env:
- name: CUDA_VISIBLE_DEVICES
value: "0"
- name: NVIDIA_VISIBLE_DEVICES
value: "all"
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # Falls benötigt
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
ephemeral-storage: "1Gi"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir:
sizeLimit: "100Mi"
- name: run
emptyDir:
sizeLimit: "50Mi"
The RuntimeDefault profile blocks dangerous syscalls:
kexec_load, module_load, delete_module (kernel persistence)ptrace, process_vm_readv/writev (process manipulation)mount, umount, pivot_root (filesystem remapping)clock_settime, settimeofday (system time manipulation)bpf (unprivileged BPF is blocked by kernel.unprivileged_bpf_disabled=1)Use custom profiles only when required (e.g., some FUSE workloads).
# Netzwerk
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.ipv4.ip_local_port_range
# Speicher
sysctl vm.swappiness vm.dirty_ratio vm.min_free_kbytes
# Dateisystem
sysctl fs.file-max fs.inotify.max_user_watches
# Pro Knoten
cat /proc/sys/vm/swappiness
cat /proc/sys/net/core/somaxconn
# Den cgroup-Pfad eines Containers finden
docker inspect --format '{{.State.Pid}}' myapp | xargs cat /proc/$$/cgroup
# Limits prüfen
cat /sys/fs/cgroup/memory.max # Memory-Limit
cat /sys/fs/cgroup/cpu.max # CPU-Quota (z. B. "200000 100000")
cat /sys/fs/cgroup/pids.max # PID-Limit
cat /sys/fs/cgroup/memory.swap.max # Swap-Limit
kubectl get pod -o jsonpath='{.metadata.name}{"\t"}{.status.qosClass}{"\n"}'
# Guaranteed? Blick in .spec.containers[].resources
kubectl get pod -o json | jq '.items[] | select(.metadata.name=="myapp") |
{
name: .metadata.name,
qos: .status.qosClass,
resources: .spec.containers[].resources
}'
# Aus Prometheus
rate(container_cpu_cfs_throttled_periods_total[5m]) / rate(container_cpu_cfs_periods_total[5m])
# Alert, wenn > 0,5 (50 % throttled)
# fd-Count pro Container prüfen
find /proc/*/fd 2>/dev/null | xargs -I{} sh -c 'echo $(dirname $(dirname {})): $(ls {}/fd | wc -l)' | sort -rn | head
# Oder cgroup verwenden
cat /proc/{PID}/limits | grep "Max open files"
# Auf dem Host
dmesg | grep -i "killed process"
# Auf K8s
kubectl get pod -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}{end}' | grep OOMKilled
requests on All Pods# Schlecht
resources:
limits:
memory: "512Mi"
cpu: "1"
# wird zu BestEffort, wenn requests nicht gesetzt sind
Result: First to evict; unpredictable scheduling; node resource exhaustion.
kernel.shmmax Too Low# PostgreSQL startet nicht:
# FATAL: could not create shared memory segment: Invalid argument
Fix: Adjust kernel parameters for database pods:
# /etc/sysctl.d/99-database.conf
kernel.shmmax = 68719476736 # 64GB
kernel.shmall = 16777216 # 64GB / 4096 (page size)
--pids-limit Not Set# Eine Fork-Bombe in einem Container kann den gesamten Node zum Absturz bringen
Fix: Set limits on daemon or container:
{
"default-pids-limit": 200
}
Or per-container:
docker run --pids-limit 200 myapp
# Standard: limits.memory=512Mi, memory-swap=unlimited
# → Container kann auf die Disk swappen (langsam) und den Host-Swap erschöpfen
--memory-swap=-1, um Swapping zu deaktivieren, oder konfigurieren Sie den kubelet memorySwap.swapBehavior: NoSwap.runAsNonRoot + fsGroup Mismatch# PostgreSQL sleeps at startup, can't read/write /var/run/postgresql
Fix: Setzen Sie fsGroup so, dass es mit dem DB-Benutzer übereinstimmt:
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999
# Kubernetes operations break when clocks are set back in time
Fix: Verwenden Sie NTP mit maxslewrate oder makestep, um große Sprünge nach vorne zu erlauben, aber Sprünge zurück abzulehnen:
# /etc/chrony.conf
maxslewrate 1000 # Allow 1000x faster clock catch-up
makestep 1.0 -1 # Step clock if offset > 1 second (forward only)
# Security review fails: "RuntimeDefault not set"
Fix: Profil explizit setzen:
podSecurityContext:
seccompProfile:
type: RuntimeDefault
# Alert: Pod Memory Usage Nearing Limit
- alert: PodMemoryHigh
expr: container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} memory > 90% of limit"
# Alert: OOM Kill
- alert: PodOOMKilling
expr: rate(kube_pod_container_status_restarts_total{reason="OOMKilled"}[5m]) > 0
for: 0m
annotations:
summary: "Container {{ $labels.container }} in pod {{ $labels.pod }} OOM killed"
RCA:
limits.memory angemessen ist (ist die App speicherintensiv?)kubectl logs ... + heap Dump (JVM) oder ps + pmap (native)requests.memory und limits.memory erhöhen# Alert: High CPU Throttling
- alert: HighCPUThrottling
expr: |
rate(container_cpu_cfs_throttled_periods_total[5m]) /
rate(container_cpu_cfs_periods_total[5m]) > 0.5
for: 10m
annotations:
summary: "Container {{ $labels.container }} throttled > 50%"
RCA:
limits.cpu erhöhen)nodeSelector auf dedizierte Nodes verschiebenstatic Policy für garantierte exklusive Kerne in Betracht ziehen# Alert: TCP Listen Drops / SYN Flood
- alert: TCPListenDrops
expr: rate(netstat_Tcp_ListenDrops[5m]) > 10
for: 5m
annotations:
summary: "High TCP listen drops (increase net.core.somaxconn?)"
RCA:
net.core.somaxconn auf dem Node erhöhennet.ipv4.tcp_max_syn_backlog optimierensomaxconn=4096, tcp_max_syn_backlog=8192, tw_reuse=1swappiness=10 (DB), dirty_ratio=20, min_free_kbytes=1-2%file-max=2097152, inotify.max_user_watches=819200kexec_load_disabled=1, unprivileged_bpf_disabled=1, yama.ptrace_scope=2default-ulimits gesetzt (nofile, nproc)userns-remap=default (UID remap)live-restore=true (Resilienz beim Daemon-Neustart)log-opts.max-size=50m (Log-Rotation)LimitRange definiert (Standard-Requests + Limits)ResourceQuota definiert (Namespace-Erschöpfung verhindern)pod-security.kubernetes.io/enforce=restrictedNetworkPolicy default-deny + explizites allowrequests.cpu und requests.memory immer gesetztlimits.cpu und limits.memory gesetzt (oder Grund dokumentiert)securityContext.runAsNonRootallowPrivilegeEscalation=falsereadOnlyRootFilesystem=true mit tmpfs-MountsseccompProfile.type=RuntimeDefaultpids-limit gesetzt (auf Daemon oder Container)volumeMounts für /tmp, /var/run, /var/cachekernel.shm* via Host-sysctls optimiertmaxmemory + maxmemory-policy gesetztworker_connections, worker_rlimit_nofile abgestimmt-Xms/-Xmx = 25-50 % des Pod-SpeichersruntimeClassName: nvidia, nvidia.com/gpu requestsrequests setzen: BestEffort wird zuerst evicted und ist unvorhersehbarcap-drop ALL, no-new-privileges, seccomp=RuntimeDefault reduzieren die Angriffsfläche ohne Performance-Einbußendmesg, sysctl und Container-Logs, um Engpässe zu identifizierenmaxmemory, die JVM benötigt Puffer über den Heap hinaus--file=/etc/sysctl.d/*.conf) und kubelet-Konfigurationen erneut anwendenEine gut abgestimmte Umgebung bewältigt das 10-fache an Traffic, erholt sich reibungslos von Ausfällen und bleibt sicher gegenüber gängigen Container-Escape-Vektoren.
man sysctl, man cgroupsbcc-tools, bpftrace für fortgeschrittenes Container-Debugging