cgroups v2 limiti di risorse con systemd

11 min di lettura - 3 giugno 2026

hero section cover
Indice
  • Limiti delle risorse di cgroups v2 con systemd
  • Abilitazione di cgroups v2
  • Come systemd organizza i cgroup
  • Limiti della CPU
  • Limiti di memoria con cgroups v2
  • Limiti di I/O
  • Isolamento multi-tenant con slice
  • Monitoraggio con systemd-cgtop e PSI
Condividi

Impostare i limiti di CPU, memoria e I/O con cgroups v2 e systemd. Configurazione pratica per host Linux multi-tenant, con monitoraggio PSI e isolamento delle slice.

Limiti delle risorse di cgroups v2 con systemd

cgroups v2 è il framework unificato di controllo delle risorse del kernel Linux. Sostituisce la gerarchia frammentata della v1 con un unico albero che gestisce in modo coerente CPU, memoria e I/O e supporta l'isolamento dei container in Docker, Kubernetes e systemd. Questo post spiega come abilitare cgroups v2, impostare i limiti tramite systemd e applicarlo a scenari reali di hosting multi-tenant.

Abilitazione di cgroups v2

Le distribuzioni moderne vengono fornite con cgroups v2 abilitato di default: Ubuntu 21.10+, Debian 11+, Fedora 31+ e RHEL/Rocky 9+. I sistemi più vecchi potrebbero utilizzare una gerarchia ibrida o utilizzare ancora v1 come impostazione predefinita. Verificare con:

stat -fc %T /sys/fs/cgroup/

L'output di cgroup2fs conferma che v2 è attivo. tmpfs in genere significa v1.

Per passare da un sistema ibrido alla versione v2 pura, modificare /etc/default/grub e aggiungi quanto segue a GRUB_CMDLINE_LINUX_DEFAULT:

systemd.unified_cgroup_hierarchy=1 cgroup_no_v1=all

Quindi rigenerare GRUB e riavviare:

sudo update-grub
sudo reboot

Per la produzione, esegui il kernel 5.2 o più recente in modo da ottenere il cgroup freezer per v2 e systemd 244+ per la piena cpuset . Su Rocky Linux 8 e RHEL 8 potrebbe anche essere necessario abilitare esplicitamente l'accounting aggiungendo queste righe a /etc/systemd/system.conf:

DefaultCPUAccounting=yes
DefaultMemoryAccounting=yes
DefaultIOAccounting=yes

Ricaricare con sudo systemctl daemon-reexec. Dopo il riavvio, verificare quali controller sono disponibili:

cat /sys/fs/cgroup/cgroup.controllers

Dovresti vedere cpu, memory, ioe pids elencati. Questi controller non sono abilitati per i cgroup figli per impostazione predefinita. Per attivarli, scrivere nel file di controllo del sottotree root:

echo "+cpu +memory +io" | sudo tee /sys/fs/cgroup/cgroup.subtree_control

Per una panoramica completa su come la v2 differisca internamente dalla v1, la presentazione di Michael Kerrisk all'NDC TechTown è la migliore risorsa disponibile:

Come systemd organizza i cgroup

systemd crea un cgroup per ogni servizio che avvia, denominato in base all'unità. nginx.service ottiene /sys/fs/cgroup/system.slice/nginx.service/, e ogni processo che genera risiede all'interno di quel cgroup. Tre tipi di unità si mappano direttamente alla gerarchia:

Tipo di unitàRuoloDescrizione
.sliceNodo internoRaggruppa i servizi correlati e definisce i limiti condivisi
.serviceNodo terminaleGestisce i processi avviati da systemd
.scopeNodo fogliaTraccia i processi avviati esternamente (payload dei container, sessioni di login)

Quattro partizioni predefinite sono disponibili fin da subito: -.slice (root), system.slice, user.slicee machine.slice. Qualsiasi limite applicato a una slice si applica automaticamente a tutti i servizi in essa contenuti.

Una regola della v2 da tenere a mente: i processi possono risiedere solo nei nodi foglia. Un cgroup con cgroup figli non può ospitare direttamente i processi, motivo per cui systemd non inserisce mai i servizi nel tronco di una slice.

Impostare sempre i limiti tramite systemd piuttosto che scrivere direttamente su /sys/fs/cgroup/ . Le modifiche manuale non persistono dopo il riavvio e sono in conflitto con la proprietà esclusiva della gerarchia da parte di systemd. Utilizza systemctl set-property per modifiche una tantum e unit drop-in (systemctl edit nginx.service) per quelle permanenti.

Limiti della CPU

cgroups v2 offre due controlli della CPU: un limite massimo (cpu.max, esposto come CPUQuota in systemd) e un peso proporzionale (cpu.weight / CPUWeight).

CPUQuota è un limite massimo assoluto. CPUQuota=50% consente metà di un core; CPUQuota=200% consente un tempo pari a due core completi. Il servizio viene limitato se cerca di andare oltre, indipendentemente da quanto sia inattiva la parte restante della CPU.

CPUWeight ha importanza solo in caso di contesa. L'intervallo va da 1 a 10.000, il valore predefinito è 100. Tre servizi con pesi di 150, 100 e 50 ricevono all'incirca il 50%, il 33% e il 17% del tempo di CPU quando lo richiedono tutti contemporaneamente. Quando la CPU è altrimenti inattiva, i pesi non limitano nulla.

Per i carichi di lavoro sensibili alla latenza, associare i processi a core specifici con AllowedCPUs=. Questo riduce il cambio di contesto e mantiene attiva la cache per core:

[Service]
CPUQuota=200%
CPUWeight=150
AllowedCPUs=0-3

Utilizza una quota rigida quando hai bisogno di costi prevedibili (fatturazione multi-tenant, isolamento dai vicini rumorosi). Utilizza i pesi quando desideri il massimo utilizzo dell'hardware e hai solo bisogno di un ordinamento per priorità durante i picchi.

Limiti di memoria con cgroups v2

La memoria ha due livelli: memory.high (soft, throttling) e memory.max (hard, OOM). Per informazioni di base su swap, page reclaim e il kernel OOM killer, consulta il nostro post di approfondimento sulla gestione della memoria in Linux.

Impostare memory.high circa dal 10 al 20% al di sotto memory.max. Il kernel inizia a recuperare le pagine e a limitare le allocazioni una volta memory.high viene superato, il che di solito consente al carico di lavoro di riprendersi prima che scatti l’OOM killer. Se l’utilizzo raggiunge memory.max, il kernel termina i processi nel cgroup.

Una configurazione tipica:

[Service]
MemoryHigh=400M
MemoryMax=512M
MemorySwapMax=0

MemorySwapMax=0 disabilita lo swap per questo cgroup. Vale la pena farlo per carichi di lavoro sensibili alla latenza (database, streaming in tempo reale) dove l'I/O dello swap farebbe precipitare la latenza di coda.

Per i pool di worker in cui lasciare fratelli orfani danneggerebbe lo stato condiviso, scrivere 1 nel file memory.oom.group . Quando un processo viene terminato per OOM, il kernel termina tutti i processi nel cgroup contemporaneamente.

Controlla memory.events per vedere con quale frequenza un servizio è stato limitato o terminato per OOM:

cat /sys/fs/cgroup/system.slice/nginx.service/memory.events

Il high e oom_kill ti indicano se i tuoi limiti sono dimensionati correttamente. Valori persistenti diversi da zero indicano che il carico di lavoro necessita di maggiore margine.

Limiti di I/O

Il controller I/O presenta lo stesso design a due modalità: limiti assoluti tramite io.max e condivisione proporzionale tramite io.weight.

I limiti sono per dispositivo a blocchi, identificati dai numeri major:minor. Trovali con lsblk -o NAME,MAJ:MIN. Una tipica configurazione systemd:

[Service]
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 30M
IOReadIOPSMax=/dev/sda 1000
IOWriteIOPSMax=/dev/sda 500

io.weight funziona come cpu.weight: intervallo da 1 a 10.000, valore predefinito 100. Assegnando 500 a un servizio rivolto ai clienti e 50 a un backup notturno si evita che il backup saturi il disco durante le ore di picco, ma gli si consente di utilizzare l'intera larghezza di banda quando nessun altro ne ha bisogno.

I limiti di I/O si applicano solo quando si punta al dispositivo giusto. Il kernel tiene traccia dell'I/O per dispositivo a blocchi, quindi un limite su /dev/sda non ha alcun effetto sull'I/O diretto a /dev/nvme0n1. Su host con più dischi, impostare i limiti per dispositivo.

Isolamento multi-tenant con slice

Per gli ambienti condivisi, definire una slice per tenant. Creare /etc/systemd/system/tenant-a.slice:

[Slice]
CPUQuota=200%
CPUWeight=150
MemoryHigh=3584M
MemoryMax=4096M
MemorySwapMax=0
IOReadBandwidthMax=/dev/sda 200M
TasksMax=512

TasksMax=512 limita il numero totale di processi e thread, impedendo così che una fork bomb in un tenant possa mandare in crash l'host. Inserisci i servizi del tenant in questa slice (tramite Slice=tenant-a.slice nei rispettivi file unitari) e questi ereditano tutto automaticamente.

Questo modello funziona anche per separare le attività di background rumorose dai servizi rivolti agli utenti. Inserite backup, rotazione dei log e lavori batch in una background.slice con basso CPUWeight e io.weight . Questi processi ottengono tutte le risorse quando il sistema è inattivo e si fanno da parte quando arriva il traffico di produzione.

Per i runtime dei container come Docker e Podman, aggiungi Delegate=yes ai loro file di unità systemd. Ciò consente loro di gestire i propri sottogruppi (sub-cgroups) senza privilegi di root, e i limiti impostati sulla porzione (slice) genitrice si applicano comunque a tutto ciò che si trova al di sotto.

Monitoraggio con systemd-cgtop e PSI

Per una visualizzazione in tempo reale in stile top di CPU, memoria e I/O per cgroup, eseguire:

systemd-cgtop

Per la gerarchia statica e per sapere dove si trovano i processi, utilizzare systemd-cgls.

La funzionalità v2 più utile per il monitoraggio della produzione è Pressure Stall Information (PSI). PSI riporta la percentuale di tempo in cui le attività in un cgroup sono rimaste in stallo in attesa di una risorsa, esposta in tre file per cgroup:

cat /sys/fs/cgroup/tenant-a.slice/cpu.pressure
cat /sys/fs/cgroup/tenant-a.slice/memory.pressure
cat /sys/fs/cgroup/tenant-a.slice/io.pressure

Una CPU con un utilizzo del 100% e una pressione dello 0% è in buono stato. Ogni task che richiede CPU la sta ottenendo. La stessa CPU con un utilizzo dell'80% ma una pressione del 30% significa che i task sono in coda per l'esecuzione. Imposta un avviso su PSI, non sull'utilizzo: rileva i conflitti che le metriche di utilizzo non rilevano affatto.

Modifica i limiti in tempo reale senza riavviare nulla:

sudo systemctl set-property tenant-a.slice MemoryMax=6144M

La modifica si applica immediatamente e persiste anche dopo il riavvio. In combinazione con gli avvisi basati su PSI, questo ti consente di rispondere ai cambiamenti di carico prima che si trasformino in interruzioni OOM o in una latenza fuori controllo.

Se state eseguendo carichi di lavoro multi-tenant ad alta densità e avete bisogno di un host con il margine necessario per applicare queste politiche in modo pulito, i nostri server dedicati sono fatti apposta per questo.

Blog

In primo piano questa settimana

Altri articoli
Perché è importante avere un VPS potente e senza contatore

Perché è importante avere un VPS potente e senza contatore

Un VPS non misurato offre una larghezza di banda forfettaria a una velocità di porta fissa. Come si differenzia dai piani con contatore, quando conviene e cosa controllare prima dell'acquisto.

7 min di lettura - 9 maggio 2025

Gestione della memoria in Linux: Swap, OOM Killer e Cgroups

12 min di lettura - 31 maggio 2026

Altri articoli
background image

Avete domande o avete bisogno di una soluzione personalizzata?

icon

Opzioni flessibili

icon

Portata globale

icon

Distribuzione immediata

icon

Opzioni flessibili

icon

Portata globale

icon

Distribuzione immediata