Premier Training & Business Partner Red Hat

CGroups: cosa sono e come sfruttarli in RHEL 7

EXTRAORDY
Ti piacerebbe diventare anche tu uno di noi e
pubblicare i tuoi articoli nel blog degli RHCE italiani?

INTRODUZIONE AI CGROUPS

15152936_10211558047733676_473516973_o

Durante i corsi ufficiali Red Hat capita spesso di parlare di systemd, il nuovo system and service manager introdotto in RHEL/CentOS dalla versione 7, in Debian 8 (Jessie) e ormai molto diffuso in numerose distribuzioni GNU/Linux. In questi momenti può capitare di parlare anche di Control Groups (CGroups) e quello che noto è che molto spesso non si sa bene di cosa si tratti. E così ho pensato, in questa fredda serata invernale, con una bella cioccolata calda vicino, di scrivere questo articolo con lo scopo di dare un’introduzione al tema e spiegare come questi vengono sfruttati da systemd per la gestione delle risorse.

I Control Groups sono una feature del kernel la cui creazione è iniziata nel 2006 ad opera di ingegneri Google (principalmente Paul Menage e Rohit Seth). Il nome iniziale era “process containers”, successivamente rinomitato nel 2007 in Control Groups per evitare confusione con in Linux Containers. Il merge nel kernel Linux è avvenuto con la versione 2.6.24 e in seguito diverse nuove features sono state aggiunte.

La prima domanda che verrebbe da porre a questo punto è: si, ma a cosa servono?
Semplicemente servono a gestire le risorse della macchina (CPU, memoria, network I/O, disk I/O, ecc) e a profilare la loro allocazione ai processi in esecuzione.
In modo molto semplificato possiamo vederli come collezioni di processi accomunati da criteri e limiti comuni sulle risorse.
Facciamo un esempio: abbiamo un server LAMP con Apache, PHP e MySQL e vogliamo profilare l’uso della cpu in modo tale che Apache allochi al massimo il 60% e MySQL il 40%. Con i CGroups questo non solo è possibile ma è anche molto semplice da fare.
Altro esempio: su un hypervisor in cui eseguo N macchine virtuali posso andare a limitare l’uso di risorse in base al tipo di lavoro che fanno le VM. Immaginiamo uno scenario in cui ho due database, uno davvero davvero mission critical e l’altro a priorità molto più bassa. Potrei giustamente desiderare che l’I/O assegnato al mio db mission critical abbia sempre una soglia minima garantita superiore al db a bassa priorità. Posso assegnare alla prima VM un maggiore I/O thoughput e limitare quello dell’altra.
Infine, anche i Linux Container utilizzano i CGroups per profilare l’uso di risorse. Con Docker possiamo creare un container e passare opzioni come la seguente:

# docker run -d --cpu-shares=20 --memory=1024 --name myapache docker.io/httpd

Un comando del genere avvia un container di nome myapache con un limite di uso cpu del 20% e 1G di memoria. Sotto al cofano anche qui stiamo usando i CGroups.

In pratica i CGroups ci permettono di decidere, per i vari processi o servizi, quante risorse (CPU, memoria, disk I/O) allocare.

Qual’è la differenza con la priorità dei processi (nice)? Qui andiamo a profilare nel dettaglio l’allocazione delle risorse e soprattutto possiamo assegnare dei settings specifici non solo ad un singolo processo ma anche a gruppi ordinati gerarchicamente!

Andiamo ora più nel dettaglio. I CGroups sono disponibili da RHEL 6 ma la loro gestione è fortemente cambiata in RHEL 7 con l’arrivo di Systemd. Questo articolo verterà maggiormente sull’integrazione con Systemd in RHEL/CentOS 7.

 

Primo assioma: i CGroups sono organizzati in modo gerarchico.

Anche systemd gestisce le sue unit in modo gerarchico creando di default crea un serie di slice, scope e service units.
I tre tipi di unit citati vengono utilizzati in ambito di resource control. Nello specifico:

  • Service Units. Sono le unit che più comunemente vediamo associate ai demoni. Possono consistere in un singolo processo o in un gruppo di più processi (vedasi httpd, ad esempio). Le service unit sono gestite tramite file ini di configurazione residenti sotto /usr/lib/systemd/system in RHEL/CentOS.
  • Scope Units. Sono gruppi di processi creati esternamente. Ad esempio le macchine virtuali e i container creati, così come le sessioni utente, vengono registrati da systemd come scope units.
  • Slice Units. Le slice unit sono gruppi di unit organizzate in modo gerarchico. Conterranno solo service o scope units i quali, a loro volta, faranno riferimento ai processi veri e propri.Per visuallizzare queste gerarchie possiamo usare il comando seguente, che mostra un elenco paginato tramite less:
# systemd-cgls

Se si vuole un output non paginato:

# systemd-cgls --no-pager

Il comando, eseguito su una vm di test da un output simile al seguente:

-.slice
├─init.scope
| └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
├─user.slice
│ └─user-0.slice
│ └─session-1.scope
│ ├─ 1602 sshd: root@pts/0
│ ├─ 1606 -bash
│ └─22580 systemd-cgls --no-pager
└─system.slice
 ├─docker-1eed085f00fc5b08cc5580fbb1a6d11670bd65bc65e7f87873992a441064ba75.scope
 │ ├─22284 httpd -DFOREGROUND
 │ ├─22302 httpd -DFOREGROUND
 │ ├─22303 httpd -DFOREGROUND
 │ └─22304 httpd -DFOREGROUND
 ├─systemd-logind.service
 │ └─21888 /usr/lib/systemd/systemd-logind
 ├─systemd-journald.service
 │ └─21887 /usr/lib/systemd/systemd-journald
 ├─httpd.service
 │ ├─2555 /usr/sbin/httpd -DFOREGROUND
 │ ├─2556 /usr/sbin/httpd -DFOREGROUND
 │ ├─2557 /usr/sbin/httpd -DFOREGROUND
 │ ├─2558 /usr/sbin/httpd -DFOREGROUND
 │ ├─2559 /usr/sbin/httpd -DFOREGROUND
 │ └─2560 /usr/sbin/httpd -DFOREGROUND
 ├─tuned.service
 │ └─1359 /usr/bin/python -Es /usr/sbin/tuned -l -P
 ├─postfix.service
 │ ├─ 1517 /usr/libexec/postfix/master -w
 │ ├─ 1519 qmgr -l -t unix -u
 │ └─21868 pickup -l -t unix -u
 ├─docker.service
 │ └─1357 /usr/bin/docker-current daemon --exec-opt native.cgroupdriver=systemd --selinux-enabled --log-driver=journald
 ├─sshd.service
 │ └─1356 /usr/sbin/sshd -D
 ├─wpa_supplicant.service
 │ └─842 /usr/sbin/wpa_supplicant -u -f /var/log/wpa_supplicant.log -c /etc/wpa_supplicant/wpa_supplicant.conf -u -f /var/log/wpa_supplicant.log -P /var/run/wpa_supplicant....
 ├─polkit.service
 │ └─840 /usr/lib/polkit-1/polkitd --no-debug
 ├─NetworkManager.service
 │ ├─ 822 /usr/sbin/NetworkManager --no-daemon
 │ └─21920 /sbin/dhclient -d -q -sf /usr/libexec/nm-dhcp-helper -pf /var/run/dhclient-eth0.pid -lf /var/lib/NetworkManager/dhclient-1ea15ee5-2861-4824-b44f-ee6d4912bebe-eth...
 ├─crond.service
 │ └─693 /usr/sbin/crond -n
 ├─atd.service
 │ └─685 /usr/sbin/atd -f
 ├─dbus.service
 │ └─648 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
 ├─smartd.service
 │ └─647 /usr/sbin/smartd -n -q never
 ├─abrt-oops.service
 │ └─638 /usr/bin/abrt-watch-log -F BUG: WARNING: at WARNING: CPU: INFO: possible recursive locking detected ernel BUG at list_del corruption list_add corruption do_IRQ: st...
 ├─abrtd.service
 │ └─637 /usr/sbin/abrtd -d -s
 ├─rngd.service
 │ └─632 /sbin/rngd -f
 ├─libstoragemgmt.service
 │ └─631 /usr/bin/lsmd -d
 ├─firewalld.service
 │ └─629 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid
 ├─rsyslog.service
 │ └─627 /usr/sbin/rsyslogd -n
 ├─openvswitch.service
 │ ├─717 ovsdb-server: monitoring pid 718 (healthy)
 │ ├─718 ovsdb-server /etc/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --private-key=db:Open_vSwitch,SSL,private...
 │ ├─735 ovs-vswitchd: monitoring pid 736 (healthy)
 │ └─736 ovs-vswitchd unix:/var/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall --no-chdir --log-file=/var/log/openvswitch/ovs-vswitchd.log --pid...
 ├─auditd.service
 │ └─603 /sbin/auditd -n
 ├─systemd-udevd.service
 │ └─488 /usr/lib/systemd/systemd-udevd
 ├─lvm2-lvmetad.service
 │ └─473 /usr/sbin/lvmetad -f
 └─system-getty.slice
 └─getty@tty1.service
 └─701 /sbin/agetty --noclear tty1 linux

Quattro slice sono sempre create di default:

  • -.slice: la root slice
  • system.slice: la slice dove di default vengono allocati tutte le service unit
  • user.slice: che conterrà ulteriori slice per i vari utenti e scope per le relative sessioni.
  • machine.slice: non presente in questo esempio, che contiene virtual machine e container lxc (mentre i docker container, vengono creati di default come sotto system.slice).

Sotto le slice vediamo varie scope units e service units. Ad esempio lo scope  docker-1eed085f00fc5b08cc5580fbb1a6d11670bd65bc65e7f87873992a441064ba75.scope è relativo ad un container creato poco prima di eseguire il comando. All’interno dello scope possiamo visualizzare i processi httpd che girano all’interno del container.

La slice unit user-0.slice è figlia di user.slice e contiene le sessioni per l’utente root (UID=0). Al suo interno troviamo una scope unit relativa alla sessione ssh con cui sono loggato sulla vm.

Secondo assioma: slice, service scope units sono gestiti come CGroups.

Ora una parte del quadro è più chiara ma resta una domanda. Come possiamo controllare l’allocazione delle risorse per questi control group?
Tramite resource controllers, noti anche come cgroup subsystems.
I resource controller rappresentato specifiche risorse e permettono di impostare una serie di valori su determinati parametri predefiniti. Vengono esposti dal kernel e montati automaticamente da systemd sotto /sys/fs/cgroup.

Un ls -al in questa directory mostra il seguente output:

[root@dockerlab cgroup]# ls -al
total 0
drwxr-xr-x. 12 root root 280 Nov 16 21:06 .
drwxr-xr-x. 6 root root 0 Nov 16 21:06 ..
drwxr-xr-x. 5 root root 0 Nov 16 21:06 blkio
lrwxrwxrwx. 1 root root 11 Nov 16 21:06 cpu -> cpu,cpuacct
lrwxrwxrwx. 1 root root 11 Nov 16 21:06 cpuacct -> cpu,cpuacct
drwxr-xr-x. 5 root root 0 Nov 16 21:06 cpu,cpuacct
drwxr-xr-x. 3 root root 0 Nov 16 21:06 cpuset
drwxr-xr-x. 5 root root 0 Nov 17 11:54 devices
drwxr-xr-x. 3 root root 0 Nov 16 21:06 freezer
drwxr-xr-x. 3 root root 0 Nov 16 21:06 hugetlb
drwxr-xr-x. 5 root root 0 Nov 16 21:06 memory
drwxr-xr-x. 3 root root 0 Nov 16 21:06 net_cls
drwxr-xr-x. 3 root root 0 Nov 16 21:06 perf_event
drwxr-xr-x. 5 root root 0 Nov 16 21:06 systemd

Ogni directory rappresenta uno specifico resource controller. Tutti quelli disponibili su RHEL 7 sono:

  • blkio: imposta limiti di I/O sui block device
  • cpu: regola l’accesso dei task alla CPU. Montato assieme a cpuacct.
  • cpuacct: crea report delle risorse CPU utilizzate dai task. Montato assieme a cpu.
  • cpuset: assegna singole CPU e memory node ai task
  • devices: regola l’accesso dei task ai device
  • freezer: regola la sospensione e il resume di task
  • memory: regola i limiti di uso di memoria e genera report sull’utilizzo
  • net_cls: applica dei tag ai pacchetti (classid) per permettere di identificare i pacchetti originati da uno specifico task.
  • perf_event: permette di fare monitoraggio tramite il tool perf.
  • hugetlb: regola l’utilizzo di huge pages da parte dei vari task.

 

RESOURCE MANAGEMENT CON SYSTEMD

Completata la panoramica su cgroup e resource controller vediamo ora come creare un cgroup tramite systemd.
Abbiamo la possibilità di creare cgroup transienti, ovvero non durevoli, o persistenti, la cui configurazione viene salvata in uno unit file da systemd.

Possiamo creare CGroup transienti con il comando systemd-run. In questo modo viene creata una unit temporanea che verrà rimossa nel momento in cui il servizio viene stoppato.

La sintassi per creare unit transienti è la seguente:

systemd-run --unit=name [--scope] --slice=slice_name command

L’opzione –unit permette di impostare il nome del service/scope.
L’opzione –scope è opzionale. Se si omette verrà creata una service unit.
L’opzione –slice permette di definire sotto quale slice deve essere gerarchicamente allocata la unit.
L’argomento command definisce il processo da eseguire.

Facciamo un esempio:

systemd-run --unit=test_checksum --slice=system.slice md5sum /dev/zero

Il seguente comando crea una unit transiente test_checksum.service che esegue il processo md5sum.

Possiamo verificare che il processo sta effettivamente girando con il comando ps. Inoltre possiamo vedere lo stato della unit:

[root@dockerlab system]# systemctl status test_cheksum.service 
● test_cheksum.service - /usr/bin/md5sum /dev/zero
 Loaded: loaded (/run/systemd/system/test_cheksum.service; static; vendor preset: disabled)
 Drop-In: /run/systemd/system/test_cheksum.service.d
 └─50-Description.conf, 50-ExecStart.conf, 50-Slice.conf
 Active: active (running) since Thu 2016-11-17 20:43:20 CET; 3min 49s ago
 Main PID: 23038 (md5sum)
 Memory: 124.0K
 CGroup: /system.slice/test_cheksum.service
 └─23038 /usr/bin/md5sum /dev/zero

Nov 17 20:43:20 dockerlab.traininglab.com systemd[1]: Started /usr/bin/md5sum /dev/zero.
Nov 17 20:43:20 dockerlab.traininglab.com systemd[1]: Starting /usr/bin/md5sum /dev/zero...

Per disabilitare e rimuovere la unit e il relativo cgroup transiente appena create è sufficiente il comando.

# systemctl stop test_checksum.service

 

Veniamo finalmente alla parte più interessante, ovvero come assegnare impostazioni custom per le varie risorse.
Per svolgere queste operazioni si può usare l’ormai nota utilty systemcl con il verbo set-property.

# systemctl set-property [--runtime] unit_name property=value

Dove la coppia property value corrisponde al limite che vogliamo definire. Per vedere tutte le possibili property assegnabili possiamo leggere la pagina man relativa:

# man systemd.resource-control

Supponiamo ad esempio di voler limitare l’uso di CPU e Memoria per una unit, magari quella che abbiamo eseguito prima, test_checksum.service.

systemctl set-property --runtime test_checksum.service CPUAccounting=true
systemctl set-property --runtime test_checksum.service CPUQuota=20%
systemctl set-property --runtime test_checksum.service MemoryAccouning=true
systemctl set-property --runtime test_checksum.service MemoryLimit=128M

L’opzione –runtime attiva le modifiche a runtime. Le property utilizzate, descritte anche nella man page di cui sopra, attivano nell’ordine:

  • CPU usage accounting per la unit specificata. Necessario per impostare le quote.
  • CPU Quota, in cui viene settato un rapporto del 20%.
  • Memory Accounting, necessario per impostare il limite di memoria
  • Memory Limit, che fissa un hard limit di 128M all’utilizzo di memoria per questo cgroup.

Qualcuno si starà ora ponendo una domanda interessante. Se posso fare questo su un cgroup (slice, service o scope) e dato che una VM è registrata come scope unit, posso limitare l’uso di CPU per una VM? Certo che si.

Ad esempio, se voglio limitare l’uso di CPU per la VM “dockerlab” usata in questo articolo:

systemctl set-property 'machine-qemux2d1x2ddockerlab.scope' CPUAccounting=true
systemctl set-property 'machine-qemux2d1x2ddockerlab.scope' CPUQuota=25%

Abbiamo impostato così un hard limit per l’uso di CPU della VM. Questo può essere davvero molto utile per profilare l’uso di risorse di varie istanze di macchine virtuali.

Con la sintassi descritta possiamo profilare le risorse per unit persistenti, ad esempio un database MariaDB o Apache. Le modifiche verranno salvate sotto /etc/systemd/system/<name>.service.d/

Si possono anche passare stringhe property=value direttamente nello unit file sotto /usr/lib/systemd/system/<name>.service

Il seguente esempio imposta lo unit file del servizio httpd.service in cui è stato impostato limite di 600K/s per le scritture sotto la directory /var/log:

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true
MemoryLimit=1G
BlockIOWriteBandwidth=/var/log 600K

[Install]
WantedBy=multi-user.target

L’approccio descritto finora utilizza le utility di systemd per svolgere le oprazioni di tuning. Possiamo operare più a basso livello direttamente i resource controllers montati sotto /sys/fs/cgroup.

 

ESPLORIAMO /sys/fs/cgroup

Abbiamo detto precedentemente che in RHEL/CentOS 7 ogni resource controller è montato sotto questo path. Il comando mount ce lo può confermare:

# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime,seclabel)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,seclabel,size=498156k,nr_inodes=124539,mode=755)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,seclabel)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,seclabel,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,seclabel,mode=755)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
configfs on /sys/kernel/config type configfs (rw,relatime)
/dev/mapper/centos-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
selinuxfs on /sys/fs/selinux type selinuxfs (rw,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=31,pgrp=1,timeout=300,minproto=5,maxproto=5,direct)
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime,seclabel)
mqueue on /dev/mqueue type mqueue (rw,relatime,seclabel)
/dev/vda1 on /boot type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
tmpfs on /run/user/0 type tmpfs (rw,nosuid,nodev,relatime,seclabel,size=101676k,mode=700)

Il seguente esempio mostra il contentuto di del resource controller cpu:

# pwd
/sys/fs/cgroup/cpu
# ls -l
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.clone_children
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.procs
-r--r--r--. 1 root root 0 Nov 18 11:13 cgroup.sane_behavior
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.stat
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_user
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_user
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.shares
-r--r--r--. 1 root root 0 Nov 18 11:13 cpu.stat
drwxr-xr-x. 2 root root 0 Nov 18 11:13 init.scope
drwxr-xr-x. 3 root root 0 Nov 18 11:13 machine.slice
-rw-r--r--. 1 root root 0 Nov 18 11:13 notify_on_release
-rw-r--r--. 1 root root 0 Nov 18 11:13 release_agent
drwxr-xr-x. 2 root root 0 Nov 18 11:13 system.slice
-rw-r--r--. 1 root root 0 Nov 18 11:13 tasks
drwxr-xr-x. 2 root root 0 Nov 18 11:13 user.slice

I vari file rappresentano i possibili parametri impostabili per questo resource controller. In grassetto sono evidenziate delle directory: corrispondono agli scope e slice descritti sopra. Al loro interno si ripetono gli stessi file di configurazione che possiamo andare a customizzare. Le modifiche verranno propagate in modo gerarchico ai cgroup figli.

Poiché è presente anche lo slice machine.slice qualcuno potrà dedurre che su questa macchina gira un hypervisor kvm. Siamo infatti sulla macchina su cui gira la VM dell’esempio precedente, “dockerlab”.

# cd /sys/fs/cgroup/cpu/machine.slice
# ls -l
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.clone_children
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.procs
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.stat
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_user
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_user
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.shares
-r--r--r--. 1 root root 0 Nov 18 11:13 cpu.stat
drwxr-xr-x. 4 root root 0 Nov 18 10:24 'machine-qemux2d1x2ddockerlab.scope'
-rw-r--r--. 1 root root 0 Nov 18 11:13 notify_on_release
-rw-r--r--. 1 root root 0 Nov 18 11:13 tasks

Ecco qui lo scope relativo alla nostra VM. All’interno troviamo qualcosa di interessante:

# cd 'machine-qemux2d1x2ddockerlab.scope'
# ls -l
total 0
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.clone_children
-rw-r--r--. 1 root root 0 Nov 18 11:13 cgroup.procs
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.stat
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_percpu_user
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_sys
-r--r--r--. 1 root root 0 Nov 18 11:13 cpuacct.usage_user
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Nov 18 11:13 cpu.shares
-r--r--r--. 1 root root 0 Nov 18 11:13 cpu.stat
drwxr-xr-x. 2 root root 0 Nov 18 11:13 emulator
-rw-r--r--. 1 root root 0 Nov 18 11:13 notify_on_release
-rw-r--r--. 1 root root 0 Nov 18 10:24 tasks
drwxr-xr-x. 2 root root 0 Nov 18 11:13 vcpu0

Precedentemente avevamo impostato CPUQuota=25% tramite systemctl. Possiamo trovare qui confermata questa impostazione:

# cat cpu.cfs_quota_us
25000

L’output non è in percentuale ma in un valore intero da 0 a 100000.
L’ovvia conseguenza è che si può fare tuning anche passando un valore direttamente da qui:

# echo 50000 > cpu.cfs_quota_us

Questo singolo comando imposterà la CPUQuota della VM al 50%.

Questa VM ha una singola vcpu assegnata, vcpu0 e la directory corrispondente rappresenta il cgroup relativo. Questo cosa significa? Che se abbiamo diverse vcpu assegnate alla macchina virtuale possiamo andare a profilare il carico per ognuna di esse!

Proviamo ad esempio a settare per vpu0 un valore più basso:

# cd vpu0
 # echo 20000 > cpu.cfs_quota_us

Si può ora riscontrare, ad esempio con il comando top eseguito sull’host, che l’hard limit per l’uso di CPU per la macchina virtuale è ora del 20%, poiché ho una sola vcpu assegnata.

cgroup_top_example

Per chi non lo sapesse, su un sistema che esegue un hypervisor QEMU/KVM processi qemu-system-x86 o qemu-kvm, a seconda delle distribuzioni, corrispondono alle istanze delle macchine virtuali in esecuzione e come si può vedere l’unico presente rispetta il limite appena impostato.

E su RHEL/CentOS 6? Non essendo disponibile systemd l’approccio è abbastanza diverso. Il servizio di default per gestire i cgroups è cgconfig, installato con il package libcgroup (deprecato in RHEL 7 e sconsigliato, a meno che non si voglia andare a configurare alcuni controller non ancora supportati in systemd come net_prio).
Le gerarchie, i mount point dei controller e le profilature, vengono definiti nel file /etc/cgconfig.conf.
Per impostare parametri sui controller si può usare l’utility cgset:

cgset parameter=value path_to_cgroup

Mentre per avviare un processo sotto un cgroup abbiamo l’utility cgexec:

cgexec controllers:path_to_cgroup command arguments

Per una lettura più dettagliata in merito all’uso dei cgroups in RHEL 6 al Resource Management Guide di Red Hat:
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/

 

CONCLUSIONI

Con questo articolo abbiamo appena iniziato a scalfire la superficie con alcuni esempi. vi invitiamo ad approfondire e testare ulteriori funzionalità.

Maggiori informazioni sui parametri impostabili su ogni singolo resource controller possono essere trovate installando il pacchetto kernel-doc. La documentazione, in formato .txt, relativa ai cgroups verrà installata sotto /usr/share/doc/kernel-doc-<version>/Documentation/cgroups

I CGroups sono uno strumento davvero efficace quando si tratta di fare performance tuning e da questo punto di vista systemd, al centro spesso di controversie, si integra a mio avviso in modo semplice ed efficace, e lasciando comunque un certo margine di manovra. Personalmente trovo soprattutto la possibilità di gestiore VM come scope units in modo trasparente una feature davvero utile e interessate.
Anche l’uso dei CGroups da parte di Docker è un aspetto tutto da esplorare, soprattutto se si lavora su cluster che contengono container scalabili orizzonatalmente. Questo è esattamente quello che avviene in un cluster Kubernetes o OpenShift: impostando resource quota sui pod di fatto stiamo agendo sui relativi cgroups, come abbiamo visto prima in un esempio manuale.

Buona divertimento con i CGroups!

Info about author

EXTRAORDY