← Volver a artículos
Noticias· 5 min de lectura

Docker y los contenedores: cómo el kernel Linux lo hace posible

Contenedores de carga apilados en un puerto, metáfora de los contenedores de software
Foto: Wolfgang Weiser · Pexels

Pocas tecnologías han cambiado tanto la forma de desarrollar y desplegar software en la última década como los contenedores. Hablamos de Docker, de Kubernetes, de imágenes que arrancan en milisegundos y se mueven igual entre el portátil de un desarrollador y un clúster en producción. Detrás de toda esa magia no hay ningún truco propietario: hay un puñado de funciones del kernel Linux que llevaban años madurando antes de que nadie pronunciara la palabra “contenedor”. Entender esas piezas es entender por qué los contenedores son tan ligeros y por qué, en el fondo, dependen tanto de Linux.

Un contenedor no es una máquina virtual

Diagrama que compara una aplicación tradicional con una aplicación en contenedor compartiendo el kernel del host
Una aplicación en contenedor comparte el kernel del host en lugar de ejecutar un sistema operativo completo como una máquina virtual. · Imagen: Joseph554 / CC BY-SA 4.0 · Wikimedia Commons

La confusión más extendida es imaginar un contenedor como una máquina virtual en miniatura. No lo es, y la diferencia importa mucho. Una máquina virtual emula hardware completo y ejecuta su propio sistema operativo, con su propio kernel, encima de un hipervisor. Eso significa gigabytes de disco, minutos de arranque y un consumo de memoria notable solo por existir.

Un contenedor, en cambio, comparte el kernel del host. No arranca un segundo sistema operativo: son procesos normales del anfitrión que el kernel mantiene aislados unos de otros para que cada uno crea que está solo en la máquina. Por eso un contenedor arranca en una fracción de segundo y pesa unos pocos megabytes en lugar de varios gigas. La contrapartida está clara: todos los contenedores de una máquina ejecutan el mismo kernel, el del host. No puedes correr un kernel distinto dentro de un contenedor.

Namespaces: la ilusión del aislamiento

La primera pieza clave son los namespaces. Un namespace envuelve un recurso global del sistema en una abstracción que hace creer a los procesos de su interior que poseen su propia instancia aislada de ese recurso. Linux ofrece varios tipos: PID (los procesos del contenedor tienen su propia numeración y no ven los del host), Network (cada contenedor puede tener su propia pila de red, interfaces e IP), Mount (su propia vista del sistema de ficheros), UTS (su propio hostname), IPC, User y Cgroup.

Gracias a los namespaces, el proceso que corre dentro de un contenedor “ve” un sistema que parece solo suyo: su propio PID 1, su propia red, sus propios puntos de montaje. Es una ilusión que el kernel construye con cuidado, pero desde dentro resulta indistinguible de una máquina dedicada.

Cgroups: poner límites a los recursos

Diagrama de la jerarquía unificada de cgroups del kernel Linux gestionada por systemd
Los cgroups del kernel Linux organizan los procesos en una jerarquía unificada para medir y limitar CPU, memoria y E/S. · Imagen: VectorVoyager / CC BY-SA 4.0 · Wikimedia Commons

Aislar no basta. También hay que evitar que un contenedor se coma toda la CPU o toda la memoria y deje al resto sin recursos. De eso se encargan los control groups, o cgroups. Esta funcionalidad nació en Google en 2006-2007 con el nombre de “process containers” y se integró en el kernel Linux con la versión 2.6.24, en 2008.

Los cgroups permiten medir y limitar cuánta CPU, memoria, ancho de banda de E/S o número de procesos puede consumir un grupo de procesos. Si lanzas un contenedor con un tope de 512 MB de RAM, son los cgroups los que hacen cumplir esa frontera a nivel de kernel. La versión 2 de cgroups, ya consolidada en las distribuciones modernas, unificó la jerarquía y mejoró bastante la gestión de memoria y E/S.

Capabilities y sistemas de ficheros de capas

Quedan dos ingredientes que completan el cuadro. Las capabilities dividen los antiguos privilegios todopoderosos de root en permisos pequeños e independientes. En lugar de dar a un contenedor poder absoluto, le concedes solo lo que necesita (por ejemplo, abrir un puerto bajo) y le quitas el resto, así reduces mucho el daño posible si algo se ve comprometido.

La otra pieza son los sistemas de ficheros de unión (union filesystems), como OverlayFS. Permiten apilar capas de solo lectura y añadir encima una capa de escritura. Así funcionan las imágenes: una capa base, capas con dependencias, tu aplicación encima, y todo compartido entre contenedores sin duplicar datos en disco. Esto explica por qué una imagen de Alpine Linux ocupa apenas unos megabytes y por qué desplegar la copia número cien de una imagen no consume cien veces el espacio.

De LXC a Docker, runc y el estándar OCI

Toda esta tecnología existía dispersa y costaba usarla. Proyectos como LXC ya combinaban namespaces y cgroups para crear entornos aislados, pero la curva de aprendizaje era empinada. En 2013 apareció Docker, construido al principio sobre LXC, y cambió las reglas con dos ideas geniales: empaquetar todo en imágenes portables y ofrecer una experiencia de uso sencilla. De repente “funciona en mi máquina” dejó de ser una excusa.

Docker reemplazó después LXC por su propia librería, libcontainer, para hablar directamente con el kernel. Y en junio de 2015 nació la Open Container Initiative (OCI), impulsada por Docker, CoreOS y otros, para estandarizar el formato. Docker donó runc, el runtime de referencia que cumple la especificación OCI y que hoy ejecuta contenedores por debajo de Docker, containerd y buena parte del ecosistema de Kubernetes.

Por qué todo esto importa

Cuando lanzas un contenedor sobre Ubuntu, Debian o cualquier distribución, no estás usando una caja negra: estás orquestando namespaces, cgroups, capabilities y OverlayFS, todos del kernel del host. Por eso los contenedores son nativos de Linux y por eso en macOS o Windows Docker arranca en realidad una pequeña máquina virtual Linux por debajo: necesita ese kernel para funcionar.

Entender estos fundamentos no es un ejercicio académico. Te ayuda a depurar problemas de red o de permisos, a captar los límites de seguridad reales de un contenedor y a tomar mejores decisiones de despliegue. Los contenedores no inventaron nada nuevo: pusieron una interfaz elegante sobre dos décadas de evolución del kernel Linux. Y ese, precisamente, es su mayor logro.