¿Qué son los contenedores?
Al terminar la unidad anterior nos quedamos con una pregunta: ¿podemos preparar TasteMatch junto con parte de lo que necesita y repetir ese entorno en otras máquinas compatibles?
Queremos, además, que preparar sus dependencias no cambie las de otra aplicación. Los contenedores nos ayudan a conseguir ambas cosas. Para entender cómo, empecemos por algo más sencillo: qué ocurre cuando ejecutamos un programa.
Del programa al proceso
Tener los archivos de TasteMatch guardados no significa que la aplicación esté funcionando. Cuando ejecutamos su código con Node.js, el sistema pone en marcha un proceso: una instancia de un programa en ejecución.
Ese proceso utiliza recursos de la máquina, como memoria y tiempo del procesador, y accede a los archivos que necesita y tiene permitido utilizar.
Archivos del programa
│
│ Ejecutamos el programa
▼
Proceso en ejecución
│
└── Utiliza memoria, procesador y archivos
Un mismo programa puede ejecutarse varias veces y dar lugar a procesos distintos. Y una aplicación puede necesitar más de un proceso para realizar su trabajo.
Ahora imagina que ejecutamos TasteMatch y otra aplicación en el mismo servidor. Ambas necesitan versiones diferentes de una librería. ¿Y si cada una pudiera encontrar sus propios archivos y dependencias, sin utilizar la instalación de la otra?
Separar lo que ve cada aplicación
Aislar procesos significa limitar qué partes del sistema pueden ver y utilizar. Por ejemplo, podemos dar a los procesos de TasteMatch una vista de archivos propia, con sus librerías y su configuración.
Así, cuando TasteMatch busca una librería, encuentra la preparada para su entorno. La otra aplicación puede encontrar una versión diferente en el suyo. No necesitamos sustituir una única versión compartida para satisfacer a ambas.
Esa separación no se consigue simplemente guardando los programas en carpetas distintas: el sistema operativo dispone de mecanismos para aislar sus procesos.
Aquí aparece el concepto que buscamos:
Un contenedor permite ejecutar uno o varios procesos en un entorno aislado, preparado con la aplicación y parte de lo que necesita para funcionar.
Desde dentro, la aplicación puede encontrar sus propios archivos, librerías y ajustes. Desde el sistema que la ejecuta, siguen siendo procesos que utilizan recursos de la máquina.
La aplicación y el contenedor no son lo mismo
TasteMatch es el software que queremos ejecutar. El contenedor es el entorno aislado donde lo ejecutamos. TasteMatch no se convierte literalmente en un contenedor ni deja de ser una aplicación.
Contenedor de TasteMatch
├── Procesos que ejecutan TasteMatch con Node.js
├── Archivos de la aplicación
├── Librerías necesarias
└── Configuración disponible para la aplicación
Es un esquema conceptual. No significa que absolutamente todo lo necesario esté guardado dentro del contenedor: la configuración puede proporcionarse al ejecutarlo, y los datos o los servicios externos deben seguir estando disponibles cuando la aplicación los necesite.
¿Quién sostiene ese entorno? El host y su kernel
Llamamos host o sistema anfitrión a la máquina en la que se ejecutan los contenedores. Puede ser una máquina física o una máquina virtual.
El anfitrión tiene un sistema operativo, que gestiona los recursos y permite ejecutar programas. Su parte central se llama kernel o núcleo: se encarga, entre otras cosas, de gestionar el uso del procesador, la memoria y el acceso a los dispositivos.
Los procesos necesitan esos servicios del kernel para trabajar. Un contenedor no arranca un kernel propio: sus procesos utilizan el del anfitrión que los ejecuta. Ese kernel proporciona también los mecanismos de aislamiento.
Host o máquina anfitriona
│
├── Contenedor A Contenedor B
│ ├── TasteMatch ├── Otra aplicación
│ └── Sus dependencias └── Sus dependencias
│ │ │
│ └────────────┬───────────────┘
│ ▼
├── Sistema operativo: kernel compartido
│ │
└── Recursos de la máquina: procesador, memoria, disco…
Las aplicaciones tienen entornos separados, pero no una máquina completa para cada una. Comparten esa base del sistema y los recursos disponibles.
No confundas runtime y kernel: Node.js permite ejecutar el código de TasteMatch; el kernel gestiona los recursos del sistema que utilizan Node.js y los demás procesos.
Un matiz sobre el anfitrión: hace falta un kernel compatible. Un contenedor preparado para Linux no utiliza directamente el kernel de Windows o macOS. Puede ejecutarse en una máquina virtual Linux dentro de ese ordenador; en ese caso, comparte el kernel de esa máquina virtual.
Como apartamentos en un edificio
Piensa en un edificio con varios apartamentos. Cada apartamento tiene su espacio y puede organizarse de forma diferente, aunque todos comparten parte de la infraestructura del edificio.
| En el símil | En nuestro modelo |
|---|---|
| El edificio alberga los apartamentos. | El host ejecuta los contenedores. |
| Cada apartamento tiene sus muebles y distribución. | Cada contenedor puede tener sus archivos y dependencias. |
| Los apartamentos comparten infraestructura. | Los contenedores comparten el kernel y recursos del anfitrión. |
Es solo un símil. Un contenedor no funciona literalmente como una vivienda: su separación depende de mecanismos y permisos del sistema, no de paredes.
Separados no significa completamente independientes
Si un contenedor consume muchos recursos, puede afectar a lo que queda disponible para los demás. Y lo que puede ver o hacer depende de cómo se haya configurado su aislamiento y sus permisos.
Por eso, aislamiento no equivale a seguridad absoluta. Ayuda a reducir interferencias, pero no corrige fallos de seguridad de una aplicación ni elimina todos los riesgos para el anfitrión.
Entonces, ¿es una máquina virtual pequeña?
No. Tanto los contenedores como las máquinas virtuales, también llamadas VM, permiten separar entornos y ejecutar varias aplicaciones en una misma máquina. La diferencia está en qué separan y qué comparten.
Una máquina virtual proporciona una máquina mediante software. En ella podemos ejecutar un sistema operativo invitado, con su propio kernel, además de las aplicaciones.
El software que proporciona los recursos virtuales a esas máquinas se llama hipervisor. Para esta comparación basta con situarlo entre las máquinas virtuales y los recursos físicos:
Máquina física
└── Hipervisor: proporciona recursos virtuales
├── VM A
│ ├── Sistema operativo invitado y su kernel
│ └── Aplicación A y dependencias
└── VM B
├── Sistema operativo invitado y su kernel
└── Aplicación B y dependencias
En el esquema de contenedores anterior, en cambio, los procesos de A y B utilizan el mismo kernel del anfitrión. Cada contenedor no necesita un sistema operativo completo independiente.
| Qué comparamos | Contenedor | Máquina virtual |
|---|---|---|
| Qué entorno proporciona | Un entorno aislado para procesos. | Una máquina virtual donde ejecutar un sistema operativo invitado. |
| Qué kernel utiliza | El del anfitrión que ejecuta sus procesos. | El de su sistema operativo invitado. |
| Qué podemos preparar por separado | Aplicación, librerías y otros archivos de su entorno. | Un sistema operativo invitado y las aplicaciones instaladas en él. |
| Qué sigue compartido | Kernel y recursos del anfitrión. | Recursos físicos mediante la virtualización. |
Un contenedor puede disponer de herramientas y archivos de una distribución de Linux, es decir, de una variante de ese sistema. Eso no significa que esté arrancando un Linux completo con un kernel propio.
Por qué normalmente son más ligeros
Para ejecutar los procesos de un contenedor no hace falta arrancar otro sistema operativo completo. Al evitar esa tarea y sus recursos asociados, normalmente los contenedores son más ligeros y rápidos de iniciar que una VM completa.
Eso no hace que cualquier aplicación arranque al instante ni consuma poca memoria: sigue importando lo que tenga que hacer.
Tampoco significa que una opción sea siempre mejor. Si necesitas probar un sistema operativo completo distinto, una VM puede ser lo adecuado. Si buscas separar entornos de aplicaciones compatibles con el kernel disponible, los contenedores pueden encajar.
Ambas tecnologías pueden utilizarse juntas: una VM puede ser el anfitrión de varios contenedores.
Repetir el entorno sin copiar una máquina entera
Volvamos a nuestra pregunta inicial. Podemos preparar los archivos de una aplicación con su runtime y sus librerías, conservar las versiones elegidas y utilizar esa misma preparación para ejecutarla en contenedores en distintos anfitriones compatibles.
Aplicación + runtime + librerías en versiones conocidas
│
Misma preparación del entorno
│
┌──────────┴──────────┐
▼ ▼
Host compatible A Host compatible B
└── Contenedor └── Contenedor
└── Aplicación └── Aplicación
Así reducimos la necesidad de reconstruir a mano las dependencias en cada máquina. Reproducibilidad es poder volver a preparar esas condiciones de forma definida, sin adivinar qué había instalado alguien.
Pero repetir los archivos del entorno no basta para garantizar cualquier resultado. Siguen importando la configuración que proporcionamos, los datos, los servicios externos y los recursos disponibles. También deben ser compatibles el kernel y el tipo de procesador para el que se haya preparado el software.
Piensa en este caso: llevamos TasteMatch a otro anfitrión con el mismo Node.js y las mismas librerías, pero olvidamos proporcionar el archivo de recetas. ¿Lo arregla el aislamiento? No: hemos separado y repetido parte del entorno, pero sigue faltando un dato necesario. Tampoco desaparecería un error que ya estuviera en el código.
¿Qué podemos ejecutar en un contenedor?
No se limita a aplicaciones escritas en un lenguaje concreto. Estos son algunos ejemplos:
| Ejemplo | Qué hace | Parte del entorno que necesita |
|---|---|---|
| TasteMatch | Permite consultar recetas desde el navegador. | Su código, Node.js y las librerías que utiliza. |
| Nginx, un servidor web | Atiende peticiones y puede entregar páginas web. | El programa Nginx y sus archivos necesarios. |
| PostgreSQL, un sistema de bases de datos | Permite guardar y consultar datos. | El programa PostgreSQL y sus dependencias. |
Normalmente preparamos cada contenedor alrededor de un propósito o responsabilidad clara, como ejecutar una aplicación web o un servicio de base de datos.
Eso no significa «un contenedor solo puede tener un proceso». Un servicio puede utilizar varios procesos para hacer su trabajo. Tampoco significa que debamos reunir en un único contenedor todos los servicios de una aplicación completa.
Por ahora nos interesa identificar qué queremos ejecutar y qué necesita, sin entrar todavía en cómo preparar esos servicios.
Ideas que pueden llevarnos a error
«Un contenedor es una VM pequeña y tiene su propio kernel»
En el modelo que estamos viendo, el contenedor ejecuta procesos aislados que comparten el kernel del anfitrión. Una VM tiene su sistema operativo invitado y su kernel. La diferencia no es simplemente el tamaño.
«Para tener un contenedor hay que meter un sistema operativo completo»
Necesitamos los archivos y programas adecuados para la aplicación, no arrancar otro sistema operativo. Disponer de algunas herramientas de Linux no equivale a tener un kernel propio.
«Aislado significa totalmente separado y automáticamente seguro»
Se limita lo que los procesos ven y utilizan, pero sigue habiendo una base compartida. La seguridad también depende de los permisos, la configuración y el software ejecutado.
«Con contenedores funcionará en cualquier máquina»
Reducimos diferencias del entorno; no eliminamos los requisitos de compatibilidad ni los recursos que necesita la aplicación.
«Los contenedores sustituyen a las máquinas virtuales»
Resuelven necesidades diferentes y pueden combinarse. Ejecutar un sistema operativo invitado completo sigue siendo distinto de aislar los procesos de una aplicación.
Tu turno: un entorno para TasteMatch
Imagina que preparamos este entorno para ejecutar TasteMatch:
Contenedor
├── TasteMatch
├── Node.js en una versión compatible
├── Librerías en las versiones probadas
└── Configuración y acceso al archivo de recetas
El dibujo representa lo que la aplicación necesita tener disponible, no que todos los datos y ajustes deban estar empaquetados juntos.
Antes de seguir, responde con tus palabras:
- ¿Qué problema de la unidad anterior intentamos solucionar?
- ¿Qué elementos interesa preparar juntos y mantener en versiones conocidas?
- ¿Qué significa que TasteMatch esté aislado de otra aplicación?
- ¿Qué aporta el host? ¿Dónde situarías el kernel y la memoria de la máquina?
- ¿Qué cambiaría si preparásemos una VM completa para TasteMatch?
Compara tu razonamiento
Queremos reducir diferencias entre equipos y conflictos entre dependencias. Preparar el código de TasteMatch con Node.js y sus librerías nos ayuda a repetir esa parte del entorno; debemos asegurar también la configuración y los datos que utilizará.
Aislarlo permite que sus procesos encuentren sus propias dependencias sin tener que cambiar las de otra aplicación. No deja de necesitar al host: este aporta el kernel y los recursos, como memoria y procesador.
En una VM completa prepararíamos también un sistema operativo invitado con su kernel. En el contenedor utilizamos el kernel del anfitrión; no estamos copiando toda la máquina de Ana.
Mini reto: elige qué necesitas separar
Lee estas tres situaciones:
- Una aplicación necesita una versión concreta de un runtime y varias librerías. Queremos repetir ese entorno en distintos servidores compatibles.
- Necesitamos ejecutar un sistema operativo completo diferente dentro de nuestra máquina para realizar pruebas.
- Dos aplicaciones comparten servidor, pero requieren versiones incompatibles de una librería. Queremos evitar que preparar una afecte a la otra.
Para cada situación, explica:
- ¿Te parece útil pensar en contenedores o tiene más sentido una VM? ¿Por qué?
- ¿Qué queremos separar o repetir?
- ¿Qué seguiría dependiendo de la máquina que lo ejecuta?
Después corrige esta afirmación: «Si pongo cada aplicación en un contenedor, cada una tendrá su propio kernel y ya no podrá afectar a las demás».
Compara tu respuesta
Situación 1: los contenedores pueden ayudar a repetir la aplicación, el runtime y las librerías en versiones conocidas. Necesitamos anfitriones compatibles, recursos suficientes y la configuración y los datos adecuados.
Situación 2: una VM tiene más sentido porque queremos probar un sistema operativo invitado completo, con su propio kernel. Aun así, utiliza recursos físicos de la máquina mediante la virtualización.
Situación 3: los contenedores permiten que cada aplicación encuentre su versión de la librería en un entorno separado. Comparten el kernel y los recursos del anfitrión, por lo que esa separación no elimina todas las posibles interferencias.
La afirmación final es incorrecta por dos razones: los contenedores comparten el kernel del anfitrión, y el aislamiento no es absoluto. Por ejemplo, siguen dependiendo de los recursos disponibles en la misma máquina.
No se trata de elegir una tecnología para todos los casos, sino de reconocer qué necesitamos aislar y qué base seguimos compartiendo.
Entonces, ¿qué papel juega Docker?
Ya podemos describir un contenedor sin recurrir a una caja mágica: es un entorno aislado donde ejecutamos procesos con los archivos y dependencias preparados para ellos, utilizando el kernel de su anfitrión.
El siguiente paso es disponer de herramientas para trabajar con esos entornos. Docker es una plataforma que permite crear y gestionar contenedores. Los contenedores son el concepto; Docker es una de las tecnologías que podemos utilizar para trabajar con ellos.
Por eso, Docker y contenedor no son sinónimos. Docker no inventó la idea de aislar procesos ni es la única tecnología relacionada con contenedores.
En un ordenador Windows o macOS, herramientas como Docker Desktop pueden proporcionar una máquina virtual Linux para ejecutar contenedores Linux. Es un ejemplo del matiz que vimos sobre el anfitrión y el kernel compatible, no un cambio en el concepto de contenedor.
¿Cómo nos ayuda Docker a trabajar con ellos? Esa será la pregunta de la siguiente unidad: Conociendo Docker.