
Photo by Aan Amrin from Pexels
Hoe werken containers?
cgroups, capabilites, seccomp, overlayfs & namespaces
Een container geeft de mogelijkheid om een applicatie in een geïsoleerde omgeving te draaien. Zo'n container bevat alles wat nodig is om de applicatie uit te voeren: de executable, de libraries, de configuratiebestanden en de data-bestanden. Daarmee zorgt een container voor een consistent en reproduceerbaar eco-systeem, ongeacht de inrichting van de host machine waarop de container wordt uitgevoerd en is een container daarmee in hoge mate portabel. Gecontaineriseerde applicaties kunnen zo probleemloos draaien op verschillende besturingssystemen en infrastructuren, zoals ontwikkelomgevingen, test-machines, on-premise datacenters en cloud-platforms.
In tegenstelling tot virtuele machines zijn containers onder Linux light-weight. Het starten van een gecontaineriseerde applicatie kost nauwelijks meer tijd dan het starten van een native applicatie. En dat is niet verwonderlijk, want een gecontaineriseerde applicatie ís feitelijk een native applicatie.
Kernel mechanismen
Er zijn inmiddels vele container runtime implementaties, zoals Docker, Podman of Kubernetes (gebaseerd op bijv. containerd of CRI-O). Deze implementaties maken allemaal gebruik van dezelfde mechanismen die geboden worden door de Linux kernel. Hierbij kun je denken aan:
- Kernel namespaces waarmee de applicatie een eigen beeld voorgeschoteld krijgt van de filesysteem-structuur, het netwerk (interfaces en open poortjes), de hostnaam en een eigen PID nummering.
- Overlayfs en chroot/pivot_root waarmee de applicatie in een container de beschikking krijgt over een privé mini-filesysteem met eigen root-directory.
- Capabilities waarmee een beperkte set privileges worden toegekend aan de applicatie, zelfs als deze draait onder root-identiteit.
- Seccomp waarmee de set beschikbare system calls voor de applicatie begrensd kan worden en 'gevaarlijke' system calls niet worden toegestaan.
- Cgroups waarmee limieten en garanties voor bijv. CPU- en memory-gebruik kunnen worden afgedwongen.
Veel opties die je kunt meegeven bij de verschillende container implementaties zijn gerelateerd aan deze onderliggende mechanismen. Bij commando's als docker run
en podman run
kun je denken aan opties zoals --network=host
(kernel namespaces), --memory=200m
(cgroups) of --caps-add=sys_nice
(capabilities), of de vergelijkbare keywords die je gebruikt in een compose bestand.
En bij Kubernetes kun je denken aan vergelijkbare opties die je meegeeft in een Pod manifest:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
hostNetwork: True
containers:
- name: mycontainer
image:
resources:
limits:
memory: 200Mi
requests:
memory: 100Mi
securityContext:
capabilities:
add: ["sys_nice"]
seccompProfile:
type: Localhost
localHostProfile: ...
yaml
Kernel namespaces
Ieder (native) proces onder Linux is gekoppeld aan kernel namespaces. Deze kernel namespaces bepalen het beeld dat het proces heeft van zijn omgeving, zoals de hostnaam, de filesysteem-structuur en het netwerk. Je zou kunnen zeggen dat ieder proces in een Linux-systeem een VR-bril draagt waardoor elk proces een andere omgeving kan 'zien', terwijl het toch 'native' op de host draait. Feitelijk is een gecontaineriseerd proces niets anders dan een native proces dat een ander beeld geprojecteerd krijgt dan een 'native' native proces, zoals systemd en zijn afstammelingen. Door een andere hostnaam, filesysteem-structuur, netwerk, etcetera te zien, lijkt het alsof het proces in een VM draait. Als een proces gekoppeld wordt aan andere kernel namespaces dan de werkelijke native processen, zoals systemd, dan zeggen we dat zo'n proces in een container draait. Het is ook mogelijk dat een proces grotendeels dezelfde namespaces gebruikt als de 'native' native processen, maar zich bijvoorbeeld alleen koppelt aan een andere netwerk namespace. Dat laatste kun je beschouwen als een hybride container.
Met de standaard Linux-commando's unshare
en nsenter
kun je manipuleren met de kernel namespaces bij het starten van een proces. Zo kun je met het commando nsenter
een nieuw proces starten dat zich koppelt aan (een selectie van) de kernel namespaces van een reeds lopend proces. Defacto doe je hiermee hetzelfde als met de commando's docker exec ...
, podman exec ...
of kubectl exec ...
waarmee we kunnen inbreken in een lopende container. In dat geval kun je alleen beschikken over de commando's die in het mini-filesysteem van die container aanwezig zijn en dan zal blijken dat het commando dat je nodig hebt nou net altijd ontbreekt.
Een voorbeeld:
$ docker run -d nginx
664573c1dc3b0e2cf123cadd80b470c3e29a40c6c301eaa5d6c9cf312daa522e
$ docker exec -it 6645 bash
root@664573c1dc3b:/# ss -tln
bash: ss: command not found
root@664573c1dc3b:/# ip a
bash: ip: command not found
bash
Als je (zoals in dit voorbeeld) alleen de netwerk-omgeving van een container wilt inspecteren, kun je met het commando nsenter
een nieuwe shell starten die specifiek de netwerk-namespace van het gecontaineriseerde proces (-t PID
) 'entert' met de -n
optie:
$ ps -C nginx
PID TTY TIME CMD
4210 ? 00:00:00 nginx
$ sudo nsenter -n -t 4210 bash
# ss -tln
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
# ip a
....
bash
Je maakt in bovenstaand voorbeeld nog steeds gebruik van het filesysteem van de host met alle commando's die daarin geïnstalleerd zijn.
Cursus Containers Advanced
Benieuwd naar meer van dit soort tips en een gedegen inzicht in de werking van containers? In de cursus Containers Advanced worden alle voorzieningen van de Linux kernel belicht die een rol spelen bij het concept 'containers', zoals kernel namespaces, cgroups, overlayfs en seccomp. We gaan ook specifiek in op 'user namespaces' die het mogelijk maken om containers te draaien zonder dat je root-privileges op de host nodig hebt, zoals gebruikt door Podman. Aan het einde van ieder hoofdstuk belichten we de opties van met name Docker, Podman en Kubernetes die met dat onderwerp te maken hebben. Naast individuele practicumopgaven zullen we tijdens de cursusdag klassikaal een container bouwen op basis van losse Linux-commando's zonder daarbij Docker, Podman of een andere container-runtime te gebruiken. Hiermee vallen voor menig deelnemer de puzzelstukjes op z'n plek. Een reactie die we inmiddels meermalen hebben gehoord aan het einde van deze cursusdag: "Ik heb nu een compleet ander beeld van een container dan toen ik vanmorgen het cursuslokaal binnenliep!".
En dat zonder VR-bril...
$ blog-details
- $ categorie: Containers, Linux
- $ tools: Docker, Podman, Kubernetes, Linux Kernel
- $ date: 2025-10-14T10:00:00 CET
- $ cursussen:
Containers Advanced
Docker Fundamentals
Kubernetes Fundamentals