{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata","title":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","description":"Deep dive en los internals de container runtimes, runc vs youki (Rust), kata-containers microVMs, sandboxing con gVisor, control de recurse es con cgroup v2 y aislamiento de namespaces con traces de syscalls reales.","language":"es","datePublished":"2025-12-19","dateModified":"2026-10-21","contentFormat":"text/markdown","content":"Containers\n\n# Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox\n\n2025-12-198 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/container-runtime-internals-runc-youki-kata>)\n\n## Qué sucede al iniciar un container\n\nCuando se ejecuta ` docker run -it ubuntu bash `, [Docker](<https://www.docker.com/>) llama a containerd, que llama a un proceso \"shim\", que llama al OCI runtime (runc por defecto). El OCI runtime es donde sucede la creación real del contenedor. Esta es la secuencia:\n\n```\ndocker run → dockerd → containerd → containerd-shim → runc create → runc start\n │\n ├── clone(CLONE_NEWNS | CLONE_NEWPID | ...)\n ├── mount rootfs\n ├── pivot_root\n ├── setup cgroups\n ├── apply seccomp filter\n ├── drop capabilities\n └── execve(\"/bin/bash\")\n```\n\nrunc en sí es un programa Go de ~15,000 líneas. Hace cinco cosas fundamentales:\n\n1. **Crea namespaces de Linux** para aislar la vista del sistema del contenedor\n2. **Configura un filesystem** (mount namespace + pivot\\_root)\n3. **Configura cgroups** para limitar uso de recursos\n4. **Aplica políticas de seguridad** (seccomp, capabilities, AppArmor/SELinux)\n5. **Ejecuta el proceso del contenedor**\n\nA continuación se examina cada uno.\n\n## Namespaces: la base del aislamiento\n\nLos namespaces de Linux particionan recurse es del kernel para que un set de procese es vea un set de recursos, y otro set vea uno diferente. Los containers típicamente usan 7 tipos de namespace:\n\n```c\n// Esto es esencialmente lo que runc hace con clone()\n#define _GNU_SOURCE\n#include <sched.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\nint child_fn(void *arg) {\n // Dentro del container, tenemos nuestro propio PID namespace\n printf(\"PID del Container: %d\\n\", getpid()); // Siempre 1\n sethostname(\"container\", 9);\n\n // Montar proc para el nuevo PID namespace\n mount(\"proc\", \"/proc\", \"proc\", 0, NULL);\n\n execve(\"/bin/sh\", (char*[]){\"/bin/sh\", NULL}, NULL);\n return 0;\n}\n\nint main() {\n char stack[1024 * 1024];\n\n int flags = CLONE_NEWNS // Namespace de montaje\n | CLONE_NEWPID // Namespace de PID\n | CLONE_NEWUTS // Namespace de hostname\n | CLONE_NEWNET // Namespace de red\n | CLONE_NEWIPC // Namespace de IPC\n | CLONE_NEWUSER // Namespace de usuario\n | CLONE_NEWCGROUP; // Namespace de cgroup\n\n pid_t pid = clone(child_fn, stack + sizeof(stack), flags | SIGCHLD, NULL);\n waitpid(pid, NULL, 0);\n return 0;\n}\n```\n\nEl user namespace es el más interesante para seguridad. Mapea el UID 0 (root) del container a un UID sin privilegios en el host:\n\n```bash\n# Verificar mapeo de user namespace\n$ cat /proc/<container-pid>/uid_map\n 0 100000 65536\n# UID 0 del container → UID 100000 del host\n```\n\nEsto significa que incluso si un proceso escapa del container como \"root\", en realidad es UID 100000 en el host, un usuario sin privilegios. Eso son los rootless containers en pocas palabras.\n\n## cgroup v2: control de recurse es bien hecho\n\ncgroup v1 era un problema complejo. Cada controlador de recurse es (cpu, memoria, io, pids) tenía su propia jerarquía, y un proceso podía estar en diferentes cgroups para diferentes controladores. cgroup v2 unifica todo en una sola jerarquía:\n\n```bash\n# Jerarquía unificada de cgroup v2\n/sys/fs/cgroup/\n├── system.slice/\n│ ├── docker-abc123.scope/\n│ │ ├── cgroup.controllers # \"cpu memory io pids\"\n│ │ ├── cpu.max # \"100000 100000\" (100% de 1 CPU)\n│ │ ├── memory.max # \"536870912\" (512MB)\n│ │ ├── memory.swap.max # \"0\" (sin swap)\n│ │ ├── io.max # \"8:0 rbps=104857600 wbps=52428800\"\n│ │ ├── pids.max # \"1024\"\n│ │ └── cgroup.procs # PIDs en este cgroup\n```\n\nLa característica más destacada de cgroup v2 es la información de pressure stall (PSI):\n\n```bash\n# ¿Cuánto tiempo están los tasks en este cgroup stalleados por recursos?\n$ cat /sys/fs/cgroup/system.slice/docker-abc123.scope/cpu.pressure\nsome avg10=4.52 avg60=2.18 avg300=1.05 total=182394\nfull avg10=0.00 avg60=0.00 avg300=0.00 total=0\n```\n\nPSI te dice *cuánto* un cgroup está afectado por contención de recursos, no solo si pegó un límite. Esto es dramáticamente más útil que la señal booleana de \"hicimos OOM\" de v1.\n\nAcá está cómo runc configura cgroups programáticamente:\n\n```go\n// Simplificado del setup de cgroups de runc\nfunc setupCgroupV2(cgroupPath string, resources *specs.LinuxResources) error {\n if err := os.MkdirAll(cgroupPath, 0755); err != nil {\n return err\n }\n\n // CPU: 50% de un core (50000us de un periodo de 100000us)\n if resources.CPU != nil {\n quota := fmt.Sprintf(\"%d %d\", *resources.CPU.Quota, *resources.CPU.Period)\n os.WriteFile(filepath.Join(cgroupPath, \"cpu.max\"), []byte(quota), 0644)\n }\n\n // Memoria: límite duro\n if resources.Memory != nil && resources.Memory.Limit != nil {\n limit := strconv.FormatInt(*resources.Memory.Limit, 10)\n os.WriteFile(filepath.Join(cgroupPath, \"memory.max\"), []byte(limit), 0644)\n os.WriteFile(filepath.Join(cgroupPath, \"memory.swap.max\"), []byte(\"0\"), 0644)\n }\n\n // Límite de PIDs, previene fork bombs\n if resources.Pids != nil {\n limit := strconv.FormatInt(resources.Pids.Limit, 10)\n os.WriteFile(filepath.Join(cgroupPath, \"pids.max\"), []byte(limit), 0644)\n }\n\n // Mover el proceso init del container a este cgroup\n return os.WriteFile(\n filepath.Join(cgroupPath, \"cgroup.procs\"),\n []byte(strconv.Itoa(containerPid)),\n 0644,\n )\n}\n```\n\nSon solo escrituras a archivos. Eso es cgroups, el kernel expone una interfaz de filesystem, y usted escribirs valores de configuración a archivos. Hermosamente simple, terriblemente poderoso.\n\n## youki: runc en Rust\n\nyouki es una reescritura de runc en Rust, y no es solo un proyecto de vanidad. Hay razones de ingeniería reales para que te importe:\n\n**Tiempo de arranque**: compará runtimes con la misma imagen, kernel, configuración de cgroups y ejecuciones repetidas. El [repositorio de youki](<https://github.com/youki-dev/youki>) es la referencia de implementación. Esta guía no incluye un benchmark reproducible de arranque.\n\n**Seguridad de memoria**: Rust puede prevenir categorías de errores de memoria en código seguro. No evita automáticamente fugas de descriptores ni escapes de contenedores. [CVE 2024 21626](<https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv>) involucra descriptores filtrados y requiere analizar el comportamiento del runtime, no deducir su seguridad del lenguaje.\n\n**Reemplazo drop-in**: youki pasa los tests completos de compliance del OCI runtime-spec:\n\n```bash\n# Reemplazar runc con youki en containerd\n# /etc/containerd/config.toml\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc]\n runtime_type = \"io.containerd.runc.v2\"\n [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n BinaryName = \"/usr/local/bin/youki\"\n```\n\nReiniciás containerd, y cada container en la máquina ahora usa youki. Hice esto en un cluster de staging hace tres meses y literalmente nadie se dio cuenta. Ese es el mejor tipo de cambio de infraestructura.\n\n## kata-containers: cuando los namespaces no alcanzan\n\nUna limitación fundamental de los containers estándar es que los namespaces son un mecanismo de aislamiento de *userspace*. Particionan la vista del kernel de los recursos, pero cada container comparte el mismo kernel. Un exploit de kernel en un container compromete todos los containers del host.\n\nkata-containers resuelve esto corriendo cada container dentro de una VM liviana (microVM):\n\n```\nContainer estándar:\n┌──────────┐ ┌──────────┐ ┌──────────┐\n│Container1│ │Container2│ │Container3│\n├──────────┴─┴──────────┴─┴──────────┤\n│ Kernel del Host │ ← Superficie de ataque compartida\n└─────────────────────────────────────┘\n\nkata-container:\n┌──────────┐ ┌──────────┐ ┌──────────┐\n│Container1│ │Container2│ │Container3│\n├──────────┤ ├──────────┤ ├──────────┤\n│ Kernel G │ │ Kernel G │ │ Kernel G │ ← Kernels separados\n├──────────┤ ├──────────┤ ├──────────┤\n│ VMM │ │ VMM │ │ VMM │\n├──────────┴─┴──────────┴─┴──────────┤\n│ Kernel del Host │\n└─────────────────────────────────────┘\n```\n\nEl overhead de rendimiento es real pero manejable. Para nuestra plataforma multi-tenant, usamos kata con Cloud Hypervisor para workloads de clientes y runc para nuestros servicios de infraestructura propios. La API RuntimeClass en Kubernetes lo hace trivial:\n\n```yaml\napiVersion: node.k8s.io/v1\nkind: RuntimeClass\nmetadata:\n name: kata-clh\nhandler: kata-clh\noverhead:\n podFixed:\n memory: \"60Mi\"\n cpu: \"50m\"\n---\napiVersion: v1\nkind: Pod\nmetadata:\n name: workload-no-confiable\nspec:\n runtimeClassName: kata-clh\n containers:\n - name: codigo-usuario\n image: customer-function:latest\n resources:\n limits:\n memory: \"256Mi\"\n cpu: \"500m\"\n```\n\n## gVisor: el término medio\n\ngVisor (runsc) toma un approach diferente. En vez de correr una VM completa, interpone un kernel de userspace que intercepta syscalls del proceso del container:\n\n```\nArquitectura gVisor:\n┌──────────────────┐\n│ App Container │\n├──────────────────┤\n│ Sentry │ ← Kernel userspace (intercepta syscalls)\n│ (implementa │\n│ ~380 syscalls) │\n├──────────────────┤\n│ Gofer │ ← Proxy de filesystem (sandboxeado)\n├──────────────────┤\n│ Kernel del Host │ ← Solo Sentry habla con el kernel del host\n└──────────────────┘\n```\n\nLos trade-offs comparados con kata:\n\n- **Startup**: gVisor agrega ~50ms (vs ~150ms de kata)\n- **Memoria**: gVisor usa ~15MB de overhead (vs ~40-80MB de kata)\n- **Compatibilidad**: gVisor no soporta todos los syscalls, apps que usan syscalls poco comunes (como io\\_uring) pueden no funcionar\n- **Rendimiento**: Workloads con muchos syscalls sufren un hit de rendimiento de 2-10x\n\n## seccomp: la última línea de defensa\n\nSin importar qué runtime uses, seccomp filtra las syscalls que un container puede hacer. El perfil seccomp default de Docker bloquea 44 syscalls de ~380+:\n\n```bash\n# Perfilá tu aplicación real para encontrar syscalls necesarias\n$ strace -c -f tu-aplicacion 2>&1 | tail -20\n% time seconds usecs/call calls errors syscall\n------ ----------- ----------- --------- --------- ----------------\n 35.42 0.182346 12 15234 write\n 22.18 0.114218 8 14028 read\n 8.91 0.045889 45 1020 io_uring_enter # ¡Necesitás esta!\n```\n\n## El árbol de decisión de runtimes\n\nDespués de correr los cuatro runtimes en producción, así decido:\n\n**runc**: Default para workloads internos de confianza. Bajo overhead, máxima compatibilidad, modelo de seguridad bien entendido. El 90% de los containers deberían correr en runc.\n\n**youki**: Reemplazo drop-in de runc cuando se desea mejor tiempo de startup y las garantías de memory safety de Rust. No hay razón para *no* usarlo si te soportás software más nuevo.\n\n**kata-containers**: Workloads no confiables que necesitan aislamiento a nivel VM. Plataformas multi-tenant, FaaS, ejecución de código de clientes. Aceptá los ~150ms de overhead de startup y ~40MB de overhead de memoria.\n\n**gVisor**: Cuando se necesita mejor aislamiento que runc pero no te es posible soportar el overhead de recurse es de kata. Bueno para procesamiento batch de datos no confiables, servidores web manejando input no confiable. Ojo con la compatibilidad de syscalls.\n\nEl ecosistema de containers está convergiendo en un modelo de seguridad por capas: namespaces para aislamiento básico, cgroups para límites de recursos, seccomp para filtrado de syscalls, y capas opcionales de VM/sandbox para defensa en profundidad. Entender estas capas, y saber qué perillas girar, es la diferencia entre containers que están \"probablemente bien\" y containers en los que apostarías tu empresa.\n\n## Preguntas frecuentes\n\n### ¿Cuál es la diferencia entre un container runtime y un container engine?\n\nEl container engine (Docker, Podman) maneja gestión de imágenes, networking y orquestación. El container runtime (runc, youki, kata) es el componente de bajo nivel que realmente crea y ejecuta el proceso del contenedor usando features del kernel Linux como namespaces y cgroups.\n\n### ¿youki está listo para producción?\n\nSí. youki pasa la suite completa de tests de compliance del OCI runtime-spec y se usa en producción en varias organizaciones. Es un reemplazo drop-in de runc con mejor rendimiento de startup y garantías de memory safety de Rust.\n\n### ¿Cuándo debería usar kata-containers en vez de runc?\n\nUtilizar kata-containers cuando se necesita aislamiento a nivel hardware para workloads no confiables, plataformas multi-tenant, ejecución de código enviado por usuarios, o requerimientos de compliance que exigen aislamiento de VM. El trade-off es ~150ms de tiempo de startup adicional y mayor overhead de memoria por contenedor.\n\n## Tips\n\n1. Evaluá compatibilidad y latencia de youki en tu entorno antes de reemplazar un runtime. Usar Rust no demuestra por sí solo el aislamiento de contenedores.\n2. **Tip:** Siempre habilitá cgroup v2 con ` systemd.unified_cgroup_hierarchy=1 `, la jerarquía por-recurso de v1 es una pesadilla para orquestación de contenedores y el árbol unificado de v2 es infinitamente más simple.\n3. **Tip:** kata-containers agregan ~150ms al startup del container pero te dan aislamiento a nivel de hardware. Usalos para workloads no confiables y runc estándar para servicios propios.\n4. **Tip:** Verificá tu perfil seccomp con ` strace -c ` en tu workload real antes de deployar, el perfil default de Docker bloquea 44 syscalls, y una de ellas podría ser la que tu app necesita.\n\n- containers\n- runc\n- youki\n- kata-containers\n- gvisor\n- cgroups\n- namespaces\n- oci\n- kubernetes\n- security\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#title"},{"level":2,"text":"Qué sucede al iniciar un container","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#qu-sucede-al-iniciar-un-container"},{"level":2,"text":"Namespaces: la base del aislamiento","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#namespaces-la-base-del-aislamiento"},{"level":2,"text":"cgroup v2: control de recurse es bien hecho","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#cgroup-v2-control-de-recurse-es-bien-hecho"},{"level":2,"text":"youki: runc en Rust","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#youki-runc-en-rust"},{"level":2,"text":"kata-containers: cuando los namespaces no alcanzan","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#kata-containers-cuando-los-namespaces-no-alcanzan"},{"level":2,"text":"gVisor: el término medio","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#gvisor-el-trmino-medio"},{"level":2,"text":"seccomp: la última línea de defensa","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#seccomp-la-ltima-lnea-de-defensa"},{"level":2,"text":"El árbol de decisión de runtimes","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#el-rbol-de-decisin-de-runtimes"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#questions"},{"level":3,"text":"¿Cuál es la diferencia entre un container runtime y un container engine?","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-1"},{"level":3,"text":"¿youki está listo para producción?","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-2"},{"level":3,"text":"¿Cuándo debería usar kata-containers en vez de runc?","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#article-tips"}],"sections":[{"heading":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","level":1,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata","content":"Containers"},{"heading":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","level":1,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#title","content":"2025-12-198 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/container-runtime-internals-runc-youki-kata>)"},{"heading":"Qué sucede al iniciar un container","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#qu-sucede-al-iniciar-un-container","content":"Cuando se ejecuta ` docker run -it ubuntu bash `, [Docker](<https://www.docker.com/>) llama a containerd, que llama a un proceso \"shim\", que llama al OCI runtime (runc por defecto). El OCI runtime es donde sucede la creación real del contenedor. Esta es la secuencia:\n\n```\ndocker run → dockerd → containerd → containerd-shim → runc create → runc start\n │\n ├── clone(CLONE_NEWNS | CLONE_NEWPID | ...)\n ├── mount rootfs\n ├── pivot_root\n ├── setup cgroups\n ├── apply seccomp filter\n ├── drop capabilities\n └── execve(\"/bin/bash\")\n```\n\nrunc en sí es un programa Go de ~15,000 líneas. Hace cinco cosas fundamentales:\n\n1. **Crea namespaces de Linux** para aislar la vista del sistema del contenedor\n2. **Configura un filesystem** (mount namespace + pivot\\_root)\n3. **Configura cgroups** para limitar uso de recursos\n4. **Aplica políticas de seguridad** (seccomp, capabilities, AppArmor/SELinux)\n5. **Ejecuta el proceso del contenedor**\n\nA continuación se examina cada uno."},{"heading":"Namespaces: la base del aislamiento","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#namespaces-la-base-del-aislamiento","content":"Los namespaces de Linux particionan recurse es del kernel para que un set de procese es vea un set de recursos, y otro set vea uno diferente. Los containers típicamente usan 7 tipos de namespace:\n\n```c\n// Esto es esencialmente lo que runc hace con clone()\n#define _GNU_SOURCE\n#include <sched.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\nint child_fn(void *arg) {\n // Dentro del container, tenemos nuestro propio PID namespace\n printf(\"PID del Container: %d\\n\", getpid()); // Siempre 1\n sethostname(\"container\", 9);\n\n // Montar proc para el nuevo PID namespace\n mount(\"proc\", \"/proc\", \"proc\", 0, NULL);\n\n execve(\"/bin/sh\", (char*[]){\"/bin/sh\", NULL}, NULL);\n return 0;\n}\n\nint main() {\n char stack[1024 * 1024];\n\n int flags = CLONE_NEWNS // Namespace de montaje\n | CLONE_NEWPID // Namespace de PID\n | CLONE_NEWUTS // Namespace de hostname\n | CLONE_NEWNET // Namespace de red\n | CLONE_NEWIPC // Namespace de IPC\n | CLONE_NEWUSER // Namespace de usuario\n | CLONE_NEWCGROUP; // Namespace de cgroup\n\n pid_t pid = clone(child_fn, stack + sizeof(stack), flags | SIGCHLD, NULL);\n waitpid(pid, NULL, 0);\n return 0;\n}\n```\n\nEl user namespace es el más interesante para seguridad. Mapea el UID 0 (root) del container a un UID sin privilegios en el host:\n\n```bash\n# Verificar mapeo de user namespace\n$ cat /proc/<container-pid>/uid_map\n 0 100000 65536\n# UID 0 del container → UID 100000 del host\n```\n\nEsto significa que incluso si un proceso escapa del container como \"root\", en realidad es UID 100000 en el host, un usuario sin privilegios. Eso son los rootless containers en pocas palabras."},{"heading":"cgroup v2: control de recurse es bien hecho","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#cgroup-v2-control-de-recurse-es-bien-hecho","content":"cgroup v1 era un problema complejo. Cada controlador de recurse es (cpu, memoria, io, pids) tenía su propia jerarquía, y un proceso podía estar en diferentes cgroups para diferentes controladores. cgroup v2 unifica todo en una sola jerarquía:\n\n```bash\n# Jerarquía unificada de cgroup v2\n/sys/fs/cgroup/\n├── system.slice/\n│ ├── docker-abc123.scope/\n│ │ ├── cgroup.controllers # \"cpu memory io pids\"\n│ │ ├── cpu.max # \"100000 100000\" (100% de 1 CPU)\n│ │ ├── memory.max # \"536870912\" (512MB)\n│ │ ├── memory.swap.max # \"0\" (sin swap)\n│ │ ├── io.max # \"8:0 rbps=104857600 wbps=52428800\"\n│ │ ├── pids.max # \"1024\"\n│ │ └── cgroup.procs # PIDs en este cgroup\n```\n\nLa característica más destacada de cgroup v2 es la información de pressure stall (PSI):\n\n```bash\n# ¿Cuánto tiempo están los tasks en este cgroup stalleados por recursos?\n$ cat /sys/fs/cgroup/system.slice/docker-abc123.scope/cpu.pressure\nsome avg10=4.52 avg60=2.18 avg300=1.05 total=182394\nfull avg10=0.00 avg60=0.00 avg300=0.00 total=0\n```\n\nPSI te dice *cuánto* un cgroup está afectado por contención de recursos, no solo si pegó un límite. Esto es dramáticamente más útil que la señal booleana de \"hicimos OOM\" de v1.\n\nAcá está cómo runc configura cgroups programáticamente:\n\n```go\n// Simplificado del setup de cgroups de runc\nfunc setupCgroupV2(cgroupPath string, resources *specs.LinuxResources) error {\n if err := os.MkdirAll(cgroupPath, 0755); err != nil {\n return err\n }\n\n // CPU: 50% de un core (50000us de un periodo de 100000us)\n if resources.CPU != nil {\n quota := fmt.Sprintf(\"%d %d\", *resources.CPU.Quota, *resources.CPU.Period)\n os.WriteFile(filepath.Join(cgroupPath, \"cpu.max\"), []byte(quota), 0644)\n }\n\n // Memoria: límite duro\n if resources.Memory != nil && resources.Memory.Limit != nil {\n limit := strconv.FormatInt(*resources.Memory.Limit, 10)\n os.WriteFile(filepath.Join(cgroupPath, \"memory.max\"), []byte(limit), 0644)\n os.WriteFile(filepath.Join(cgroupPath, \"memory.swap.max\"), []byte(\"0\"), 0644)\n }\n\n // Límite de PIDs, previene fork bombs\n if resources.Pids != nil {\n limit := strconv.FormatInt(resources.Pids.Limit, 10)\n os.WriteFile(filepath.Join(cgroupPath, \"pids.max\"), []byte(limit), 0644)\n }\n\n // Mover el proceso init del container a este cgroup\n return os.WriteFile(\n filepath.Join(cgroupPath, \"cgroup.procs\"),\n []byte(strconv.Itoa(containerPid)),\n 0644,\n )\n}\n```\n\nSon solo escrituras a archivos. Eso es cgroups, el kernel expone una interfaz de filesystem, y usted escribirs valores de configuración a archivos. Hermosamente simple, terriblemente poderoso."},{"heading":"youki: runc en Rust","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#youki-runc-en-rust","content":"youki es una reescritura de runc en Rust, y no es solo un proyecto de vanidad. Hay razones de ingeniería reales para que te importe:\n\n**Tiempo de arranque**: compará runtimes con la misma imagen, kernel, configuración de cgroups y ejecuciones repetidas. El [repositorio de youki](<https://github.com/youki-dev/youki>) es la referencia de implementación. Esta guía no incluye un benchmark reproducible de arranque.\n\n**Seguridad de memoria**: Rust puede prevenir categorías de errores de memoria en código seguro. No evita automáticamente fugas de descriptores ni escapes de contenedores. [CVE 2024 21626](<https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv>) involucra descriptores filtrados y requiere analizar el comportamiento del runtime, no deducir su seguridad del lenguaje.\n\n**Reemplazo drop-in**: youki pasa los tests completos de compliance del OCI runtime-spec:\n\n```bash\n# Reemplazar runc con youki en containerd\n# /etc/containerd/config.toml\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc]\n runtime_type = \"io.containerd.runc.v2\"\n [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n BinaryName = \"/usr/local/bin/youki\"\n```\n\nReiniciás containerd, y cada container en la máquina ahora usa youki. Hice esto en un cluster de staging hace tres meses y literalmente nadie se dio cuenta. Ese es el mejor tipo de cambio de infraestructura."},{"heading":"kata-containers: cuando los namespaces no alcanzan","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#kata-containers-cuando-los-namespaces-no-alcanzan","content":"Una limitación fundamental de los containers estándar es que los namespaces son un mecanismo de aislamiento de *userspace*. Particionan la vista del kernel de los recursos, pero cada container comparte el mismo kernel. Un exploit de kernel en un container compromete todos los containers del host.\n\nkata-containers resuelve esto corriendo cada container dentro de una VM liviana (microVM):\n\n```\nContainer estándar:\n┌──────────┐ ┌──────────┐ ┌──────────┐\n│Container1│ │Container2│ │Container3│\n├──────────┴─┴──────────┴─┴──────────┤\n│ Kernel del Host │ ← Superficie de ataque compartida\n└─────────────────────────────────────┘\n\nkata-container:\n┌──────────┐ ┌──────────┐ ┌──────────┐\n│Container1│ │Container2│ │Container3│\n├──────────┤ ├──────────┤ ├──────────┤\n│ Kernel G │ │ Kernel G │ │ Kernel G │ ← Kernels separados\n├──────────┤ ├──────────┤ ├──────────┤\n│ VMM │ │ VMM │ │ VMM │\n├──────────┴─┴──────────┴─┴──────────┤\n│ Kernel del Host │\n└─────────────────────────────────────┘\n```\n\nEl overhead de rendimiento es real pero manejable. Para nuestra plataforma multi-tenant, usamos kata con Cloud Hypervisor para workloads de clientes y runc para nuestros servicios de infraestructura propios. La API RuntimeClass en Kubernetes lo hace trivial:\n\n```yaml\napiVersion: node.k8s.io/v1\nkind: RuntimeClass\nmetadata:\n name: kata-clh\nhandler: kata-clh\noverhead:\n podFixed:\n memory: \"60Mi\"\n cpu: \"50m\"\n---\napiVersion: v1\nkind: Pod\nmetadata:\n name: workload-no-confiable\nspec:\n runtimeClassName: kata-clh\n containers:\n - name: codigo-usuario\n image: customer-function:latest\n resources:\n limits:\n memory: \"256Mi\"\n cpu: \"500m\"\n```"},{"heading":"gVisor: el término medio","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#gvisor-el-trmino-medio","content":"gVisor (runsc) toma un approach diferente. En vez de correr una VM completa, interpone un kernel de userspace que intercepta syscalls del proceso del container:\n\n```\nArquitectura gVisor:\n┌──────────────────┐\n│ App Container │\n├──────────────────┤\n│ Sentry │ ← Kernel userspace (intercepta syscalls)\n│ (implementa │\n│ ~380 syscalls) │\n├──────────────────┤\n│ Gofer │ ← Proxy de filesystem (sandboxeado)\n├──────────────────┤\n│ Kernel del Host │ ← Solo Sentry habla con el kernel del host\n└──────────────────┘\n```\n\nLos trade-offs comparados con kata:\n\n- **Startup**: gVisor agrega ~50ms (vs ~150ms de kata)\n- **Memoria**: gVisor usa ~15MB de overhead (vs ~40-80MB de kata)\n- **Compatibilidad**: gVisor no soporta todos los syscalls, apps que usan syscalls poco comunes (como io\\_uring) pueden no funcionar\n- **Rendimiento**: Workloads con muchos syscalls sufren un hit de rendimiento de 2-10x"},{"heading":"seccomp: la última línea de defensa","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#seccomp-la-ltima-lnea-de-defensa","content":"Sin importar qué runtime uses, seccomp filtra las syscalls que un container puede hacer. El perfil seccomp default de Docker bloquea 44 syscalls de ~380+:\n\n```bash\n# Perfilá tu aplicación real para encontrar syscalls necesarias\n$ strace -c -f tu-aplicacion 2>&1 | tail -20\n% time seconds usecs/call calls errors syscall\n------ ----------- ----------- --------- --------- ----------------\n 35.42 0.182346 12 15234 write\n 22.18 0.114218 8 14028 read\n 8.91 0.045889 45 1020 io_uring_enter # ¡Necesitás esta!\n```"},{"heading":"El árbol de decisión de runtimes","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#el-rbol-de-decisin-de-runtimes","content":"Después de correr los cuatro runtimes en producción, así decido:\n\n**runc**: Default para workloads internos de confianza. Bajo overhead, máxima compatibilidad, modelo de seguridad bien entendido. El 90% de los containers deberían correr en runc.\n\n**youki**: Reemplazo drop-in de runc cuando se desea mejor tiempo de startup y las garantías de memory safety de Rust. No hay razón para *no* usarlo si te soportás software más nuevo.\n\n**kata-containers**: Workloads no confiables que necesitan aislamiento a nivel VM. Plataformas multi-tenant, FaaS, ejecución de código de clientes. Aceptá los ~150ms de overhead de startup y ~40MB de overhead de memoria.\n\n**gVisor**: Cuando se necesita mejor aislamiento que runc pero no te es posible soportar el overhead de recurse es de kata. Bueno para procesamiento batch de datos no confiables, servidores web manejando input no confiable. Ojo con la compatibilidad de syscalls.\n\nEl ecosistema de containers está convergiendo en un modelo de seguridad por capas: namespaces para aislamiento básico, cgroups para límites de recursos, seccomp para filtrado de syscalls, y capas opcionales de VM/sandbox para defensa en profundidad. Entender estas capas, y saber qué perillas girar, es la diferencia entre containers que están \"probablemente bien\" y containers en los que apostarías tu empresa."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#questions","content":""},{"heading":"¿Cuál es la diferencia entre un container runtime y un container engine?","level":3,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-1","content":"El container engine (Docker, Podman) maneja gestión de imágenes, networking y orquestación. El container runtime (runc, youki, kata) es el componente de bajo nivel que realmente crea y ejecuta el proceso del contenedor usando features del kernel Linux como namespaces y cgroups."},{"heading":"¿youki está listo para producción?","level":3,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-2","content":"Sí. youki pasa la suite completa de tests de compliance del OCI runtime-spec y se usa en producción en varias organizaciones. Es un reemplazo drop-in de runc con mejor rendimiento de startup y garantías de memory safety de Rust."},{"heading":"¿Cuándo debería usar kata-containers en vez de runc?","level":3,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#question-3","content":"Utilizar kata-containers cuando se necesita aislamiento a nivel hardware para workloads no confiables, plataformas multi-tenant, ejecución de código enviado por usuarios, o requerimientos de compliance que exigen aislamiento de VM. El trade-off es ~150ms de tiempo de startup adicional y mayor overhead de memoria por contenedor."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#article-tips","content":"1. Evaluá compatibilidad y latencia de youki en tu entorno antes de reemplazar un runtime. Usar Rust no demuestra por sí solo el aislamiento de contenedores.\n2. **Tip:** Siempre habilitá cgroup v2 con ` systemd.unified_cgroup_hierarchy=1 `, la jerarquía por-recurso de v1 es una pesadilla para orquestación de contenedores y el árbol unificado de v2 es infinitamente más simple.\n3. **Tip:** kata-containers agregan ~150ms al startup del container pero te dan aislamiento a nivel de hardware. Usalos para workloads no confiables y runc estándar para servicios propios.\n4. **Tip:** Verificá tu perfil seccomp con ` strace -c ` en tu workload real antes de deployar, el perfil default de Docker bloquea 44 syscalls, y una de ellas podría ser la que tu app necesita.\n\n- containers\n- runc\n- youki\n- kata-containers\n- gvisor\n- cgroups\n- namespaces\n- oci\n- kubernetes\n- security\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/container-runtime-internals-runc-youki-kata"},{"text":"Docker","url":"https://www.docker.com/"},{"text":"repositorio de youki","url":"https://github.com/youki-dev/youki"},{"text":"CVE 2024 21626","url":"https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata"},"headline":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","description":"Deep dive en los internals de container runtimes, runc vs youki (Rust), kata-containers microVMs, sandboxing con gVisor, control de recurse es con cgroup v2 y aislamiento de namespaces con traces de syscalls reales.","datePublished":"2025-12-19","dateModified":"2026-10-21","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Containers","keywords":"containers, runc, youki, kata-containers, gvisor, cgroups, namespaces, oci, kubernetes, security","wordCount":1524,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/container-runtime-internals-runc-youki-kata"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Internals de Container Runtimes: runc, youki, kata-containers y Modelos de Aislamiento Sandbox","item":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Cuál es la diferencia entre un container runtime y un container engine?","acceptedAnswer":{"@type":"Answer","text":"El container engine (Docker, Podman) maneja gestión de imágenes, networking y orquestación. El container runtime (runc, youki, kata) es el componente de bajo nivel que realmente crea y ejecuta el proceso del contenedor usando features del kernel Linux como namespaces y cgroups."}},{"@type":"Question","name":"¿youki está listo para producción?","acceptedAnswer":{"@type":"Answer","text":"Sí. youki pasa la suite completa de tests de compliance del OCI runtime-spec y se usa en producción en varias organizaciones. Es un reemplazo drop-in de runc con mejor rendimiento de startup y garantías de memory safety de Rust."}},{"@type":"Question","name":"¿Cuándo debería usar kata-containers en vez de runc?","acceptedAnswer":{"@type":"Answer","text":"Utilizar kata-containers cuando se necesita aislamiento a nivel hardware para workloads no confiables, plataformas multi-tenant, ejecución de código enviado por usuarios, o requerimientos de compliance que exigen aislamiento de VM. El trade-off es ~150ms de tiempo de startup adicional y mayor overhead de memoria por contenedor."}}]}]}],"contentHash":"b403fc3fec08c5ceccbf9963974cc4076e9d10a9d15377433f4c6404b4d2840c","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/container-runtime-internals-runc-youki-kata","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":70340,"contentBytes":14590}}
