I cgroups (Control Groups) sono una funzionalità del kernel Linux che permette di controllare, limitare e monitorare l’utilizzo delle risorse da parte dei processi come CPU, memoria RAM, I/O su disco e numero di processi generati. In questo modo si evita che un singolo programma possa rallentare o bloccare l’intero sistema.
I processi vengono organizzati all’interno di una gerarchia gestita direttamente dal kernel.
Linux permette sia una gestione manuale dei cgroup tramite cgroupfs, utile per comprendere i meccanismi interni, sia una gestione automatizzata e persistente tramite systemd, preferibile per l’uso quotidiano e in produzione.
Gestione manuale dei cgroup con cgroupfs
Comprendere i meccanismi interni del kernel
Creare un cgroup
frank@laptop01:~$ sudo mkdir /sys/fs/cgroup/testgroup
Stiamo aggiungendo un nuovo gruppo all’interno della gerarchia dei cgroup già esistente. Nel nostro caso, testgroup è un cgroup figlio perché si trova dentro la directory principale del sistema dei cgroup:/sys/fs/cgroup/ = cgroup padre (cgroup root)
|
|_testgroup = cgroup figlio
Il cgroup root è il punto più alto della gerarchia e tutti i cgroup che creiamo sono figli del cgroup root o figli di altri cgroup.
Non ci sono limiti dalla radice cgroup.
I controller attivi nel cgroup padre (root), come CPU, Memory, IO e PIDS vengono automaticamente ereditati dai cgroup figli come testgroup.
Visualizzare i file del nuovo cgroup
Per impostazione predefinita il gruppo figlio testgroup appena creato eredita tutte le impostazioni del gruppo padre cgroup. Non ci sono limiti dalla radice cgroup.
Elenco dei file presenti nel cgroup testgroup:
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup
cgroup.controllers cpu.uclamp.min memory.oom.group
cgroup.events cpu.weight memory.peak
cgroup.freeze cpu.weight.nice memory.pressure
cgroup.kill io.bfq.weight memory.reclaim
cgroup.max.depth io.latency memory.stat
cgroup.max.descendants io.max memory.swap.current
cgroup.pressure io.pressure memory.swap.events
cgroup.procs io.prio.class memory.swap.high
cgroup.stat io.stat memory.swap.max
cgroup.subtree_control io.weight memory.swap.peak
cgroup.threads irq.pressure memory.zswap.current
cgroup.type memory.current memory.zswap.max
cpu.idle memory.events memory.zswap.writeback
cpu.max memory.events.local pids.current
cpu.max.burst memory.high pids.events
cpu.pressure memory.low pids.events.local
cpu.stat memory.max pids.max
cpu.stat.local memory.min pids.peak
cpu.uclamp.max memory.numa_stat
Quindi, i controller definiscono quali risorse possono essere monitorate o limitate.
Si riconoscono facilmente dal prefisso dei file: cpu.* - memory.* - io.* - pids.*
Questo significa che i cgroup figli (come testgroup) possono utilizzare i controller per:- CPU: gestione cpu
- Memory: gestione memoria
- I/O: gestione I/O su disco
- PIDs: numero processi
In pratica, il processo padre decide quali risorse si possono controllare e il processo figlio applica quei controlli ai suoi processi.
Cos’è cgroupfs
Quando creiamo un nuovo cgroup in Linux il kernel genera automaticamente una serie di file utilizzati per monitorare e controllare le risorse dei processi appartenenti a quel gruppo. Questi file non sono memorizzati realmente sul disco: fanno parte del filesystem virtuale cgroupfs.
I cgroup sono gestiti tramite un filesystem virtuale esposto dal kernel sotto /sys/fs/cgroup.
Questo filesystem si chiama appunto cgroupfs e non è un vero filesystem su disco. I file tipo cpu.max, memory.max, io.max, cgroup.procs, etc non esistono davvero su HDD sono finte entry che il kernel usa per farti leggere e scrivere impostazioni dei cgroup.
frank@laptop01:~$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot)
La directory /sys/fs/cgroup fà parte di uno pseudo filesystem chiamato cgroupfs: i file che contiene non sono file reali memorizzati su disco ma una interfaccia diretta al kernel. Quando leggiamo o scriviamo in questi file non stiamo modificando dati su storage ma stiamo chiedendo al kernel di fornire informazioni aggiornate o applicare regole sui processi gestiti dai cgroups.
Abilitare i controller nel cgroup padre
All’interno dei cgroups esistono dei controller ovvero i componenti che si occupano concretamente di assegnare e limitare le risorse ai processi.
Per vedere quali controller sono disponibili nel tuo sistema Linux e aggiungerli:frank@laptop01:~$ cat /sys/fs/cgroup/cgroup.subtree_control
io memory pids
frank@laptop01:~$ echo "+cpu" | sudo tee -a /sys/fs/cgroup/cgroup.subtree_control
frank@laptop01:~$ echo "+cpuset" | sudo tee -a /sys/fs/cgroup/cgroup.subtree_control
frank@laptop01:~$ cat /sys/fs/cgroup/cgroup.subtree_control
cpuset cpu io memory pids
Con l'esecuzione del comando sopra viene abilitato il controller cpu per i cgroup figlio immediati del /sys/fs/cgroup (root cgroup) come il cgroup appena creato testgroup.
Gli utenti possono leggere il contenuto del file cgroup.sbtree_control a qualsiasi livello per vedere quali controller saranno disponibili per l'abilitazione nel gruppo figlio successivo.
Grazie ai controller puoi evitare che una singola applicazione mandi in crisi tutto il server: il sistema rimane stabile e tu mantieni il controllo.
Abilitare i controller desiderati per cgroups figlio
In questo caso il comando garantisce che i cgroup figli di testgroup avranno disponibili solo i controller cpu e cpuset e non controller come memory o pids.
frank@laptop01:~$ echo "+cpu +cpuset" | sudo tee /sys/fs/cgroup/testgroup/cgroup.subtree_control
frank@laptop01:~$ cat /sys/fs/cgroup/testgroup/cgroup.subtree_control
frank@laptop01:~$ sudo mkdir /sys/fs/cgroup/testgroup/child1
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup/child1
frank@laptop01:~$ cat /sys/fs/cgroup/testgroup/cgroup.subtree_control
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup/child1 | grep '^cpu\.'
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup/child1 | grep '^cpuset\.'
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup/child1 | grep '^memory\.'
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup/child1 | grep '^pids\.'
Quindi, cgroup.subtree_control non limita il cgroup corrente ma decide quali controller potranno usare i cgroup figli. In questo caso il comando garantisce che il cgroup figlio successo ha solo il controller cpu e non controller come memory oppure pids.
Spostare un processo nel cgroup
Nell'esempio inserisco i processi al cgroups e utilizzo il controller CPU per i processi inseriti: echo “PID” | sudo tee /sys/fs/cgroup/testgroup/cgroup.procs
--cpu 1 avrai un solo worker:frank@laptop01:~$ stress --cpu 1&
Dopo aver avviato il comando in background possiamo recuperare il PID del processo padre e poi trovare il PID del suo worker.
Salva il PID dell’ultimo processo avviato in background
frank@laptop01:~$ PARENT=$!
frank@laptop01:~$ echo "$PARENT"
8338
La variabile $! contiene il PID dell’ultimo comando lanciato in background con &.
Qui lo salviamo in PARENT: è il PID del processo padre (ad esempio stress).
Trova i processi figli (worker) creati da quel PID
frank@laptop01:~$ WORKER=$(pgrep -P "$PARENT")
frank@laptop01:~$ echo "$WORKER"
8343
pgrep -P <PID> cerca tutti i processi che hanno come padre il PID indicato. In questo caso recupera i worker generati dal processo stress e li salva in WORKER.
Stampa a video il PID del worker
frank@laptop01:~$ echo "PID worker = $WORKER"
PID worker = 8343
Mostra a schermo il PID del worker trovato. Se il comando genera più worker
(es. stress --cpu 4) WORKER può contenere più PID.
Usando pstree è possibile visualizzare i processi ad albero
frank@laptop01:~$ sudo dnf install pstree
frank@laptop01:~$ pstree -p "$PARENT"
stress(8338)───stress(8343)
Inseriamo il processo nel cgroup
frank@laptop01:~$ echo "$WORKER" | sudo tee /sys/fs/cgroup/testgroup/cgroup.procs
Controlla che sia stato aggiunto correttamente:
frank@laptop01:~$ cat /sys/fs/cgroup/testgroup/cgroup.procs
Visualizziamo il limite massimo della CPU al 100%
frank@laptop01:~$ cat /sys/fs/cgroup/testgroup/cpu.max
max 100000
Usare pidstat per visualizzare l'uso della CPU al 100%
frank@laptop01:~$ pidstat -p "$WORKER" 1
Limitiamo l’utilizzo della CPU al 50%
frank@laptop01:~$ echo "50000 100000" | sudo tee /sys/fs/cgroup/testgroup/cpu.max
frank@laptop01:~$ cat /sys/fs/cgroup/testgroup/cpu.max
max 50000 100000
Usare nuovamente pidstat per visualizzare l'uso della CPU al 50%
frank@laptop01:~$ pidstat -p "$WORKER" 1
INFO: monitorare i processi con htop
- Premi F5 per attivare la Tree View (vista ad albero)
- Premi F3 e cerca NOME_PROCESSO (esempio: stress) per visualizzare solo i processi interessati
- Premi F2 -> Setup -> Display options per migliorare la leggibilità:
- Tree View (se non attiva)
- Show custom thread names
- Highlight program path
Per interrompere il test uccidere il processo padre
Il processo padre a sua volta terminerà automaticamente il processo figlio (worker) e ripulirà le risorse.frank@laptop01:~$ pstree -p "$PARENT"
stress(8338)───stress(8343)
frank@laptop01:~$ kill "$PARENT"
frank@laptop01:~$ pstree -p "$PARENT"
Questo è il comportamento preferibile perché il processo padre elimina correttamente i suoi worker senza lasciarli orfani.
Un cgroup può essere rimosso solo se è vuoto, cioè non contiene processi (cgroup.procs vuoto) e non ha cgroup figli. In caso contrario il comando rmdir fallisce.
I file presenti all’interno di una directory cgroup non sono file reali ma interfacce del kernel esposte tramite il filesystem virtuale cgroupfs. Per questo motivo non possono essere eliminati con rm. Il comando rmdir non cancella i singoli file: chiede al kernel di eliminare il cgroup, operazione che il kernel consente solo quando il cgroup non contiene processi né cgroup figli.
In cgroups le directory rappresentano entità logiche del kernel, non semplici cartelle: la loro eliminazione è governata dallo stato interno del kernel.
frank@laptop01:~$ ls /sys/fs/cgroup/testgroup
frank@laptop01:~$ sudo rmdir /sys/fs/cgroup/testgroup/child1
frank@laptop01:~$ sudo rmdir /sys/fs/cgroup/testgroup
Gestione dei cgroup con systemd
Automazione, persistenza e uso in produzione
Persistenza dopo il riavvio
Quando i cgroup vengono gestiti manualmente tramite /sys/fs/cgroup (cgroupfs), le configurazioni non sono persistenti. Al riavvio del sistema il kernel ricrea da zero la gerarchia dei cgroup e tutti i limiti impostati manualmente come cpu.max o memory.max vengono persi.
Per ottenere limiti persistenti dopo il reboot è consigliato utilizzare systemd che gestisce i cgroup dei servizi in modo automatico e salva la configurazione su disco.
Gestione dei cgroups con Systemd
Con systemd non è necessario spostare manualmente i processi nei cgroup. Systemd crea automaticamente i cgroup dei servizi, abilita i controller necessari e assegna al cgroup tutti i processi del servizio inclusi i processi figli (worker).
systemd inserisce nel relativo cgroup sia il processo principale sia i processi figli (worker).
Systemd crea, abilita i controller e assegna automaticamente i processi al cgroup giusto.
systemd organizza i processi in slice:- system.slice: servizi del sistema
- user.slice: processi user space
- machine.slice: macchine virtuali e container
frank@laptop01:~$ systemd-cgls
Il processo padre e tutti i processi worker finiscono già nel cgroup corretto non serve usare echo PID > cgroup.procs. Con systemd non devi più spingere i processi dentro un cgroup: systemd li raggruppa automaticamente in base al servizio.
Esempio pratico: crond.service
Il servizio crond è ideale per i laboratori sui cgroup: è sempre presente, non è critico, permette rollback immediato e non impatta il funzionamento del sistema.
Systemd crea automaticamente il cgroup in/sys/fs/cgroup/system.slice/crond.service
Tutti i processi di crond inclusi eventuali worker vengono inseriti automaticamente in questo cgroup:
frank@laptop01:~$ systemd-cgls /system.slice/crond.service
CGroup /system.slice/crond.service:
└─4656 /usr/sbin/crond -n
Limitare la CPU di crond.service
frank@laptop01:~$ sudo systemctl set-property crond.service CPUQuota=25%
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/cpu.max
Limitare la memoria di crond.service
frank@laptop01:~$ sudo systemctl set-property crond.service MemoryMax=100M
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/memory.max
Limitare il numero di processi di crond.serviceIn systemd un task rappresenta un processo (inclusi processo principale e processi figli) e TasksMax definisce il numero massimo di processi che un servizio può creare all’interno del proprio cgroup.
frank@laptop01:~$ sudo systemctl set-property crond.service TasksMax=10
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/pids.max
Monitorare i processi nel cgroup
frank@laptop01:~$ systemd-cgls /system.slice/crond.service
5952
Restart del servizio e verifica
A differenza della gestione manuale tramite cgroupfs systemd salva le impostazioni su disco, ricrea automaticamente i cgroup al boot e riapplica i limiti ai servizi.
I file di configurazione vengono salvati in /etc/systemd/system.control/crond.service.d/ quindi dopo un riavvio del sistema o del servizio i limiti risultano ancora attivi.
Con systemd i cgroup diventano parte integrante della gestione dei servizi: i processi vengono assegnati automaticamente al cgroup corretto e i limiti sulle risorse risultano persistenti e sicuri.
Anche dopo il restart del servizio i limiti di risorse restano attivi e continuano ad essere applicati senza variazioni:frank@laptop01:~$ sudo systemctl restart crond.service
frank@laptop01:~$ systemd-cgls /system.slice/crond.service
5952
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/cpu.max
25000 100000
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/memory.max
104857600
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/pids.max
10
frank@laptop01:~$ ls /etc/systemd/system.control/crond.service.d/
50-CPUQuota.conf 50-MemoryMax.conf 50-TasksMax.conf
In systemd i limiti di risorse sono proprietà del servizio. Riavviare il servizio riavvia i processi ma non modifica il cgroup né i limiti associati.
In systemd i limiti di utilizzo delle risorse non sono associati ai singoli processi (PID) ma all’unità di servizio (*.service). Quando vengono configurati limiti come CPUQuota, MemoryMax o TasksMax systemd li applica al cgroup del servizio e non al processo che in quel momento lo esegue.
Il riferimento system.slice/crond.service è un identificatore logico usato da systemd: il cgroup reale esiste ed è ispezionabile solo sotto /sys/fs/cgroup/system.slice/crond.service.
Questo comportamento è evidente osservando il servizio crond.service. Prima del riavvio del servizio il cgroup conteneva il processo:
└─4656 /usr/sbin/crond -n
Dopo l’esecuzione del comando systemctl restart crond.service systemd ha terminato il processo esistente e ne ha avviato uno nuovo:
└─5952 /usr/sbin/crond -n
Il PID è cambiato perché il processo è stato ricreato ma il cgroup /system.slice/crond.service è rimasto lo stesso e ha continuato a mantenere invariati tutti i limiti di risorse configurati.
Questo dimostra che in systemd i limiti di CPU, memoria e numero di processi non seguono i PID ma sono legati al servizio. Riavviare un servizio sostituisce i processi ma il cgroup e i suoi limiti restano invariati.
Rollback dei limiti
Per rimuovere i limiti e tornare allo stato iniziale:frank@laptop01:~$ sudo systemctl set-property crond.service CPUQuota=
frank@laptop01:~$ sudo systemctl set-property crond.service MemoryMax=
frank@laptop01:~$ sudo systemctl set-property crond.service TasksMax=
Anche dopo il rollback dei limiti systemd mantiene i file di override nella directory system.control:
frank@laptop01:~$ cd /etc/systemd/system.control/crond.service.d
frank@laptop01:/etc/systemd/system.control/crond.service.d$ ls
50-CPUQuota.conf 50-MemoryMax.conf 50-TasksMax.conf
Tuttavia, la presenza di questi file non implica che i limiti siano ancora attivi, ciò che conta davvero è il valore effettivamente applicato al cgroup del servizio. Se si desidera rimuovere completamente gli override creati da systemctl set-property è possibile eliminare manualmente la directory del servizio, ricaricare la configurazione di systemd e riavviare il servizio:
frank@laptop01:~$ sudo rm -rf /etc/systemd/system.control/crond.service.d/
frank@laptop01:~$ sudo systemctl daemon-reload
frank@laptop01:~$ sudo systemctl restart crond.service
Il modo corretto per verificare se un limite di risorse è ancora attivo non è controllare i file di configurazione in /etc/systemd/system.control/crond.service.d/ ma ispezionare direttamente il cgroup associato al servizio:
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/cpu.max
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/memory.max
frank@laptop01:~$ cat /sys/fs/cgroup/system.slice/crond.service/pids.max


