- Els contenidors a Linux es basen en namespaces per aïllar el que els processos poden veure i en cgroups per limitar quants recursos poden utilitzar.
- Hi ha diferents tipus de namespaces (PID, NET, MNT, UTS, IPC, USER, cgroup, time) i controladors de cgroup (CPU, memory, I/O, pids) que es combinen segons el cas.
- Docker, Kubernetes i systemd es recolzen en aquestes primitives del nucli per crear entorns aïllats, aplicar polítiques de recursos i gestionar serveis de forma uniforme.
- Monitoritzar mètriques de cgroups i comprendre la topologia de namespaces és essencial per diagnosticar OOMs, problemes de rendiment i assegurar un aïllament sòlid en producció.
Si treballes amb Docker, Kubernetes o qualsevol plataforma de contenidors, tard o d'hora et toca baixar als budells del sistema. Darrere d'aquesta “màgia” de llançar contenidors en segons hi ha dues peces clau del nucli de Linux: namespaces i cgroups, el duet responsable de l'aïllament i la gestió fina de recursos . Entendre'ls marca la diferència entre ser només usuari d'eines i ser qui dissenya la infraestructura.
Al llarg d'aquest article desgranarem, amb calma però sense enrotllar-nos de més, com funcionen els namespaces (allò que un procés pot veure) i els cgroups (allò que un procés pot utilitzar); com els aprofiten Docker i Kubernetes; com pots trastejar amb ells a baix nivell amb ordres com unshare, ip netns o escrivint directament a /sys/fs/cgroup; i quins trucs són útils per a rendiment, seguretat i resolució de problemes en entorns de producció.
Què fa realment un contenidor a Linux
Quan executes alguna cosa com docker run, no es crea una màquina virtual màgica; el que obtens és un procés de Linux (o un grup de processos) amb aïllament fort i recursos limitats. El “truc” es basa en quatre mecanismes del nucli:
- Espais de noms: controlen què veu el procés (PIDs, xarxa, sistema de fitxers, hostname, etc.).
- Grups C: controlen quant pot utilitzar el procés (CPU, RAM, I/O, PIDs…).
- Chroot / rootfs aïllat: donen al procés un sistema de fitxers propi.
- Processos normals de Linux: al final tot es redueix a PIDs del host gestionats pel nucli.
En altres paraules, un contenidor no és més que un procés executant-se amb un conjunt de namespaces nous, encaixat en un o diversos cgroups i amb un sistema de fitxers “enganxat” a mida . La resta la posa l'eina (Docker, containerd, runc, etc.) amb automatització i API còmodes.
Namespaces de Linux: aïllar el que cada procés pot veure
Els namespaces són una capa d'abstracció del nucli que crea “móns” separats per a grups de processos . Un procés només veu els recursos que són dins del seu namespace; la resta del sistema hi continua, però per a ell és invisible.
Linux compta actualment amb diversos tipus de namespaces, cadascun enfocat a una part concreta del sistema. Combinats, aconsegueixen que un contenidor es percebi com un sistema operatiu independent encara que comparteixi nucli amb el host.
Principals tipus de namespaces
Cada tipus de namespace aïlla una dimensió diferent. L'habitual en un contenidor seriós és usar-ne diversos alhora. Aquests són els més importants:
- PID namespace: aïlla l'espai d'identificador de procés. Dins del contenidor, el procés principal sol ser PID 1, encara que al host tingui un altre número. Els processos de diferents namespaces PID poden tenir el mateix PID sense destorbar-se, i no es poden enviar senyals entre si llevat que el host ho permeti explícitament.
- Network (NET) namespace: crea una pila de xarxa independent, amb les seves pròpies interfícies, rutes, regles de tallafocs, sockets, etc. Cada contenidor té el seu loopback, els seus IPs i el seu routing. Així, podeu reutilitzar la mateixa adreça IP en diferents namespaces sense conflicte i aplicar regles d'iptables aïllades.
- Mount (MNT) namespace: separa la vista dels punts de muntatge. Els processos només veuen la jerarquia de fitxers del seu namespace, encara que a sota hi hagi bind mounts, volums compartits, overlays, etc. És el que permet que un contenidor sembli tenir un “/” propi sense tocar el root real del host.
- UTS namespace: controla l'hostname i el domini NIS. Dins un UTS namespace, pots canviar el nom de host sense afectar el sistema amfitrió. Molt útil per distingir contenidors o simular nodes en entorns de proves.
- IPC namespace: aïlla cues de missatges System V, semàfors i memòria compartida. Evita que processos de contenidors diferents es trepitgin la IPC per accident o mal disseny.
- Espai de noms d'usuari: probablement el més delicat. Permet mapejar UIDs i GIDs interns del namespace a altres IDs del host. Això fa possible que el “root” dins del contenidor estigui mapejat a un usuari sense privilegis al host, reduint molt el risc d'escalada de privilegis i millorant la seguretat en contenidors.
- Cgroup namespace: dóna una vista virtualitzada de
/proc/self/cgroupi de la jerarquia de cgroups. Des de dins del contenidor només es veuen els seus propis grups, el que amaga l'estructura real del host i reforça l'aïllament. - Time namespace: permet oferir percepcions diferents del temps (rellotges com CLOCK_MONOTONIC o CLOCK_BOOTTIME) per a diferents grups de processos. És útil per a escenaris de proves o aïllament molt fi.
Tots aquests namespaces s'exposen a /proc/<pid>/ns/, i es poden crear o unir amb eines com unshare y nsenter. En morir el darrer procés que pertany a un namespace, aquest namespace es destrueix automàticament.
Trastejar amb namespaces des de la línia d'ordres
la comanda unshare és un dels bàsics per experimentar: crea un procés fill amb un o diversos namespaces nous. Per exemple, per provar un PID namespace aïllat:
unshare --pid --fork --mount-proc bash
ps aux
echo $$
Dins només veuràs els processos d'aquest namespace i el teu intèrpret d'ordres apareixerà com a PID 1 allà dins, encara que el host segueixi tenint tots els seus processos de sempre. És bàsicament l'esquelet del que farà un runtime OCI quan arrenca un contenidor.
Namespaces de xarxa detalladament
La part de xarxa és de les que dóna més joc. En crear un NET namespace amb ip netns add, obtens una pila buida llevat de per la loopback. A partir d´aquí pots:
- Connectar aquest namespace al host amb un parell
veth(dues interfícies virtuals unides per un cable lògic). - Crear bridges tipus
br0i enganxar diversos namespaces a aquest pont perquè es vegin entre si amb IPs privades. - Fer NAT i forwarding per donar sortida a Internet a aquests “contenidors casolans” amb iptables i
/proc/sys/net/ipv4/ip_forward.
És així, a un nivell molt baix, el que fa Docker amb el bridge docker0 i les seves xarxes per defecte: munta namespaces de xarxa, parells veth, bridges i regles NAT perquè els contenidors es parlin entre ells i surtin cap a fora.
Mount i user namespaces per a rootfs i seguretat
Amb el mount namespace pots jugar a crear un sistema de fitxers de contenidor sense fer servir Docker. El patró típic és:
- Muntar o preparar un rootfs mínim (per exemple, una Alpine minirootfs descarregada per
wgeti extreta en un directori). - Crear un mount namespace i fer
pivot_rootochroota aquest directori. - muntar
/proc,/sysi altres pseudo-sistemes dins del nou root.
Amb això, el teu shell veurà només aquest sistema de fitxers, però segueixes sent un procés normal del host, amb el seu PID real i controlable per l'administrador. Si a més uses un user namespace amb --map-root-user, podràs tenir un “root” dins que fora és un usuari sense privilegis.
Cgroups: quant recurs pot gastar cada procés
Si els namespaces decideixen el que un procés pot veure, els cgroups delimiten quants recursos del sistema pot consumir un conjunt de processos . Es van introduir al nucli 2.6.24 i avui són imprescindibles a qualsevol plataforma de contenidors moderna.
A nivell conceptual, un cgroup és un directori dins /sys/fs/cgroup amb fitxers que descriuen límits, prioritats i mètriques. Quan afegiu un PID al fitxer adequat (per exemple, cgroup.procs), aquest procés queda sotmès a aquestes regles.
Controladors de recursos a cgroups
Els cgroups es componen de controladors (subsystems) que actuen sobre recursos específics. Entre els més usats estan:
- CPU: controla temps de CPU assignat. Amb accions defineix prioritat relativa; amb quotes marques límits durs (per exemple, 50 ms de CPU per cada 100 ms de període); amb cpusets fixes processos a nuclis concrets.
- Memòria: limita RAM i swap. Permet límits durs i suaus, configuració de l'OOM killer, estadístiques i pressió de memòria. Molt útil per evitar que un servei es mengi el servidor sencer.
- Block I/O (blkio/io): restringeix amplada de banda i IOPS per dispositiu. Pots assignar pes proporcional o establir límits exactes en bytes/so ops/s.
- Xarxa: hi ha mecanismes per limitar ample de banda i paquets, encara que el suport està menys madur i sol combinar-se amb tc i QoS.
- PID: marca el màxim nombre de processos/fils que pot crear un grup, mitigant fork bombs o errors de codi que disparen fils.
La idea pràctica és molt directa: col·loques la teva aplicació en un cgroup i li marques CPU, memòria, I/O i PIDs màxims . A partir d'aquí, el nucli s'encarrega de fer complir les regles.
cgroups v1 vs cgroups v2
En producció et trobaràs dos sabors de cgroups, i convé tenir clar en quin terreny trepitges:
- cgroups v1: cada controlador té la seva jerarquia independent. Tens rutes com
/sys/fs/cgroup/cpu/...,/sys/fs/cgroup/memory/..., Etc És flexible però complicat de gestionar a gran escala. Moltes distros i plataformes clàssiques encara ho usen o ho suporten per compatibilitat. - cgroups v2: agrupa tot sota una jerarquia unificada en
/sys/fs/cgroup. Els límits es configuren via fitxers comcpu.max,memory.max,io.maxi similars. Simplifica la gestió i millora el control fi de recursos. Distribucions modernes tendeixen a utilitzar v2 per defecte, de vegades amb mode híbrid.
En /proc/cgroups pots veure quins controladors estan habilitats, quants cgroups actius hi ha i si estàs a v1 o v2 (a v2 veuràs una jerarquia unificada amb ID 0). L'ordre mount | grep cgroup també et xiva el tipus de sistema de fitxers (cgroup2fs implica v2, tmpfs sol apuntar a v1).
Creació manual de cgroups i límits típics
Per entendre bé què fa Docker per sota, val la pena crear cgroups a mà. El patró general és:
- Crear un directori de cgroup.
- Configurar fitxers de límit/prioritat (CPU, memòria, I/O, pids…).
- Afegir PIDs al cgroup escrivint a
cgroup.procsotasks(en v1).
Per exemple, a cgroups v2, podries tenir alguna cosa així per a CPU i memòria:
mkdir /sys/fs/cgroup/test_app
echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control
echo "50000 100000" > /sys/fs/cgroup/test_app/cpu.max
echo "512M" > /sys/fs/cgroup/test_app/memory.max
echo $$ > /sys/fs/cgroup/test_app/cgroup.procs
Amb això, el procés actual queda limitat al 50% d'un core i 512 MB de RAM. Si arrenques una mica intensiu (un dd a /dev/null, per exemple), veuràs que el sistema no es desmadra. A cgroups v1, l'esquema és semblant però amb fitxers tipus cpu.shares, cpu.cfs_quota_us, memory.limit_in_bytes, etc., cadascun en la seva jerarquia.
Com s'apilen Docker, Kubernetes i el nucli
Per sobre de namespaces i cgroups hi ha diverses capes de programari que es van passant la pilota fins a arribar al nucli. Encara que canvieu el nom de l'eina, el final del viatge és sempre el mateix: trucades al nucli per crear namespaces, configurar cgroups i muntar sistemes de fitxers.
El “stack” típic de contenidors avui dia s'assembla molt a això:
- Capa d'orquestració: Kubernetes, Docker Swarm, Nomad… S'encarreguen del cicle de vida de pods/serveis, scheduling, escalat i suporten arquitectures de microserveis.
- Container runtime d'alt nivell: Docker Engine, containerd, CRI-O. Reben les ordres de l'orquestrador i les converteixen en accions concretes (descarregar imatges, crear contenidors, gestionar snapshots, etc.).
- Runtime OCI de baix nivell: runc, cru i similars. Implementen l'especificació OCI i són els que directament criden a
clone(),unshare(),mount()i companyia per materialitzar el contenidor. - nucli Linux: proporciona namespaces, cgroups, OverlayFS i tota la resta de primitius. És on realment passa la “màgia”.
Quan llences un docker run, el recorregut és: client CLI → dockerd → containerd → runc → trucades al nucli. En aquest camí es creen els namespaces, s'assignen cgroups, es munta el rootfs amb OverlayFS més volums i s'enganxen les interfícies de xarxa i el bridge. El que veus com “un contenidor” és, per al nucli, un grapat de processos amb certs flags i rutes especials.
Requisits i comprovacions prèvies al sistema
Abans de posar-te a jugar dur amb namespaces i cgroups en un servidor, va bé assegurar-se que el maquinari i el nucli compleixen uns mínims raonables , sobretot si penses muntar alguna cosa semblant a producció.
Com a referència, solen recomanar-se almenys 2 CPU lògiques, 4 GB de RAM (millor 8 GB o més) , uns 20 GB lliures en disc i un nucli 3.10 o superior (idealment 4.xo 5.x per tenir totes les floritures de cgroups v2 i features moderns).
Per validar que el kernel suporta namespaces i cgroups només cal revisar el fitxer de configuració de la versió en ús:
grep -E "CONFIG_.*_NS" /boot/config-$(uname -r)
grep -E "CONFIG_CGROUP" /boot/config-$(uname -r)
Aquí haurien d'aparèixer opcions com CONFIG_NAMESPACES, CONFIG_PID_NS, CONFIG_NET_NS, CONFIG_CGROUPS, CONFIG_MEMCG, CONFIG_CGROUP_SCHED marcades com =y. Sense això, poc joc tindràs.
Un altre detall pràctic és si el sistema permet crear user namespaces sense ser root, una cosa molt útil per a proves i algunes eines. Això es controla amb el sysctl kernel.unprivileged_userns_clone; si està a 0, hauràs d'activar-lo (o estirar sudo a tot arreu).
Explorant cgroups i namespaces en màquines “reals”
Una forma força directa d'interioritzar aquests conceptes és mirar com els fa servir el propi sistema i els teus contenidors . Algunes pistes útils:
ls /proc/self/ns/t'ensenya els namespaces a què pertany el teu intèrpret d'ordres actual (cgroup, ipc, mnt, net, pid, user, uts…).systemd-cgtopmostra l'ús de CPU, memòria i I/O per cgroup, molt semblant atopperò agrupant per serveis i contenidors.- En
/sys/fs/cgrouppots localitzar els cgroups que Docker o Podman creen per a cada contenidor, normalment amb l'ID del contenidor a la ruta. Dins,cgroup.procsllista els PID associats. /proc/<pid>/cgroupt'indica a quins cgroups està adscrit un procés concret, perfecte per seguir la pista a una app problemàtica.
Veuràs que els contenidors no són més que una altra branca de la jerarquia de cgroups, amb els seus límits de CPU, memòria, blkio i companyia, tal com tu podries crear-los a mà . La gràcia de Docker és que ho fa automàticament i amb una API agradable.
Rendiment, NUMA i estratègies d'afinitat
En escenaris d'alta càrrega (bases de dades grosses, processament intensiu, streaming, etc.), no n'hi ha prou amb “aixecar contenidors i ja”. La manera com assignes CPU i memòria a cada servei marca moltíssim el rendiment.
Amb el controlador cpuset pots fixar processos o serveis a conjunts concrets de CPU i nodes NUMA. Una cosa tan simple com:
mkdir /sys/fs/cgroup/cpuset/dedicated_cpus
echo "4-7" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.mems
echo 1 > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpu_exclusive
permet reservar un grup de cors per a un servei concret i evitar que la resta del sistema es fiqui pel mig. Això redueix el soroll, les migracions de fils i els problemes de latència que solen aparèixer quan tothom lluita per les mateixes CPUs.
A nivell de memòria, combinar numactl amb cgroups (o bé utilitzar directament els knobs NUMA del controlador de memòria quan estiguin disponibles) ajuda que les dades es quedin al node físic més proper als cors que les usen. A apps molt sensibles a latència, es nota.
Monitorització i observabilitat sobre cgroups i namespaces
Ficar límits de recursos està bé, però si no mesures el que passa et pots enganxar trets al peu sense assabentar-te'n. El propi cgroup v2 ofereix força mètrica útil als arxius *.stat, *.events y *.pressure.
Alguns exemples pràctics:
cpu.statet diu quant de temps de CPU ha fet servir el grup, quantes vegades ha estat throttled, etc.memory.currentymemory.maxmostren consum actual i límit configurat.memory.eventsregistra OOMs, soft limits aconseguits i altres ensurts.io.statacumula lectures/escriptures per dispositiu.
Per a una vista ràpida, systemd-cgtop és molt pràctic, i si vols alguna cosa més industrial, eines com cAdvisor exporten mètriques de cgroups de Docker/containerd a Prometheus, de manera que pots muntar dashboards complets a Grafana sense gaire esforç, ideal per a un monitor de sistema avançat.
A l'hora de depurar problemes de namespaces, scripts senzills que recorrin /proc/<pid>/ns/ i comparin els enllaços simbòlics ajuden a saber quins processos comparteixen exactament el mateix namespace, oa localitzar un namespace de xarxa “perdut” que s'ha quedat penjat.
Ús de systemd com a gestor de cgroups
En la majoria de distros modernes, systemd es recolza de forma intensiva en cgroups per gestionar serveis, slices i scopes . Quan defineixes una unitat amb límits de recursos, en realitat el que fa systemd és crear un cgroup per a aquest servei i configurar els seus controladors.
Un servei declarat amb directives com:
CPUQuota=50%
CPUWeight=500
MemoryMax=512M
IOReadBandwidthMax=/dev/sda 10M
acaba traduït en límits de cgroup sobre CPU, memòria i I/O per al procés daquesta unitat. Això permet alinear la gestió “clàssica” de serveis amb el model de contenidors, usant el mateix subsistema del nucli per sota. Per a més detalls sobre systemd i els seus requisits, consulteu canvis de systemd.
Per als equips que barregen serveis tradicionals amb contenidors (molt habitual), recolzar-se en systemd per marcar límits a dimonis crítics és una forma elegant d' evitar que es matin recursos amb els pods de l'entorn containeritzat.
En entorns on conviuen diverses càrregues, des de bases de dades fins a agents de monitorització i aplicacions a mida, dominar namespaces i cgroups permet definir polítiques d'aïllament forts, ajustar la qualitat de servei i garantir que cada peça té la “parcel·la” de CPU i memòria ben controlada . Entendre com Docker, Kubernetes i systemd exploten aquestes capacitats del nucli t'arma per dissenyar infraestructures multiinquilí més segures, depurar problemes difícils (des d'OOMs fins a colls d'ampolla d'I/O) i treure el màxim profit al maquinari sense perdre estabilitat.