Optimierung der Docker-Container-Leistung mit CPU- und Speicherbegrenzungen

Erfahren Sie, wie Sie die Docker-Container-Leistung durch Festlegen von CPU- und Speicherbegrenzungen optimieren können. Diese Anleitung behandelt wesentliche Konfigurationsoptionen wie CPU-Anteile, Kontingente, Speicherbegrenzungen und Swap. Entdecken Sie, wie Sie die Ressourcennutzung von Containern mit `docker stats` überwachen und bewährte Methoden implementieren, um Ressourcenmangel zu verhindern, die Anwendungsstabilität zu verbessern und die Gesamtsystemeffizienz zu steigern.

Optimierung der Docker-Container-Leistung mit CPU- und Speicherbegrenzungen

Docker-Container verhalten sich nicht automatisch wie kleine virtuelle Maschinen mit festen Ressourcen. Wenn Sie Docker nichts anderes mitteilen, kann ein Container wie jeder andere Prozess um Host-CPU und -Speicher konkurrieren. Das ist auf einem Laptop praktisch und auf einem gemeinsam genutzten Server riskant.

CPU- und Speicherbegrenzungen sind keine magischen Leistungsverstärker. Eine zu niedrige Begrenzung macht eine Anwendung langsamer oder instabil. Eine zu hohe Begrenzung schützt den Host nicht. Das Ziel ist es, jedem Container genügend Spielraum für normale und Spitzenlasten zu geben, während verhindert wird, dass ein fehlerhafter Prozess alles andere auf der Maschine lahmlegt.

Warum Begrenzungen wichtig sind

Der häufige Fehlermodus ist bekannt: Ein Container startet einen außer Kontrolle geratenen Job, der Speicher steigt, der Host beginnt zu swappen, und nicht betroffene Dienste werden langsamer. Oder ein CPU-intensiver Batch-Job verwendet jeden Kern, und der API-Container daneben beginnt, Latenzziele zu verfehlen.

Begrenzungen helfen bei drei praktischen Problemen:

  • Sie schützen den Host davor, dass ein einzelner Container den gesamten Speicher verwendet.
  • Sie machen Leistungstests ehrlicher, da der Container mit produktionsähnlichen Einschränkungen läuft.
  • Sie zwingen Sie dazu, zu bemerken, wenn eine Anwendung Skalierung oder Optimierung benötigt, anstatt stillschweigend Ressourcen von Nachbarn auszuleihen.

Beginnen Sie nicht mit zufälligen Zahlen. Führen Sie den Dienst unter Last aus, beobachten Sie die tatsächliche Nutzung und setzen Sie Begrenzungen mit Spielraum. Ein kleiner Hintergrundarbeiter und ein JVM-Dienst benötigen eine sehr unterschiedliche Behandlung.

CPU-Steuerung

Docker bietet einige CPU-Steuerungen. Die im Alltag nützlichste ist --cpus.

Begrenzen Sie einen Container auf etwa eineinhalb CPUs:

docker run -d --name api --cpus="1.5" nginx

Dies ist einfacher zu lesen als das manuelle Festlegen von CFS-Kontingent und -Zeitraum. Unter der Haube verwendet Docker die Linux-CPU-Planungssteuerung.

--cpu-shares ist anders. Es ist ein relatives Gewicht bei CPU-Konkurrenz, keine harte Grenze:

docker run -d --name important-api --cpu-shares 2048 nginx
docker run -d --name batch-worker --cpu-shares 512 worker-image

Wenn der Host freie CPU hat, können beide Container mehr nutzen. Wenn sie konkurrieren, hat der erste Container mehr Planungsgewicht. Dies ist nützlich für die Priorisierung, stoppt jedoch nicht, dass ein Container freie CPU nutzt.

Wenn Sie genaue CFS-Einstellungen benötigen, sind --cpu-period und --cpu-quota weiterhin verfügbar:

docker run -d --name limited-api \
  --cpu-period 100000 \
  --cpu-quota 50000 \
  nginx

Dies gibt dem Container 50.000 Mikrosekunden CPU-Zeit in jedem 100.000-Mikrosekunden-Zeitraum, etwa die Hälfte einer CPU. Für die meisten Teams kommuniziert `--cpus="0.5"" denselben Zweck klarer.

Speichersteuerung

Speicherbegrenzungen sind gefährlicher als CPU-Begrenzungen, da das Überschreiten den Prozess töten kann. Setzen Sie sie bewusst und testen Sie das Spitzenverhalten.

Die grundlegende Option ist --memory:

docker run -d --name web --memory 512m nginx

Wenn der Container die Grenze überschreitet, kann der Kernel einen Prozess im Container töten. In der Docker-Ausgabe sehen Sie dies oft als OOM-Kill:

docker inspect web --format '{{.State.OOMKilled}}'

Das Swap-Verhalten ist leicht misszuverstehen. Wenn --memory gesetzt ist, ist --memory-swap die Gesamtspeicher- plus Swap-Erlaubnis, nicht die Swap-Erlaubnis allein.

Dies erlaubt 256 MB RAM und bis zu 256 MB Swap, insgesamt 512 MB:

docker run -d --name worker --memory 256m --memory-swap 512m alpine

Das Setzen von --memory-swap gleich --memory deaktiviert zusätzlichen Swap für diesen Container auf Systemen, auf denen Swap-Abrechnung verfügbar ist:

docker run -d --name no-extra-swap --memory 256m --memory-swap 256m alpine

Das Setzen von --memory-swap -1 erlaubt unbegrenzten Swap bis zum verfügbaren Swap des Hosts. Das kann einen Prozess am Leben halten, kann aber die Latenz erheblich verschlechtern.

Überwachung vor und nachher

Verwenden Sie docker stats, während der Dienst unter realistischer Last steht:

docker stats
docker stats web worker

Beobachten Sie CPU %, MEM USAGE / LIMIT, BLOCK I/O und PIDS. Ein Dienst, der bei 100 Prozent seiner Speichergrenze sitzt, ist nicht "effizient"; er ist einen Verkehrsspitze davon entfernt, getötet zu werden. Ein CPU-gebundener Dienst, der ständig gedrosselt wird, kann eine akzeptable durchschnittliche CPU zeigen, während Benutzer langsame Anfragen sehen.

Für eine schnelle Einzelansicht:

docker stats --no-stream

Für OOM- und Neustart-Hinweise:

docker ps -a
docker inspect <container> --format 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}}'
docker logs --tail 100 <container>

Für die laufende Überwachung exportieren Sie Containermetriken an Prometheus, Grafana, einen Cloud-Überwachungsdienst oder die Plattform, die Sie bereits verwenden. docker stats ist Triage, kein langfristiges Alarmsystem.

Ein sinnvoller Optimierungs-Workflow

Beginnen Sie ohne enge Grenzen in einer Testumgebung und erfassen Sie die Nutzung im Leerlauf, normal und bei Spitzenlast. Setzen Sie dann den Speicher über der beobachteten Spitze mit genügend Spielraum für Garbage Collection, Cache-Wachstum, TLS-Handshakes und kurze Ausbrüche. Entscheiden Sie bei der CPU, ob Sie eine harte Grenze oder nur eine relative Priorität benötigen.

Beispiel für eine kleine API:

docker run -d --name api \
  --cpus="2" \
  --memory 1g \
  --memory-swap 1g \
  -p 8080:80 \
  my-api:latest

Das besagt, dass die API bis zu zwei CPUs, 1 GB RAM und keine zusätzliche Swap-Erlaubnis erhält. Es ist nicht universell korrekt. Es ist ein klarer Ausgangspunkt für einen Dienst, der in diesem Bereich getestet wurde.

Für Docker Compose unterstützt die lokale Docker-Engine Ressourcenoptionen wie:

services:
  api:
    image: my-api:latest
    mem_limit: 1g
    cpus: 2.0

Compose- und Swarm/Kubernetes-Ressourceneinstellungen sind nicht identisch. Überprüfen Sie daher das Bereitstellungsziel, bevor Sie annehmen, dass ein Feld überall das gleiche Verhalten aufweist.

Häufige Fehler

Das zu niedrige Setzen des Speichers ist der häufigste Fehler. Die Anwendung startet, besteht einen Rauchtest und stirbt dann unter einem Anfragemuster, das mehr Speicher zuweist, als der Test abgedeckt hat.

Die Verwendung von CPU-Begrenzungen, um ineffizienten Code zu verstecken, ist ein weiterer Fehler. Wenn ein Dienst langsam ist, weil er pro Anfrage teure Arbeit leistet, macht eine niedrigere CPU-Grenze die Symptome deutlicher. Sie behebt nicht den Codepfad.

Das Ignorieren von Laufzeitumgebungen schadet ebenfalls. JVM, Node.js, Go, Python und Ruby-Apps reagieren unterschiedlich auf Speicherdruck. Einige Laufzeiten benötigen explizite Heap-Einstellungen, damit ihre internen Speicherannahmen mit der Containerbegrenzung übereinstimmen.

Optimieren Sie schließlich nicht nur einen Container. Der Host benötigt weiterhin CPU und Speicher für den Kernel, Docker, Protokollierung, Überwachungsagenten und alle Sidecar-Prozesse. Lassen Sie Platz.

Gute Begrenzungen machen Fehler kleiner und die Leistung vorhersagbarer. Sie sollten aus Messungen stammen, nicht aus Vermutungen.