Saltar a contenido

2.0.-Introducción a los contenedores Docker

2.0. Introducción a los contenedores Docker

Idea principal

Un contenedor empaqueta una aplicación y las dependencias que necesita para ejecutarse de forma aislada y reproducible. Docker proporciona las herramientas para construir, distribuir y ejecutar esos contenedores sin tener que instalar cada aplicación directamente sobre el sistema anfitrión.

Cuando una aplicación pasa del ordenador de quien la desarrolla a otro equipo, es habitual que aparezcan diferencias: otra versión del lenguaje, una biblioteca que falta, una configuración distinta o un sistema operativo incompatible. La contenerización aborda este problema separando el entorno de ejecución de la aplicación del resto del sistema. Así, el equipo puede describir qué necesita la aplicación y repetir el mismo despliegue en desarrollo, pruebas y producción.

Este punto introduce los conceptos que se utilizarán durante toda la unidad: contenedor, imagen, motor Docker, registro, aislamiento, persistencia y red. No se trata de memorizar comandos aislados, sino de entender qué ocurre cuando se crea un contenedor y qué responsabilidades siguen siendo de la persona que lo administra. La instalación y los primeros comandos se desarrollan en Instalación de Docker.

Este contenido prepara el RA1 y, en particular, el CE 1.e de la normativa del módulo. La demostración práctica de la instalación y de las pruebas se completa en el punto siguiente.

Código Descripción
RA1 Implanta arquitecturas web analizando y aplicando criterios de funcionalidad.
CE 1.e Se ha realizado la instalación y configuración básica de tecnologías de virtualización de servidores en la nube y en contenedores.

Qué deberías saber al terminar

Al acabar este tema deberías poder:

  • explicar qué es un contenedor y por qué no equivale a una máquina virtual;
  • describir cómo intervienen el núcleo del sistema, los namespaces y los cgroups en el aislamiento;
  • distinguir una imagen, un contenedor, un registro y el motor Docker;
  • interpretar el ciclo básico de una aplicación empaquetada en contenedores;
  • valorar cuándo Docker aporta ventajas y qué riesgos no elimina.

Mapa del tema

En este documento seguiremos una progresión de lo general a lo concreto:

  1. el problema de las dependencias y la idea de contenerización;
  2. la diferencia entre máquinas virtuales y contenedores;
  3. el aislamiento que ofrece el sistema operativo;
  4. Docker como plataforma y sus componentes;
  5. el ciclo de vida de una aplicación contenedorizada;
  6. los casos de uso, las decisiones y los límites del modelo.

1. Del despliegue tradicional a la contenerización

Una aplicación no está formada únicamente por su código fuente. Para ejecutarse necesita un entorno: un lenguaje o runtime, bibliotecas, herramientas del sistema, variables de configuración, puertos y, en algunos casos, servicios auxiliares como una base de datos. Si esas piezas no coinciden entre dos máquinas, la aplicación puede fallar aunque el código sea exactamente el mismo.

Por ejemplo, una aplicación web desarrollada con Node.js puede requerir una versión concreta del runtime y de sus paquetes. Instalarla directamente en el ordenador anfitrión puede afectar a otras aplicaciones, y documentar todos los pasos manualmente no garantiza que otra persona reproduzca el entorno sin errores. Un contenedor permite reunir el código, el runtime y las bibliotecas necesarias en una unidad que se crea y se elimina de forma controlada.

Contenedor

Un contenedor es un proceso aislado que utiliza el núcleo del sistema operativo anfitrión y dispone de una vista restringida de los procesos, el sistema de archivos, la red y los recursos asignados. Incluye el entorno que necesita una aplicación, pero no contiene un sistema operativo completo con un núcleo independiente.

La palabra ligero no significa que el contenedor sea una carpeta comprimida ni que carezca de aislamiento. Significa que no necesita arrancar otro núcleo y que puede compartir partes de su sistema de archivos con otros contenedores. Docker es una plataforma de código abierto que organiza este proceso para que la misma definición pueda distribuirse y ejecutarse en distintos equipos compatibles. Sus ventajas principales son:

  • Portabilidad: el entorno empaquetado se comporta de forma equivalente en los equipos que proporcionan un motor compatible;
  • Eficiencia: no es necesario arrancar un sistema operativo completo por cada servicio;
  • Aislamiento: cada aplicación obtiene una vista y unos recursos separados de los demás servicios;
  • Rapidez: crear o detener un contenedor suele requerir mucho menos tiempo que arrancar una máquina virtual;
  • Consistencia: el mismo artefacto puede probarse y desplegarse en varias fases del ciclo de entrega.

Un problema que la contenerización resuelve

Supón que una aplicación necesita Python 3.12 y una biblioteca que no es compatible con Python 3.10. En lugar de modificar todo el sistema anfitrión, el equipo puede empaquetar la aplicación con Python 3.12 dentro de una imagen. El contenedor utilizará ese entorno, mientras que otras aplicaciones del anfitrión podrán mantener sus propias versiones.

2. Contenedores y máquinas virtuales

Una máquina virtual (MV) emula o virtualiza un ordenador completo. Un hipervisor le asigna CPU, memoria, almacenamiento y red, y en ella se instala un sistema operativo invitado con su propio núcleo. El resultado ofrece un aislamiento amplio, pero necesita más recursos y tarda más en arrancar.

Un contenedor se ejecuta como un conjunto de procesos aislados sobre el sistema operativo anfitrión. Comparte su núcleo y reutiliza capas comunes del sistema de archivos. Por eso suele iniciar más rápido y consumir menos memoria, aunque su aislamiento no es equivalente al de una máquina virtual.

Característica Máquina virtual Contenedor
Núcleo Cada MV tiene su propio núcleo Comparte el núcleo del anfitrión
Unidad aislada Sistema operativo completo Proceso y dependencias de la aplicación
Arranque Normalmente más lento Normalmente rápido
Consumo Mayor, porque incluye otro sistema operativo Menor, porque reutiliza el anfitrión y las capas
Portabilidad Requiere una plataforma de virtualización compatible Requiere un motor de contenedores compatible
Uso habitual Otro sistema operativo, aplicaciones heredadas o aislamiento amplio Servicios reproducibles, pruebas y despliegues ágiles
flowchart TB
  H["Hardware del servidor"] --> HV["Hipervisor"]
  HV --> VM["Máquina virtual<br/>núcleo y sistema operativo propios"]
  H --> OS["Sistema operativo anfitrión"]
  OS --> E["Motor de contenedores"]
  E --> C1["Contenedor web<br/>proceso aislado"]
  E --> C2["Contenedor de base de datos<br/>proceso aislado"]

El diagrama muestra dos estrategias posibles. En un laboratorio pueden combinarse: Docker Desktop, por ejemplo, puede utilizar una máquina virtual gestionada internamente en Windows o macOS para proporcionar un núcleo Linux. Eso no cambia el modelo que estudiaremos: dentro de ese entorno, Docker crea contenedores que comparten el núcleo disponible.

No es una máquina virtual pequeña

Un contenedor no debe describirse como una máquina virtual reducida. Si una aplicación necesita otro núcleo, otro sistema operativo o una frontera de aislamiento más amplia, una máquina virtual puede ser una elección más adecuada. La decisión depende de los requisitos y del riesgo, no de una supuesta superioridad universal.

3. Cómo consigue el sistema operativo el aislamiento

La contenerización se apoya en capacidades del núcleo de Linux que permiten que un proceso vea un entorno limitado y tenga recursos controlados. Docker no inventa un núcleo nuevo para cada contenedor: coordina estas capacidades y ofrece una interfaz más sencilla para utilizarlas.

3.1. Namespaces: una vista separada

Un namespace proporciona a un proceso una vista diferente de determinados recursos del sistema. Un contenedor puede tener su propio espacio de procesos, red, usuarios, nombres de host y puntos de montaje. Desde dentro, el proceso principal puede aparecer como PID 1, mientras que desde el anfitrión sigue siendo un proceso normal gestionado por el núcleo.

Esta separación explica por qué un contenedor puede tener una interfaz de red y un sistema de archivos propios sin poseer hardware virtual completo. También explica por qué el aislamiento depende del núcleo y de la configuración del motor: no es una garantía absoluta frente a cualquier vulnerabilidad.

3.2. Cgroups: límites y contabilidad de recursos

Los control groups o cgroups permiten agrupar procesos y limitar o medir su consumo de recursos. Se pueden controlar, entre otros, la memoria, la CPU, la entrada y salida de disco y determinados aspectos de la red. Así se evita que un servicio consuma todos los recursos del anfitrión sin control.

Namespaces y cgroups resuelven problemas distintos:

Mecanismo Pregunta que responde Ejemplo
Namespace ¿Qué puede ver el proceso? Solo ve sus procesos y su red
Cgroup ¿Cuánto puede consumir? Como máximo 512 MiB de memoria
Sistema de archivos por capas ¿Qué archivos recibe y modifica? Usa una imagen de solo lectura y una capa de cambios

Aislamiento no significa seguridad automática

Compartir el núcleo implica que una vulnerabilidad del núcleo, del motor o de una configuración con demasiados privilegios puede afectar al aislamiento. No conviene utilizar --privileged por comodidad ni ejecutar servicios como root sin una razón. En un entorno real también hay que actualizar imágenes, limitar recursos, separar redes y proteger las credenciales.

3.3. Contenedores de sistema y de aplicación

Los materiales de referencia distinguen dos familias según su finalidad:

  • Los contenedores de sistema, como LXC, intentan ofrecer un entorno parecido a una máquina completa. Su ciclo de vida puede incluir acceso, actualización e instalación de varios servicios dentro del mismo entorno.
  • Los contenedores de aplicación, como Docker, se centran en ejecutar una aplicación o un servicio y distribuirlo con sus dependencias. Su ciclo de vida está ligado al proceso principal: cuando termina ese proceso, el contenedor se detiene.

LXC y Docker no son sinónimos. Ambos se apoyan en el aislamiento del sistema operativo, pero Docker aporta un flujo orientado a construir imágenes, distribuirlas y ejecutar contenedores de aplicación. Esta diferencia de propósito ayuda a entender por qué en Docker se suele separar una aplicación web, una base de datos y un servicio de colas en contenedores distintos.

4. Docker como plataforma

Docker popularizó un flujo de trabajo centrado en tres acciones: build, ship and run. Primero se construye una imagen; después se distribuye mediante un registro; finalmente se ejecuta como uno o varios contenedores. La imagen se convierte así en un artefacto que puede versionarse y trasladarse entre entornos.

4.1. El motor Docker

Docker Engine sigue un modelo cliente-servidor:

  1. La orden docker actúa como cliente y recoge la intención de la persona administradora.
  2. La API transmite la petición al demonio dockerd.
  3. El demonio coordina imágenes, contenedores, redes, almacenamiento y permisos.
  4. Componentes como containerd y runc participan en la creación y ejecución del proceso aislado.

En una instalación local, la comunicación suele realizarse mediante el socket de Docker. La interfaz de línea de comandos oculta la complejidad de la API, pero el modelo es importante para diagnosticar problemas: si el cliente no puede comunicarse con el demonio, ningún docker run llegará a crear el contenedor.

Componente Responsabilidad Ejemplo de uso
Cliente Docker Enviar órdenes al motor docker ps, docker run
Docker Engine Coordinar el entorno de ejecución Crear redes y contenedores
dockerd Recibir peticiones y administrar recursos Gestionar el ciclo de vida
containerd y runc Preparar y ejecutar el proceso aislado Iniciar el proceso principal
Almacenamiento y red Proporcionar capas, volúmenes y conectividad Montar datos y publicar puertos

4.2. Registro, Compose y orquestación

Un registro almacena y distribuye imágenes. Docker Hub es un registro público muy conocido, pero también se pueden utilizar registros privados de una empresa o de un proveedor cloud. El registro no ejecuta la aplicación: entrega la imagen al motor, y el motor crea el contenedor en el equipo elegido.

Docker Compose permite describir en un archivo YAML una aplicación que necesita varios contenedores, sus redes, sus volúmenes y su configuración. Está orientado a trabajar con varios servicios en un mismo equipo y se estudiará en el punto 2.4.

Un orquestador coordina contenedores en un clúster. Puede encargarse de replicar servicios, distribuir carga, reiniciar instancias y realizar actualizaciones controladas. Docker Swarm y Kubernetes son ejemplos de este tipo de herramientas. Compose y un orquestador no son equivalentes: Compose simplifica una aplicación multi-contenedor, mientras que el orquestador gestiona un conjunto de máquinas y servicios distribuidos.

flowchart LR
  C["Cliente docker"] --> A["API del Docker Engine"]
  A --> D["Demonio dockerd"]
  D --> I["Imágenes y capas"]
  D --> K["Contenedores"]
  D --> N["Redes y puertos"]
  D --> V["Volúmenes"]
  R["Registro de imágenes"] -->|pull| I
  I -->|docker run| K

5. Imagen, contenedor y ciclo de vida

Una imagen es una plantilla inmutable formada por capas de solo lectura. Incluye el sistema de archivos mínimo, las bibliotecas, los metadatos y el comando que se ejecutará. Una imagen no es un contenedor en funcionamiento.

Un contenedor es una instancia de una imagen con una capa de escritura y una configuración concreta: nombre, variables de entorno, red, puertos, volúmenes y límites de recursos. Varias instancias pueden utilizar la misma imagen y compartir sus capas de solo lectura.

Elemento Qué representa Qué ocurre al eliminarlo
Imagen Plantilla versionada para crear contenedores Se conserva hasta borrarla expresamente
Contenedor Ejecución concreta de una imagen Se pierde su capa de cambios
Volumen Datos separados del ciclo del contenedor Se conserva hasta eliminarlo explícitamente
Registro Servicio que distribuye imágenes Los contenedores locales no se detienen por borrarlo

El ciclo básico puede resumirse así:

  1. Se obtiene una imagen localmente o desde un registro.
  2. El motor crea un contenedor y añade su configuración.
  3. Se inicia el proceso principal de la imagen.
  4. El contenedor permanece en ejecución mientras ese proceso siga activo.
  5. Si el proceso termina, el contenedor queda detenido; no se convierte en una máquina apagada que pueda seguir ejecutando otros servicios por sí sola.
  6. Se puede reiniciar, eliminar o crear una nueva instancia desde la misma imagen.
flowchart LR
  I["Imagen de aplicación"] -->|crear y ejecutar| C["Contenedor"]
  C --> P["Proceso principal"]
  P -->|sigue activo| R["Servicio disponible"]
  P -->|termina| S["Contenedor detenido"]
  S -->|recrear desde la imagen| C2["Nuevo contenedor"]

Un ejemplo conceptual es docker run hello-world. Docker busca la imagen en el equipo; si no la encuentra, la obtiene del registro; crea el contenedor; ejecuta el programa que muestra el mensaje y, cuando ese programa finaliza, detiene el contenedor. El ejemplo no instala un sistema Ubuntu completo ni deja un servicio ejecutándose: demuestra que el motor puede descargar una imagen y ejecutar correctamente su proceso principal.

El sistema de archivos del contenedor es efímero

Los cambios realizados en la capa de escritura pertenecen a ese contenedor. Si el contenedor se elimina, esos cambios desaparecen. Los datos que deban sobrevivir se guardarán en volúmenes, bind mounts o servicios de almacenamiento externos. La unidad estudia estas opciones en el punto 2.3.

6. Aplicaciones web preparadas para contenedores

La contenerización resulta especialmente útil cuando una aplicación se puede separar en servicios con responsabilidades claras. Una aplicación monolítica también puede ejecutarse en un contenedor, pero conviene analizar si su servidor web, su lógica de aplicación y su base de datos deberían gestionarse como componentes diferenciados.

En una arquitectura sencilla, un servidor web recibe las peticiones, un servidor de aplicación ejecuta la lógica y una base de datos conserva el estado. Cada servicio puede tener su propia imagen, configuración, red y ciclo de actualización. No significa que haya que convertir cualquier programa en microservicios: separar componentes introduce comunicación de red, observación, copias de seguridad y coordinación adicionales.

flowchart LR
  U["Navegador"] -->|HTTP| W["Contenedor web"]
  W -->|Red interna| A["Contenedor de aplicación"]
  A -->|Red interna| B["Contenedor de base de datos"]
  B --> D["Volumen de datos"]

Un diseño razonable mantiene la base de datos en una red interna y publica solo el puerto que necesita el navegador. Las direcciones IP de los contenedores pueden cambiar al recrearlos; por eso los servicios deben localizarse por nombre dentro de una red Docker o mediante la configuración declarativa de Compose. Las decisiones de red, persistencia y publicación se desarrollarán en los puntos posteriores de la unidad.

Qué aporta el modelo en un despliegue web

Para probar una nueva versión de una aplicación, se puede crear un contenedor a partir de otra etiqueta de imagen sin modificar el servidor web del anfitrión. Si la prueba no funciona, se elimina el contenedor y se vuelve a crear el anterior. La facilidad para recrear no sustituye a una copia de seguridad ni a un procedimiento de cambio, pero reduce el riesgo de dejar instalaciones manuales a medio modificar.

7. Buenas prácticas

Para trabajar con contenedores con criterio técnico, conviene aplicar estas decisiones desde el principio:

  • Elegir la tecnología por requisitos: utiliza contenedores cuando necesites empaquetado reproducible y arranque rápido; utiliza una máquina virtual si necesitas otro núcleo, otro sistema operativo o más aislamiento.
  • Separar aplicación y datos: trata el contenedor como recreable y coloca la información persistente en volúmenes o servicios externos.
  • Mantener una responsabilidad principal por contenedor: facilita los registros, las actualizaciones, las comprobaciones de salud y la recuperación.
  • Fijar versiones: una etiqueta como latest puede cambiar; registra la versión probada y, cuando sea necesario, el digest de la imagen.
  • Aplicar mínimo privilegio: evita --privileged, limita recursos y usa un usuario no privilegiado cuando la imagen y la aplicación lo permitan.
  • Publicar solo lo necesario: un puerto interno no tiene por qué exponerse al anfitrión ni a Internet.
  • Leer la documentación de cada imagen: comprueba variables de entorno, puertos, volúmenes, arquitectura y procedimiento de actualización.
  • Documentar el entorno: anota versión de Docker, sistema anfitrión, imágenes empleadas, puertos y decisiones de configuración.

8. Errores frecuentes

Error frecuente Por qué ocurre Cómo evitarlo
Confundir un contenedor con una máquina virtual Ambos parecen entornos aislados desde la terminal Explica qué comparte cada uno con el anfitrión y recuerda que el contenedor no tiene un núcleo propio
Pensar que un contenedor es un sistema operativo completo Una imagen puede incluir directorios con aspecto de sistema Linux Identifica siempre el proceso principal y el núcleo que realmente utiliza
Interpretar Exited como un fallo automático Se espera que el contenedor siga abierto después de terminar su comando Comprueba el código de salida y relaciona el estado con el proceso principal
Guardar datos importantes en el sistema de archivos del contenedor Durante una prueba los archivos parecen permanecer Usa un volumen o almacenamiento externo antes de recrear o eliminar el contenedor
Publicar todos los puertos Se usa -p sin analizar quién necesita el acceso Publica solo la entrada necesaria y limita la interfaz de escucha cuando proceda
Usar latest como garantía de estabilidad Se interpreta como una versión fija Fija una etiqueta compatible y documenta la imagen probada
Creer que Docker Hub ejecuta la aplicación Se confunde registro con motor El registro distribuye imágenes; el Docker Engine las ejecuta en un host
Convertir cualquier monolito en microservicios Se asocia contenerización con microservicios obligatorios Evalúa el beneficio y el coste operativo antes de separar componentes
Añadir la cuenta al grupo docker sin valorar el riesgo Se busca evitar sudo Recuerda que el acceso al socket Docker puede equivaler a privilegios elevados y aplica la política del entorno

9. Resumen

En este tema has aprendido que:

  • un contenedor empaqueta una aplicación y sus dependencias como procesos aislados que comparten el núcleo del anfitrión;
  • una máquina virtual incluye un sistema operativo y un núcleo propios, por lo que ofrece otro equilibrio entre aislamiento, consumo y flexibilidad;
  • los namespaces separan la vista de los recursos y los cgroups controlan su consumo;
  • Docker coordina cliente, API, demonio, imágenes, contenedores, redes y almacenamiento;
  • una imagen es una plantilla, un contenedor es una ejecución y un registro es un lugar para distribuir imágenes;
  • el proceso principal determina el ciclo de vida del contenedor y los datos persistentes deben separarse de su capa efímera;
  • Docker Compose y los orquestadores resuelven problemas distintos y se estudiarán con más detalle en los siguientes puntos.

Idea clave

Docker no consiste en ejecutar una máquina virtual pequeña, sino en empaquetar y ejecutar procesos reproducibles sobre un sistema anfitrión. Entender qué se aísla, qué se comparte y qué debe persistir permite elegir una arquitectura de despliegue funcional y segura.

10. Para seguir practicando

Bibliografía y fuentes

Presentación