🎯 Introducción
Si crees que endurecer una imagen es suficiente para dormir tranquilo, te estás equivocando: si el contenedor comparte directamente el kernel del host, el riesgo sigue ahí. Con el runtime por defecto (runc), un contenedor no es más que un proceso extra en el host aislado mediante namespaces, cgroups y seccomp; esto significa que cualquier fallo de aislamiento en el kernel —un CVE de escalada de privilegios o un syscall mal filtrado— puede derivar en una fuga directa hacia el host y todos sus vecinos.
Para solucionar esto necesitamos aislamiento en runtime (sandboxing), una técnica que mete una capa de protección entre el contenedor y el kernel real. En este artículo vamos a centrarnos en las dos soluciones más maduras que existen actualmente: gVisor (un kernel que corre en espacio de usuario) y Kata Containers (contenedores que viven dentro de microVMs). No vamos a perder el tiempo repitiendo conceptos de hardening de imágenes, gestión de secrets o scanning; para eso, tienes que consultar Docker — Seguridad y Scanning, que es el complemento perfecto para lo que vamos a ver aquí.
¿Qué NO cubre este doc?
Hardening de Dockerfile, usuarios no-root, gestión de secretos y escaneo de vulnerabilidades. Todo eso vive en docker_security.md. Aquí hablamos solo de aislamiento del kernel en tiempo de ejecución.
🧠 El problema del kernel compartido
Con runc, todos los contenedores del host comparten un único kernel Linux. La superficie de ataque es enorme: cientos de syscalls, drivers, filesystems y subsistemas de red. seccomp reduce esa superficie, pero sigue siendo el mismo kernel para todos.

- gVisor intercepta las syscalls del contenedor y las sirve desde un kernel reimplementado en Go (el Sentry), que solo hace un puñado de syscalls reales al host.
- Kata arranca cada contenedor (o pod) dentro de una microVM ligera con su propio kernel invitado, aislado por virtualización de hardware.
🔍 gVisor (runsc)
gVisor implementa un kernel de aplicación en espacio de usuario. El proceso Sentry intercepta las syscalls del contenedor (vía ptrace o, por defecto, la plataforma systrap/KVM) y las procesa sin dárselas al kernel real. El acceso al filesystem pasa por un proceso separado, el Gofer, mediante el protocolo 9P.
Instalación
# Descargar runsc y su shim para containerd
(
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512 \
${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)
Configuración con Docker
# Registra runsc como runtime en el daemon de Docker
sudo runsc install
sudo systemctl reload docker
Esto añade el runtime a /etc/docker/daemon.json:
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
Ejecutar un contenedor con gVisor:
docker run --rm --runtime=runsc hello-world
# Comprobar que el kernel es el del Sentry, no el del host
docker run --rm --runtime=runsc alpine dmesg | head
# -> se ve "Starting gVisor..." en lugar del kernel real
Configuración con containerd
En /etc/containerd/config.toml:
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
sudo systemctl restart containerd
Depuración de gVisor
Activa logs por sandbox con runsc --debug --debug-log=/tmp/runsc/. Es la forma más rápida de detectar una syscall no soportada que rompe una aplicación.
🔍 Kata Containers (microVMs)
Kata arranca cada pod dentro de una VM ligera usando un hipervisor (QEMU por defecto, o Cloud Hypervisor / Firecracker). El contenedor ve un kernel invitado completo, de modo que el aislamiento lo garantiza la virtualización de hardware (VT-x/AMD-V), no solo el software.
Requisitos
# Verificar soporte de virtualización por hardware
kata-runtime check
# o, manualmente:
egrep -c '(vmx|svm)' /proc/cpuinfo # > 0 = soportado
Virtualización anidada
En VMs cloud, Kata con QEMU necesita nested virtualization habilitada por el proveedor. Sin ella, arranca por software (mucho más lento) o falla. Firecracker y Cloud Hypervisor tienen los mismos requisitos de KVM.
Instalación
# Vía kata-manager (script oficial que instala binarios + config)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/kata-containers/kata-containers/main/utils/kata-manager.sh)"
Los binarios quedan en /opt/kata/bin/ y la configuración en /opt/kata/share/defaults/kata-containers/configuration.toml.
Configuración con containerd
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata.options]
ConfigPath = "/opt/kata/share/defaults/kata-containers/configuration.toml"
sudo systemctl restart containerd
Prueba directa con ctr:
sudo ctr images pull docker.io/library/alpine:latest
sudo ctr run --runtime io.containerd.kata.v2 --rm \
docker.io/library/alpine:latest kata-test uname -r
# El kernel mostrado es el invitado de Kata, distinto al del host
Hipervisor más ligero
Para arranques rápidos y menor consumo, cambia a Cloud Hypervisor o Firecracker en configuration.toml ([hypervisor.clh] / [hypervisor.fc]). Firecracker prioriza densidad y tiempo de arranque; QEMU prioriza compatibilidad de dispositivos.
☸️ Integración en Kubernetes (RuntimeClass)
Ambos runtimes se exponen a Kubernetes mediante el recurso RuntimeClass. El handler debe coincidir con el nombre del runtime configurado en containerd.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc # coincide con [...runtimes.runsc]
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata # coincide con [...runtimes.kata]
Asignar un pod a un runtime sandboxeado:
apiVersion: v1
kind: Pod
metadata:
name: untrusted-workload
spec:
runtimeClassName: gvisor # o "kata"
containers:
- name: app
image: nginx:1.27-alpine
Nodos dedicados
Es habitual etiquetar/taintar nodos con el runtime instalado y dejar que el RuntimeClass los seleccione vía scheduling.nodeSelector/tolerations, para no exigir gVisor o Kata en todos los nodos del clúster.
# Fragmento de RuntimeClass con scheduling a nodos etiquetados
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
scheduling:
nodeSelector:
katacontainers.io/kata-runtime: "true"
tolerations:
- key: "kata"
operator: "Exists"
effect: "NoSchedule"
⚖️ Comparativa: runc vs gVisor vs Kata
| Criterio | runc (por defecto) | gVisor (runsc) | Kata Containers |
|---|---|---|---|
| Mecanismo de aislamiento | Namespaces + cgroups + seccomp | Kernel en userspace (Sentry) | microVM con kernel invitado propio |
| Superficie de syscall al host | Amplia (mitigable seccomp) | Muy reducida | Solo la del hipervisor |
| Kernel | Compartido con el host | Reimplementado (Go) | Kernel Linux invitado independiente |
| Overhead de arranque | Mínimo | Bajo | Medio (arranque de VM) |
| Overhead de I/O / syscalls | Nativo | Notable en I/O intensivo | Bajo-medio (virtio) |
| Compatibilidad de syscalls | Total | Parcial (subset implementado) | Total (kernel real) |
| Requiere virtualización HW | No | No | Sí (VT-x/AMD-V o nested) |
| Acceso a GPU / dispositivos | Directo | Limitado | Vía passthrough (más complejo) |
| Caso ideal | Cargas de confianza | Multi-tenant, código no confiable | Aislamiento fuerte tipo VM |
🧭 ¿Cuándo usar cada uno?
Regla práctica
- runc: cargas propias y de confianza donde el rendimiento manda.
- gVisor: ejecutar código no confiable o multi-tenant (funciones serverless, sandboxes de CI, ejecución de código de usuario) con arranque rápido, aceptando algo de overhead de I/O y compatibilidad parcial de syscalls.
- Kata: necesitas aislamiento equivalente a una VM, compatibilidad total de syscalls o cumplimiento normativo estricto, y puedes pagar el coste del hipervisor y exigir virtualización por hardware.
⚠️ Limitaciones
gVisor
- No implementa el 100% de las syscalls: aplicaciones que usan interfaces exóticas del kernel pueden fallar o degradarse.
- Overhead notable en cargas con mucha I/O de red o disco por la indirección del Sentry/Gofer.
- Soporte de GPU y dispositivos especiales limitado.
Kata Containers
- Requiere virtualización por hardware; en cloud,
nested virtualizationhabilitada. - Mayor consumo de memoria por microVM y arranque más lento que runc/gVisor.
- Passthrough de dispositivos (GPU, red SR-IOV) es más complejo de configurar.
🔗 Enlaces relacionados
- Docker — Seguridad y Scanning — hardening de imágenes, secrets y escaneo (complemento de esta guía).
- Docker — Base
- Docker — Optimizaciones
- gVisor — Documentación oficial
- Kata Containers — Documentación oficial
- Kubernetes — RuntimeClass
