352.1 Conceptos de Contenedores

Introducción

Los contenedores son una forma de virtualización a nivel de sistema operativo que permite ejecutar múltiples instancias aisladas sobre un mismo kernel. A diferencia de las máquinas virtuales, no requieren un hipervisor ni un sistema operativo completo por cada instancia.

Contenedores vs Máquinas Virtuales

    Máquinas Virtuales              Contenedores
┌───────┐ ┌───────┐           ┌───────┐ ┌───────┐
│ App A │ │ App B │           │ App A │ │ App B │
├───────┤ ├───────┤           ├───────┤ ├───────┤
│ Libs  │ │ Libs  │           │ Libs  │ │ Libs  │
├───────┤ ├───────┤           └───┬───┘ └───┬───┘
│Guest OS│ │Guest OS│              │         │
├───────┴─┴───────┤           ┌───┴─────────┴───┐
│   Hipervisor     │           │  Container Engine│
├──────────────────┤           ├─────────────────┤
│   Host OS        │           │   Host OS + Kernel│
├──────────────────┤           ├─────────────────┤
│   Hardware       │           │   Hardware       │
└──────────────────┘           └─────────────────┘
AspectoMáquinas VirtualesContenedores
AislamientoCompleto (kernel propio)Nivel de proceso (kernel compartido)
ArranqueMinutosSegundos/milisegundos
TamañoGB (SO completo)MB (solo app + dependencias)
RendimientoOverhead de hipervisorCasi nativo
DensidadDecenas por hostCientos/miles por host
SeguridadMayor (superficie de ataque reducida)Menor (kernel compartido)

Para el examen: Los contenedores comparten el kernel del host. Esto los hace más ligeros pero potencialmente menos seguros que las VMs. Un exploit en el kernel afecta a todos los contenedores.

Namespaces del Kernel Linux

Los namespaces proporcionan aislamiento de recursos del sistema. Cada contenedor tiene su propio conjunto de namespaces:

NamespaceFlagAísla
pidCLONE_NEWPIDÁrbol de procesos (PID 1 propio)
netCLONE_NEWNETInterfaces de red, rutas, firewall
mntCLONE_NEWNSPuntos de montaje del filesystem
utsCLONE_NEWUTSHostname y domainname
ipcCLONE_NEWIPCColas de mensajes, semáforos, memoria compartida
userCLONE_NEWUSERUIDs/GIDs (mapeo de usuarios)
cgroupCLONE_NEWCGROUPVista de cgroups
timeCLONE_NEWTIMEReloj del sistema (desde kernel 5.6)

Trabajar con namespaces

# Ver namespaces de un proceso
ls -la /proc/<PID>/ns/
 
# Crear un nuevo namespace de red
unshare --net bash
 
# Entrar en los namespaces de un contenedor existente
nsenter -t <PID> -n -p -m
 
# Listar todos los namespaces
lsns
 
# Ver namespace de un proceso específico
lsns -p <PID>

Para el examen: El namespace user es fundamental para contenedores rootless. Permite mapear el UID 0 dentro del contenedor a un UID sin privilegios en el host.

Cgroups (Control Groups)

Los cgroups limitan, contabilizan y aíslan el uso de recursos del sistema por grupos de procesos.

Cgroups v1

Estructura jerárquica con múltiples árboles independientes (uno por controlador):

/sys/fs/cgroup/
├── cpu/
│   └── docker/
│       └── <container-id>/
│           ├── cpu.shares
│           └── cpu.cfs_quota_us
├── memory/
│   └── docker/
│       └── <container-id>/
│           ├── memory.limit_in_bytes
│           └── memory.usage_in_bytes
├── blkio/
├── cpuset/
├── devices/
├── freezer/
├── net_cls/
└── pids/

Cgroups v2

Estructura unificada con un único árbol jerárquico:

/sys/fs/cgroup/
└── system.slice/
    └── docker-<container-id>.scope/
        ├── cgroup.controllers
        ├── cgroup.subtree_control
        ├── cpu.max
        ├── memory.max
        ├── memory.current
        ├── io.max
        └── pids.max
AspectoCgroups v1Cgroups v2
EstructuraMúltiples jerarquíasJerarquía única
ConfiguraciónUn montaje por controladorMontaje único unificado
DelegaciónComplejaSimplificada
Pressure Stall InfoNoSí (PSI)
Controlador memorymemory.limit_in_bytesmemory.max
Controlador CPUcpu.shares, cpu.cfs_quota_uscpu.max, cpu.weight

Recursos controlados por cgroups

ControladorRecurso
cpuTiempo de CPU (cuotas, pesos)
memoryMemoria RAM y swap
blkio/ioAncho de banda de E/S de disco
pidsNúmero máximo de procesos
cpusetAsignación de CPUs y nodos NUMA específicos
devicesAcceso a dispositivos
freezerCongelar/descongelar grupos de procesos
net_clsClasificación de tráfico de red
# Ver controladores disponibles (v2)
cat /sys/fs/cgroup/cgroup.controllers
 
# Limitar memoria de un cgroup (v2)
echo "512M" > /sys/fs/cgroup/mi-grupo/memory.max
 
# Ver uso actual de memoria
cat /sys/fs/cgroup/mi-grupo/memory.current
 
# Limitar CPU (v2): máximo 50% de un core
echo "50000 100000" > /sys/fs/cgroup/mi-grupo/cpu.max

Overlay Filesystems

OverlayFS

Sistema de archivos por capas que combina múltiples directorios en una vista unificada:

┌─────────────────────────┐
│    Merged View          │  ← Lo que ve el contenedor
├─────────────────────────┤
│    Upper Layer (RW)     │  ← Cambios del contenedor
├─────────────────────────┤
│    Lower Layer 1 (RO)  │  ← Capa de imagen
├─────────────────────────┤
│    Lower Layer 2 (RO)  │  ← Capa base
└─────────────────────────┘
# Montar overlayfs manualmente
mount -t overlay overlay \
  -o lowerdir=/lower1:/lower2,upperdir=/upper,workdir=/work \
  /merged

AUFS (Advanced Multi-Layered Unification Filesystem)

Predecesor de OverlayFS, ya en desuso en la mayoría de distribuciones. OverlayFS es el estándar actual incluido en el kernel Linux mainline.

Para el examen: OverlayFS es el storage driver predeterminado en Docker moderno. AUFS fue usado en versiones antiguas pero no está en el kernel mainline.

Container Runtimes

Arquitectura de capas

┌─────────────────────────────────────────┐
│  Alto nivel: Docker, Podman, LXC        │
├─────────────────────────────────────────┤
│  Runtime de alto nivel: containerd, CRI-O│
├─────────────────────────────────────────┤
│  Runtime de bajo nivel: runc, crun       │
├─────────────────────────────────────────┤
│  Kernel: namespaces, cgroups, seccomp    │
└─────────────────────────────────────────┘
RuntimeNivelDescripción
runcBajoRuntime OCI de referencia, escrito en Go
crunBajoRuntime OCI alternativo, escrito en C, más ligero y rápido
containerdAltoDemonio de gestión de contenedores (usado por Docker y Kubernetes)
CRI-OAltoRuntime optimizado para Kubernetes

Especificación OCI

La Open Container Initiative define tres especificaciones estándar:

1. Runtime Specification

Define cómo ejecutar un contenedor. Un “bundle” OCI contiene:

  • config.json: Configuración del contenedor (namespaces, mounts, proceso, etc.)
  • rootfs/: Sistema de archivos raíz

2. Image Specification

Define el formato de las imágenes de contenedor:

  • Manifest: Referencia a la configuración y capas
  • Configuration: Metadatos (CMD, ENV, puertos, etc.)
  • Layers: Capas del sistema de archivos (tarballs comprimidos)

3. Distribution Specification

Define cómo distribuir imágenes a través de registros (registries).

# Inspeccionar una imagen OCI
skopeo inspect docker://docker.io/library/nginx:latest
 
# Copiar imagen entre registros
skopeo copy docker://registry1/app:v1 docker://registry2/app:v1

Para el examen: OCI asegura la interoperabilidad entre diferentes herramientas de contenedores. Una imagen construida con Docker puede ejecutarse con Podman, y viceversa.

Seccomp (Secure Computing Mode)

Filtra las llamadas al sistema (syscalls) que un proceso puede realizar:

# Ver perfil seccomp por defecto de Docker
docker info | grep -i seccomp
 
# Ejecutar contenedor con perfil personalizado
docker run --security-opt seccomp=mi-perfil.json nginx
 
# Ejecutar sin restricciones seccomp (peligroso)
docker run --security-opt seccomp=unconfined nginx

Ejemplo de perfil seccomp (JSON):

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "stat"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Linux Capabilities

Las capabilities dividen los privilegios de root en unidades individuales:

CapabilityPermite
CAP_NET_ADMINAdministrar redes (interfaces, rutas, firewall)
CAP_NET_BIND_SERVICEVincular puertos privilegiados (<1024)
CAP_SYS_ADMINOperaciones de administración del sistema
CAP_SYS_PTRACETrazar procesos (debug)
CAP_CHOWNCambiar propietario de archivos
CAP_DAC_OVERRIDEIgnorar permisos de archivos
CAP_MKNODCrear dispositivos especiales
# Eliminar todas las capabilities y añadir solo las necesarias
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
 
# Ver capabilities de un proceso
getpcaps <PID>
 
# Ver capabilities de un contenedor
docker inspect --format='{{.HostConfig.CapAdd}}' <container>

Para el examen: Por defecto, los contenedores Docker se ejecutan con un subconjunto limitado de capabilities. Nunca deben ejecutarse con --privileged en producción.

Contenedores Rootless

Ejecutan el container engine y los contenedores sin privilegios de root en el host:

# Docker rootless
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
 
# Podman rootless (por defecto)
podman run nginx
 
# Verificar mapeo de usuarios
cat /etc/subuid
cat /etc/subgid

Requisitos para contenedores rootless:

  • Soporte de user namespaces en el kernel
  • /etc/subuid y /etc/subgid configurados
  • newuidmap y newgidmap disponibles

Resumen

ConceptoDetalle clave
NamespacesAislamiento de recursos (pid, net, mnt, uts, ipc, user, cgroup)
CgroupsLímites de recursos (CPU, memoria, E/S, PIDs)
Cgroups v2Jerarquía unificada, reemplaza v1
OverlayFSFilesystem por capas (lower RO + upper RW)
runc/crunRuntimes OCI de bajo nivel
containerdRuntime de alto nivel, usado por Docker y K8s
OCIEstándar de interoperabilidad (runtime, image, distribution)
SeccompFiltrado de syscalls
CapabilitiesPermisos granulares (sustituyen a root monolítico)
RootlessContenedores sin privilegios root en el host

Trampas del examen

Errores comunes y distinciones criticas que LPI suele evaluar en este subtema:

  • Contenedores comparten kernel, VMs no — Esta es la distincion fundamental. Un exploit en el kernel del host afecta a todos los contenedores pero no a VMs (que tienen kernel propio). El examen preguntara cual tecnologia ofrece mejor aislamiento de seguridad (respuesta: VMs).
  • Cgroups v1 vs v2: estructura de archivos — En v1, cada controlador tiene su propio directorio (/sys/fs/cgroup/cpu/, /sys/fs/cgroup/memory/). En v2, hay una unica jerarquia unificada (/sys/fs/cgroup/). Los nombres de los archivos cambian: v1 usa memory.limit_in_bytes, v2 usa memory.max. El examen puede dar una ruta y preguntar que version de cgroups es.
  • Namespaces aislan, cgroups limitan — Los namespaces proporcionan aislamiento (el contenedor no ve los procesos/red/filesystem del host). Los cgroups limitan recursos (cuanta CPU, RAM, E/S puede usar). Son complementarios, no alternativos. El examen puede intentar confundir sus funciones.
  • CLONE_NEWNS es el namespace de montajes, no de nombres — El flag CLONE_NEWNS corresponde al namespace mnt (mount), no a un “namespace de nombres”. Fue el primer namespace implementado, por eso tiene un nombre generico (NS). Esta trampa lingueistica es clasica en el examen.
  • User namespace es clave para rootless — El namespace user permite mapear UID 0 dentro del contenedor a un UID sin privilegios en el host. Sin user namespaces, los contenedores rootless no son posibles. /etc/subuid y /etc/subgid definen los rangos disponibles.
  • runc vs containerd vs Dockerrunc es el runtime de bajo nivel que crea los contenedores (namespaces, cgroups). containerd es el runtime de alto nivel que gestiona el ciclo de vida. Docker es la plataforma completa que incluye CLI, demonio y registros. El examen puede preguntar que componente interactua directamente con el kernel.
  • OverlayFS: lower es RO, upper es RW — Las capas inferiores (lower) son de solo lectura; la capa superior (upper) almacena los cambios (read-write). El contenedor ve la vista fusionada (merged). Si se elimina un archivo del lower, se crea un whiteout en el upper. El examen puede preguntar donde se almacenan los cambios del contenedor.
  • OCI garantiza interoperabilidad — Una imagen OCI construida con Docker puede ejecutarse con Podman, CRI-O o cualquier runtime compatible OCI. El examen puede preguntar si una imagen Docker solo funciona con Docker (respuesta: no, gracias a OCI).
  • Seccomp filtra syscalls, capabilities filtra privilegios — Seccomp decide QUE llamadas al sistema puede hacer un proceso. Capabilities decide QUE privilegios de root tiene. --cap-drop=ALL elimina todos los privilegios de root; seccomp=unconfined desactiva el filtrado de syscalls. Son mecanismos diferentes y complementarios.
  • --privileged desactiva TODAS las protecciones--privileged da al contenedor acceso completo al host: todas las capabilities, sin seccomp, acceso a todos los dispositivos. Nunca debe usarse en produccion. El examen puede preguntar que flag elimina el aislamiento de un contenedor.