Umfassender Leitfaden zu Systemd Cgroups für Ressourcenbegrenzung und Isolation
Nutzen Sie systemd cgroups, Slices und Unit-Eigenschaften, um CPU, Arbeitsspeicher und I/O zu begrenzen, ohne rohe cgroup-Dateien bearbeiten zu müssen.
Umfassender Leitfaden zu Systemd Cgroups für Ressourcenbegrenzung und Isolation
Systemd platziert Dienste bereits in Linux-Control-Groups. Sie müssen keine rohen cgroup-Verzeichnisse von Hand erstellen, um einen Batch-Worker davon abzuhalten, die gesamte Maschine zu belegen. In vielen Fällen reicht es, ein paar Eigenschaften zu einem Dienst oder Slice hinzuzufügen, systemd neu zu laden und CPU-, Speicher-, Task- und I/O-Kontrollen zu erhalten, die einen Neustart überdauern und in den normalen systemctl-Werkzeugen sichtbar sind.
Die Kunst liegt darin, die richtige Art der Begrenzung zu wählen. Eine harte Speichergrenze kann den Host schützen, aber den Dienst beenden, wenn sie zu niedrig angesetzt ist. CPU-Gewichtungen sind sanft, bis das System ausgelastet ist. CPU-Kontingente sind streng, können aber Latenz verursachen. I/O-Grenzen hängen vom Speicherstack und der cgroup-Version ab. Ressourcenkontrolle ist kein Kontrollkästchen; es ist ein betrieblicher Kompromiss.
Grundlegendes Verständnis von Control Groups (cgroups)
Bevor wir uns mit der Implementierung von systemd befassen, ist es wichtig, die grundlegenden Konzepte von cgroups zu verstehen. Cgroups sind ein hierarchischer Mechanismus im Linux-Kernel, der es Ihnen ermöglicht, Prozesse zu gruppieren und diesen Gruppen dann Ressourcenverwaltungsrichtlinien zuzuweisen. Diese Richtlinien können Folgendes umfassen:
- CPU: Begrenzung der CPU-Zeit, Priorisierung des CPU-Zugriffs.
- Speicher: Festlegen von Speichernutzungsgrenzen, Verhindern von Out-of-Memory (OOM)-Bedingungen.
- I/O: Drosselung von Lese-/Schreibvorgängen auf der Festplatte.
- Netzwerk: Netzwerkkontrolle ist über Linux Traffic Control und zugehörige Werkzeuge möglich, aber die integrierten Unit-Eigenschaften von systemd konzentrieren sich hauptsächlich auf CPU, Speicher, Prozessanzahl, Gerätezugriff und Block-I/O.
- Gerätezugriff: Kontrolle des Zugriffs auf bestimmte Geräte.
Der Kernel stellt cgroup-Konfigurationen über ein virtuelles Dateisystem bereit, das normalerweise unter /sys/fs/cgroup eingehängt ist. Jeder Controller (z. B. cpu, memory) hat sein eigenes Verzeichnis, und innerhalb dieser Verzeichnisse stellen Hierarchien von Verzeichnissen Gruppen und ihre zugehörigen Ressourcengrenzen dar.
Systemds Cgroup-Verwaltungsarchitektur
Systemd abstrahiert die Komplexität der direkten cgroup-Manipulation, indem es ein strukturiertes Unit-Verwaltungssystem bereitstellt. Es organisiert Prozesse in einer Hierarchie von Units, die dann auf cgroup-Hierarchien abgebildet werden. Die wichtigsten Unit-Typen für die Ressourcenverwaltung sind:
- Slices: Dies sind abstrakte Container für Service-Units. Slices bilden eine Hierarchie, die die Delegation von Ressourcen ermöglicht. Beispielsweise könnte ein Slice für Benutzersitzungen Slices für einzelne Anwendungen enthalten. Systemd erstellt automatisch Slices für Systemdienste, Benutzersitzungen und virtuelle Maschinen/Container.
- Scopes: Diese werden typischerweise für temporäre oder dynamisch erstellte Gruppen von Prozessen verwendet, die oft mit Benutzersitzungen oder Systemdiensten verbunden sind, die nicht als vollständige Service-Units verwaltet werden. Sie sind transient und existieren, solange die Prozesse in ihnen laufen.
- Services: Dies sind die grundlegenden Units für die Verwaltung von Daemons und Anwendungen. Wenn eine Service-Unit gestartet wird, platziert systemd ihre Prozesse in einer cgroup-Hierarchie, normalerweise innerhalb eines Slices. Ressourcengrenzen können direkt auf Service-Units angewendet werden.
Die Standardhierarchie von systemd sieht oft so aus:
-.slice (Root-Slice)
|- system.slice
| |- <service_name>.service
| |- another-service.service
| ...
|- user.slice
| |- user-1000.slice
| | |- session-c1.scope
| | | |- <application>.service (falls vom Benutzer gestartet)
| | | ...
| | ...
| ...
|- machine.slice (für VMs/Container)
...
Anwenden von Ressourcengrenzen mit Systemd-Unit-Dateien
Systemd ermöglicht es Ihnen, cgroup-Ressourcengrenzen direkt in den .service-, .slice- oder .scope-Unit-Dateien anzugeben. Diese Direktiven werden in den entsprechenden Abschnitten [Service], [Slice] oder [Scope] platziert.
CPU-Grenzen
Die wichtigsten Direktiven für die CPU-Ressourcenkontrolle sind:
CPUQuota=: Begrenzt die gesamte CPU-Zeit, die die Unit verwenden kann. Dies wird als Prozentsatz (z. B.50%für einen halben CPU-Kern) oder als Bruchteil eines CPU-Kerns (z. B.0.5) angegeben. Es ist auch möglich, einen Wert in Mikrosekunden pro Periode anzugeben. Die Standardperiode beträgt 100 ms.CPUWeight=: Setzt eine relative Gewichtung für CPU-Zeit auf cgroup-v2-Systemen. Eine Unit mit einem höheren Gewicht erhält einen größeren Anteil bei Konflikten, reserviert aber keine CPU, wenn die Maschine im Leerlauf ist.CPUShares=: Ältere Gewichtung aus der cgroup-v1-Ära. Bevorzugen SieCPUWeight=auf modernen Distributionen, es sei denn, Sie benötigen explizit v1-Kompatibilität.CPUQuotaPeriodSec=: Legt den Zeitraum fürCPUQuotafest. Standard ist100ms.
Beispiel: Begrenzung eines Webservers auf 75 % eines CPU-Kerns:
Erstellen oder bearbeiten Sie eine Service-Datei, z. B. /etc/systemd/system/mywebapp.service:
[Unit]
Description=Meine Webanwendung
[Service]
ExecStart=/usr/bin/mywebapp
User=webappuser
Group=webappgroup
# Begrenzung auf 75 % eines CPU-Kerns
CPUQuota=75%
[Install]
WantedBy=multi-user.target
Nach dem Erstellen oder Ändern der Service-Datei laden Sie den systemd-Daemon neu und starten Sie den Dienst neu:
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service
Speichergrenzen
Speichergrenzen werden durch Direktiven wie die folgenden gesteuert:
MemoryMax=: Legt eine harte Grenze für die Speichermenge fest, die die Prozesse der Unit verbrauchen können. Dies kann in Bytes oder mit Suffixen wieK,M,G,T(z. B.512M) angegeben werden.MemoryLimit=: Ältere Schreibweise, die auf einigen Systemen aus Kompatibilitätsgründen beibehalten wird. Bevorzugen SieMemoryMax=bei modernen systemd-Versionen.MemoryHigh=: Legt eine weiche Grenze fest. Wenn diese Grenze erreicht wird, wird die Speicherrückgewinnung (Swapping) aggressiver ausgelöst, aber die harte Grenze wird noch nicht durchgesetzt.MemorySwapMax=: Begrenzt die Menge an Swap-Speicher, die die Unit verwenden kann.
Beispiel: Begrenzung einer Datenbank auf 2 GB RAM:
Erstellen oder bearbeiten Sie eine Service-Datei, z. B. /etc/systemd/system/mydb.service:
[Unit]
Description=Mein Datenbankdienst
[Service]
ExecStart=/usr/bin/mydb
User=dbuser
Group=dbgroup
# Speicher auf 2 Gigabyte begrenzen
MemoryMax=2G
[Install]
WantedBy=multi-user.target
Neu laden und neu starten:
sudo systemctl daemon-reload
sudo systemctl restart mydb.service
I/O-Grenzen
I/O-Drosselung kann mit Direktiven wie den folgenden gesteuert werden:
IOWeight=: Setzt ein relatives Gewicht für I/O-Operationen. Höhere Werte geben eine höhere I/O-Priorität. Bereich ist 1 bis 1000 (Standard 500).IOReadBandwidthMax=: Begrenzt die Lese-I/O-Bandbreite. Angegeben als[<device>] <bytes_per_second>. Zum Beispiel begrenztIOReadBandwidthMax=/dev/sda 100MLeseoperationen auf/dev/sdaauf 100 MB/s.IOWriteBandwidthMax=: Begrenzt die Schreib-I/O-Bandbreite. Ähnliches Format wieIOReadBandwidthMax.
Beispiel: Begrenzung eines Hintergrundverarbeitungsdienstes auf 50 MB/s auf einer bestimmten Festplatte:
Erstellen oder bearbeiten Sie eine Service-Datei, z. B. /etc/systemd/system/batchproc.service:
[Unit]
Description=Batch-Verarbeitungsdienst
[Service]
ExecStart=/usr/bin/batchproc
User=batchuser
Group=batchgroup
# Schreiboperationen auf 50 MB/s auf /dev/sdb begrenzen
IOWriteBandwidthMax=/dev/sdb 50M
# Eine moderate Lesepriorität geben
IOWeight=200
[Install]
WantedBy=multi-user.target
Neu laden und neu starten:
sudo systemctl daemon-reload
sudo systemctl restart batchproc.service
Verwalten und Überwachen von Cgroups
Systemd bietet Werkzeuge, um die mit Ihren Units verbundenen cgroups zu inspizieren und zu verwalten.
Inspizieren des Cgroup-Status
Der Befehl systemctl status liefert Informationen über die cgroup-Mitgliedschaft und Ressourcennutzung einer Unit.
systemctl status mywebapp.service
Achten Sie auf Zeilen, die den cgroup-Pfad angeben. Zum Beispiel:
● mywebapp.service - Meine Webanwendung
Geladen: geladen (/etc/systemd/system/mywebapp.service; aktiviert; voreingestellt: aktiviert)
Aktiv: aktiv (läuft) seit Di 2023-10-27 10:00:00 UTC; vor 1 Tag
Docs: man:mywebapp(8)
Haupt-PID: 12345 (mywebapp)
Tasks: 5 (Limit: 4915)
Speicher: 15.5M
CPU: 2h 30m 15s
CGroup: /system.slice/mywebapp.service
└─12345 /usr/bin/mywebapp
Sie können auch direkt das cgroup-Dateisystem inspizieren:
systemd-cgls # Zeigt die von systemd verwaltete cgroup-Hierarchie an
systemd-cgtop # Ähnlich wie top, aber für cgroups
Um die spezifischen Grenzen zu sehen, die auf die cgroup eines Dienstes angewendet werden:
# Für Speichergrenzen auf einem typischen cgroup-v2-Host
cat /sys/fs/cgroup/system.slice/mywebapp.service/memory.max
# Für CPU-Grenzen
cat /sys/fs/cgroup/system.slice/mywebapp.service/cpu.max
Die genauen Pfade und Dateinamen variieren je nach cgroup-Version und Distribution. Auf cgroup-v1-Systemen können controllerspezifische Pfade wie /sys/fs/cgroup/memory/... noch existieren. Auf cgroup-v2-Systemen ist die einheitliche Hierarchie unter /sys/fs/cgroup/... die normale Ansicht.
Ändern von Cgroup-Grenzen im laufenden Betrieb
Obwohl es bewährte Praxis ist, Grenzen in Unit-Dateien festzulegen, können Sie sie vorübergehend mit systemctl set-property anpassen:
sudo systemctl set-property mywebapp.service CPUQuota=50%
Abhängig von der systemd-Version und den Flags kann set-property einen Drop-In unter /etc/systemd/system.control/ für dauerhafte Eigenschaften schreiben. Verwenden Sie systemctl cat mywebapp.service und systemctl show mywebapp.service -p CPUQuota -p MemoryMax, um zu bestätigen, was passiert ist. Für Infrastructure-as-Code und Peer-Review ist ein expliziter Unit-Drop-In normalerweise klarer.
Slices zur Ressourcendelegation
Slices sind leistungsstark für die Verwaltung von Gruppen von Diensten oder Anwendungen. Sie können Ressourcengrenzen für einen Slice definieren, und alle Dienste oder Scopes innerhalb dieses Slices erben diese Grenzen oder werden durch sie eingeschränkt.
Beispiel: Erstellen eines dedizierten Slices für ressourcenintensive Batch-Jobs:
Erstellen Sie eine Slice-Datei, z. B. /etc/systemd/system/batch.slice:
[Unit]
Description=Batch-Verarbeitungs-Slice
[Slice]
# Gesamt-CPU für alle Jobs in diesem Slice auf 1 Kern begrenzen
CPUQuota=100%
# Gesamtspeicher auf 4 GB begrenzen
MemoryMax=4G
Jetzt können Sie Dienste so konfigurieren, dass sie innerhalb dieses Slices ausgeführt werden, indem Sie die Direktive Slice= in ihren .service-Dateien verwenden:
[Unit]
Description=Spezifischer Batch-Job
[Service]
ExecStart=/usr/bin/mybatchjob
# Diesen Dienst in den batch.slice platzieren
Slice=batch.slice
[Install]
WantedBy=multi-user.target
Laden Sie systemd neu, aktivieren/starten Sie den Slice bei Bedarf (obwohl er oft implizit aktiviert wird) und starten Sie den Dienst.
sudo systemctl daemon-reload
sudo systemctl start mybatchjob.service
Dieser Ansatz ermöglicht es Ihnen, verwandte Prozesse zu gruppieren und ihren kollektiven Ressourcenverbrauch zu verwalten.
Best Practices und Überlegungen
- Beginnen Sie mit inkrementellen Grenzen: Setzen Sie Grenzen zunächst mit konservativen Werten und erhöhen Sie sie bei Bedarf schrittweise. Aggressive Grenzen können Anwendungen destabilisieren.
- Überwachen Sie: Überwachen Sie regelmäßig die Ressourcennutzung Ihres Systems und die Auswirkungen Ihrer cgroup-Einstellungen. Werkzeuge wie
systemd-cgtop,htop,topundiotopsind unverzichtbar. - Verstehen Sie cgroup v1 vs. v2: Systemd unterstützt sowohl cgroup v1 als auch v2. Während viele Direktiven ähnlich sind, bietet v2 eine einheitliche Hierarchie und einige Verhaltensunterschiede. Stellen Sie sicher, dass Sie wissen, welche Version Ihr System verwendet, wenn Sie auf komplexe Probleme stoßen.
- Priorisierung vs. harte Grenzen: Verwenden Sie
CPUWeightfür die Priorisierung, wenn Ressourcen knapp sind, undCPUQuotafür strenge Obergrenzen. Ebenso istMemoryHighfür Druck vor der harten Grenze undMemoryMaxfür die harte Grenze. - Service vs. Slice: Verwenden Sie Service-Units für einzelne Anwendungen und Slices für die Verwaltung von Gruppen verwandter Anwendungen oder Ressourcenpools.
- Dokumentation: Dokumentieren Sie klar die Ressourcengrenzen, die auf kritische Dienste angewendet werden, insbesondere in Produktionsumgebungen.
- OOM-Killer: Beachten Sie, dass der Out-Of-Memory (OOM)-Killer des Kernels einen Prozess beenden kann, wenn er sein
MemoryMax-Limit überschreitet, selbst wenn er sich innerhalb einer cgroup befindet. Systemd kann verwalten, wie sich der OOM-Killer für bestimmte cgroups verhält, indem es Direktiven wieOOMPolicy=verwendet.
Ein sicherer Weg, Grenzen einzuführen
Beginnen Sie mit Beobachtung. Bevor Sie Grenzen hinzufügen, sehen Sie sich an, wie sich der Dienst unter normaler Last und unter seiner schlimmsten erwarteten Last verhält:
systemctl status mywebapp.service
systemd-cgtop
systemctl show mywebapp.service -p MemoryCurrent -p CPUUsageNSec -p TasksCurrent
Für den Speicher ist ein guter erster Schritt oft MemoryHigh= anstelle von MemoryMax=:
[Service]
MemoryHigh=1G
MemoryMax=1536M
MemoryHigh= teilt dem Kernel mit, Druck auszuüben, bevor der Dienst die harte Grenze erreicht. MemoryMax= ist die Wand. Wenn der Prozess sie überschreitet und der Speicher nicht zurückgewonnen werden kann, kann der Kernel einen Prozess in der cgroup beenden. Das kann genau das sein, was Sie für einen außer Kontrolle geratenen Worker wollen, aber es ist eine böse Überraschung für eine Datenbank, es sei denn, Sie haben dafür geplant.
Für die CPU entscheiden Sie, ob Sie Fairness oder eine harte Obergrenze wünschen:
[Service]
CPUWeight=50
Dies senkt die Priorität bei Konflikten, erlaubt dem Dienst aber dennoch, die CPU im Leerlauf zu nutzen. Für Hintergrundjobs ist das oft besser als ein Kontingent.
[Service]
CPUQuota=200%
Dies begrenzt den Dienst auf etwa zwei CPU-Kerne an Zeit. Das ist nützlich für einen lauten Batch-Prozessor, kann aber latenzempfindliche Anwendungen beeinträchtigen, wenn Worker-Threads während Verkehrsspitzen gedrosselt werden.
Für Prozessexplosionen fügen Sie eine Task-Grenze hinzu:
[Service]
TasksMax=200
Dies schützt den Host vor versehentlichen Fork-Stürmen. Setzen Sie es hoch genug für normale Thread-Anzahlen. Java-, Datenbank- und browserähnliche Workloads können mehr Tasks verwenden, als Sie erwarten.
Drop-Ins anstelle der Bearbeitung von Vendor-Units
Vermeiden Sie es, Unit-Dateien zu bearbeiten, die von Paketen unter /usr/lib/systemd/system/ oder /lib/systemd/system/ ausgeliefert werden. Verwenden Sie einen Drop-In:
sudo systemctl edit mywebapp.service
Fügen Sie dann hinzu:
[Service]
MemoryHigh=1G
MemoryMax=1536M
CPUWeight=80
Nach dem Speichern:
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service
systemctl cat mywebapp.service
systemctl cat zeigt die Vendor-Unit und Ihre Überschreibung zusammen an. Das macht die zukünftige Fehlersuche viel einfacher, da die aktive Konfiguration in einem Befehl sichtbar ist.
Slices für Teams, Mandanten und Workload-Klassen
Slices werden nützlich, wenn Sie aufhören, einen Dienst nach dem anderen zu betrachten. Angenommen, ein Host betreibt die API, einen Berichtsgenerator und mehrere Import-Worker. Es ist Ihnen vielleicht egal, welcher Import-Worker die CPU nutzt, aber Sie legen Wert darauf, dass alle Importarbeiten zusammen die API nicht aushungern können.
Erstellen Sie einen Slice:
# /etc/systemd/system/import.slice
[Unit]
Description=Import- und Backfill-Workloads
[Slice]
CPUWeight=30
MemoryHigh=4G
MemoryMax=5G
Platzieren Sie Importdienste darin:
[Service]
Slice=import.slice
ExecStart=/usr/local/bin/import-worker
Jetzt hat die Gruppe gemeinsamen Druck. Dies ist sauberer, als separate harte Obergrenzen für jeden Worker zu setzen und zu hoffen, dass die Mathematik immer noch aufgeht, nachdem jemand einen neuen hinzugefügt hat.
Es gibt ein Benennungsdetail, das Leute erwischt: Slice-Namen kodieren die Hierarchie. customer-a.slice ist ein Slice der obersten Ebene. customer-a-batch.slice ist kein Kind von customer-a.slice; es ist nur ein weiterer Name der obersten Ebene. Hierarchische Slices verwenden Bindestriche auf eine bestimmte Weise als Trennzeichen, also lesen Sie systemd.slice(5), bevor Sie einen großen Slice-Baum entwerfen.
Was Ressourcengrenzen nicht beheben können
Cgroups können verhindern, dass eine Workload den Host überlastet, aber sie können eine unterdimensionierte Maschine nicht schnell machen. Wenn eine Datenbank mehr Speicher für ihren Working Set benötigt, als Sie zulassen, verbringt sie möglicherweise mehr Zeit mit der Speicherrückgewinnung oder fällt unter Last aus. Wenn eine API kurze Antwortzeiten benötigt, kann ein strenges CPU-Kontingent Drosselungsverzögerungen verursachen, die wie zufällige Latenz aussehen. Wenn ein Speichergerät bereits gesättigt ist, können I/O-Gewichtungen die Fairness verbessern, aber keinen Durchsatz erzeugen.
Behandeln Sie Grenzen als Leitplanken. Kombinieren Sie sie mit anwendungsspezifischen Einstellungen: Datenbankpuffergrößen, Worker-Anzahlen, Warteschlangenparallelität, JVM-Heap-Grenzen, Go GOMEMLIMIT, Node-Speicherflags oder was auch immer Ihre Laufzeitumgebung bietet. Die beste Einrichtung ist normalerweise beides: Die Anwendung kennt ihr eigenes Speicher- und Parallelitätsmodell, und systemd schützt den Rest der Maschine, wenn dieses Modell bricht.
Das mentale Modell, das Sie behalten sollten
Verwenden Sie Service-Level-Grenzen für einen einzelnen Daemon. Verwenden Sie Slice-Level-Grenzen für eine Gruppe verwandter Workloads. Verwenden Sie Gewichtungen, wenn Sie Priorität bei Konflikten wünschen. Verwenden Sie Kontingente und harte Speicherobergrenzen, wenn Sie eine feste Grenze benötigen und auf die Konsequenzen vorbereitet sind. Überprüfen Sie die effektiven Eigenschaften mit systemctl show, beobachten Sie das Verhalten mit systemd-cgtop und bewahren Sie die Konfiguration in Drop-Ins oder Unit-Dateien auf, die Ihr Team überprüfen kann.