Contenedores
Bibliografía
- Aprende Docker ahora! Curso completo gratis desde cero
- Docker Docs
- DevOps con Docker, Jenkins, Kubernetes, Git, GitFlow CI y CD
- Minikube Docs
- Kubernetes Tutorials
- Kubernetes: De novato a pro! (Curso completo en español)
Docker
Logo de Docker
Docker es una plataforma de código abierto que facilita la creación, implementación y ejecución de aplicaciones mediante contenedores. Un contenedor empaqueta una aplicación junto con todas sus dependencias y configuraciones en una unidad estandarizada, lo que simplifica el desarrollo de software y garantiza la consistencia entre distintos entornos. Cabe mencionar que también existen alternativas de código abierto como Podman, que están ganando relevancia debido a los últimos cambios de licencia y uso de Docker en entornos empresariales.
Sistema basado en Microservicios
Este tipo de arquitectura se basa en el concepto de microservicios, ya que permite empaquetar cada servicio de forma independiente con sus propias dependencias, evitando así conflictos entre ellas. La comunicación entre contenedores, es decir, entre cada microservicio, se realiza habitualmente mediante APIs.
Entre sus características principales destacan la portabilidad, puesto que los contenedores se ejecutan en cualquier sistema que soporte Docker independientemente del sistema operativo del host. También destaca la ligereza, dado que comparten el kernel del sistema operativo del host, lo que los hace más rápidos de iniciar que las máquinas virtuales. La consistencia asegura que una aplicación se ejecute de la misma manera en cualquier entorno. El aislamiento garantiza que cada contenedor opera de manera independiente, mejorando la seguridad y evitando conflictos entre aplicaciones. Por último, la escalabilidad facilita la creación y eliminación rápida de instancias.
Contenedores frente a máquinas virtuales
Los contenedores y las máquinas virtuales son tecnologías de virtualización que permiten ejecutar múltiples aplicaciones en un solo servidor físico, lo que se conoce como host. Aunque comparten objetivos similares, como optimizar el uso de recursos y asegurar el aislamiento, difieren significativamente en su implementación y arquitectura subyacente.
Pasos para la creación de un contenedor en Docker
Los contenedores constituyen una forma de virtualización a nivel del sistema operativo, también conocida como virtualización ligera. A diferencia de las máquinas virtuales, que virtualizan un sistema operativo completo, los contenedores comparten el núcleo (kernel) del sistema operativo del host y ejecutan aplicaciones dentro de espacios de usuario completamente aislados.
Cada contenedor contiene únicamente la aplicación y sus dependencias (bibliotecas, archivos de configuración y variables de entorno), lo que lo hace extremadamente portátil y fácil de desplegar en diferentes entornos, desde la máquina local de un desarrollador hasta un clúster en la nube.
El aislamiento de los contenedores se logra mediante diferentes técnicas. Los
namespaces (espacios de nombres) aíslan recursos del sistema operativo: pid aísla los
identificadores de procesos, net proporciona pilas de red separadas, mnt aísla los
puntos de montaje del sistema de archivos, ipc aísla recursos de comunicación entre
procesos, uts aísla nombres de host y dominios, y user aísla identificadores de
usuarios y grupos.
Por otra parte, los cgroups (grupos de control) gestionan el uso de recursos como CPU, memoria y disco, garantizando que los contenedores no consuman más recursos de los asignados. Además, el Union Filesystem (UFS) permite que los contenedores se construyan en capas. Las capas de solo lectura contienen archivos del sistema, mientras que las capas de escritura se mantienen en la parte superior, minimizando el uso de almacenamiento y facilitando el desarrollo iterativo.
Las máquinas virtuales, por su parte, representan una tecnología de virtualización más tradicional que permite ejecutar múltiples sistemas operativos en un servidor físico mediante un hipervisor, como VMware o VirtualBox. Un hipervisor puede ejecutarse directamente en el hardware del servidor (virtualización tipo 1) o sobre un sistema operativo (virtualización tipo 2), gestionando la creación y ejecución de múltiples máquinas virtuales y asignando recursos de hardware de forma eficiente. Cada máquina virtual dispone de su propio sistema operativo completo, lo que proporciona un aislamiento más fuerte que los contenedores, pero a costa de un mayor consumo de CPU, memoria y almacenamiento, así como tiempos de inicio más prolongados.
Los contenedores resultan ideales para desarrollo y pruebas, arquitecturas de microservicios y despliegue continuo (por ejemplo, en herramientas de CI/CD que ofrecen GitLab, GitHub o similares), mientras que las máquinas virtuales son más adecuadas para aplicaciones monolíticas que requieren aislamiento completo del sistema operativo, entornos con múltiples sistemas operativos y cargas de trabajo heredadas.
En la nube, proveedores como Amazon Web Services (AWS), Google Cloud Platform (GCP) y Microsoft Azure ofrecen servicios tanto de contenedores (AWS ECS/Fargate, EKS, Azure Kubernetes Service (AKS) y Google Kubernetes Engine (GKE)) como de máquinas virtuales (EC2 en AWS, VM Instances en GCP y Azure Virtual Machines). Para la gestión local de contenedores, herramientas como Docker Desktop o Docker CLI permiten desarrollar, gestionar y desplegar contenedores.
Arquitectura de Docker Engine
Docker Engine se compone de tres elementos fundamentales. El primero es Docker CLI, una interfaz de línea de comandos que puede ejecutarse incluso en una máquina remota. El segundo es la REST API, que actúa como canal de comunicación entre el CLI y el daemon. El tercero es el Docker Daemon, que gestiona imágenes, contenedores, redes y volúmenes. El CLI puede comunicarse con un daemon remoto a través de la REST API, lo que permite gestionar contenedores en servidores remotos de forma transparente.
Info
Un daemon es un tipo de programa que se ejecuta en segundo plano, en lugar de bajo el control directo de un usuario. Son procesos autónomos que inician durante el arranque del sistema y gestionan tareas recurrentes como servicios de red, impresión o sincronización.
Hay que tener en cuenta que los contenedores comparten el kernel del host. Si el host tiene un kernel Linux, no se pueden ejecutar contenedores Windows de forma nativa, y viceversa. Sin embargo, al instalar Docker en Windows, se crea una instancia de Linux mediante WSL sobre la que Docker ejecuta los contenedores.
Tags e imágenes
Las imágenes utilizan tags para identificar variantes según el sistema operativo
base (Alpine, Debian, Ubuntu), la versión del paquete y otros criterios. Si no se
especifica un tag, Docker utiliza latest por defecto, lo cual no se considera buena
práctica, ya que no se tiene control sobre las versiones utilizadas y podrían aparecer
nuevos problemas no contemplados previamente. Por ello, se recomienda fijar siempre la
versión y actualizarla de forma controlada, por ejemplo, ante problemas de seguridad.
Las propias imágenes públicas en Docker Hub suelen disponer de un sistema de alertas que notifica cuando una imagen se ve comprometida o se detecta algún tipo de fallo o mejora relevante en el servicio al que corresponde.
Ejemplo
Los tags soportados se consultan en la documentación de cada imagen en Docker Hub.
Recopilación de comandos
A continuación se muestra una tabla que recopila los comandos más utilizados para la gestión de contenedores con Docker:
| Comando | Uso/función |
|---|---|
docker images |
Devuelve un listado de todas las imágenes descargadas en la máquina. |
docker pull nombre_imagen |
Descarga una imagen de Docker desde Docker Hub. |
docker image rm nombre_imagen:tag |
Elimina una imagen de Docker. |
docker create nombre_imagen |
Crea un contenedor a partir de una imagen y devuelve el ID del contenedor creado. |
docker create --name nombre_contenedor nombre_imagen |
Crea un contenedor con un nombre específico a partir de una imagen. |
docker start ID_contenedor |
Inicia un contenedor mediante su ID. |
docker start nombre_contenedor |
Inicia un contenedor mediante su nombre. |
docker ps |
Muestra los contenedores activos con información sobre ID, imagen, estado y nombre. |
docker ps -a |
Muestra todos los contenedores, tanto activos como detenidos. |
docker stop nombre_contenedor |
Detiene un contenedor usando su nombre. |
docker stop ID_contenedor |
Detiene un contenedor usando su ID. |
docker rm nombre_contenedor |
Elimina un contenedor de Docker. |
docker run -d -p 8080:80 -i --name Debian debian:latest |
Crea y ejecuta un contenedor mapeando puertos. -d: Ejecuta el contenedor en segundo plano. -p: Mapeo de puertos. -i: Acceso al terminal. --name: Nombre del contenedor. |
docker exec -it nombre_contenedor bash |
Ejecuta un comando en el contenedor, en este caso, accede al terminal del contenedor para interactuar con él. |
docker cp ruta_host nombre_contenedor:ruta_contenedor |
Copia archivos del host al contenedor. |
docker stats nombre_contenedor |
Monitorea el uso de CPU, memoria y ancho de banda de un contenedor. |
docker stats |
Monitorea el uso de recursos de todos los contenedores en ejecución. |
docker network ls |
Muestra todas las redes configuradas en Docker. |
docker network inspect nombre_red |
Obtiene detalles sobre una red específica, incluyendo direcciones IP y contenedores conectados. |
docker network create nombre_red |
Crea una red personalizada. |
docker network rm nombre_red |
Elimina una red. |
docker volume create nombre_volumen |
Crea un volumen. |
docker volume ls |
Lista todos los volúmenes. |
docker volume rm nombre_volumen |
Elimina un volumen. |
docker inspect nombre_contenedor |
Muestra detalles de un contenedor. |
docker logs nombre_contenedor |
Muestra los registros del contenedor. |
docker logs -f nombre_contenedor |
Muestra los registros del contenedor de manera continua. |
docker tag nombre_imagen nueva_etiqueta |
Etiqueta una imagen. |
docker login |
Inicia sesión en un registry. |
docker push nombre_imagen |
Sube una imagen a Docker Hub. |
docker logout |
Cierra sesión en un registry. |
docker system prune --all |
Elimina todos los contenedores detenidos e imágenes que no estén en uso. |
docker volume prune |
Elimina todos los volúmenes que no estén en uso. |
docker network prune |
Elimina todas las redes que no estén en uso, excepto las predeterminadas (bridge, none, host). |
docker update [OPTIONS] CONTAINER [CONTAINER...] |
Actualiza la configuración de uno o varios contenedores. Documentación |
docker run --cpu-shares=512 -m 256m nombre_imagen |
Especifica recursos de sistema (CPU, memoria) para un contenedor. |
docker stop $(docker ps -q) |
Detiene todos los contenedores en ejecución. |
docker start $(docker ps -a -q) |
Inicia todos los contenedores. |
docker rm $(docker ps -a -q) |
Elimina todos los contenedores. |
Modo interactivo y attach/detach
Es importante tener en cuenta que un contenedor no es un sistema operativo completo, sino que está pensado para alojar servicios y aplicaciones. Si se ejecuta una imagen como Ubuntu sin ningún proceso activo, el contenedor se detiene inmediatamente al no tener ninguna tarea que mantener.
Los contenedores de Docker no leen la entrada estándar (stdin) de forma predeterminada.
Para interactuar con un contenedor se utilizan las opciones -i (modo interactivo, que
mapea stdin) y -t (que crea un pseudoterminal).
Ejemplo
Cuando se ejecuta un contenedor sin la opción -d, el terminal queda en modo attach
(primer plano). Para ejecutar en segundo plano se utiliza -d (modo detached). Si se
desea volver al primer plano de un contenedor que se encuentra en segundo plano debemos
utilizar el comando attach junto con el ID del contenedor.
Ejemplo
Acceso a contenedores mediante mapeo de puertos
Mapeo de puertos
El mapeo de puertos, o port mapping, asigna un puerto específico del host al puerto de un contenedor, lo que permite que una aplicación dentro del contenedor sea accesible desde el host o desde otros contenedores.
Ejemplo
El siguiente comando crea un contenedor de MongoDB y mapea el puerto 27017 del host al puerto 27017 del contenedor:
En este comando, -p mapea un puerto del host al puerto del contenedor, mongodb es el nombre del contenedor y mongo es la imagen utilizada.
Crear e iniciar un contenedor
El comando docker run combina los comandos docker create y docker start, realizando
los siguientes pasos:
- Busca la imagen especificada. Si no está disponible localmente, la descarga del repositorio.
- Crea un contenedor a partir de la imagen e inicia el contenedor.
Ejemplo
El siguiente ejemplo ejecuta un contenedor de MongoDB en segundo plano mapeando el puerto 27017:
Variables de entorno
Para conectar una base de datos con una aplicación dentro de Docker, se utilizan variables de entorno específicas para la imagen del contenedor.
Ejemplo
El siguiente ejemplo crea un contenedor de MongoDB con credenciales de administrador:
Estas variables configuran el usuario y la contraseña del administrador de la base de datos durante la inicialización del contenedor. Es importante revisar la documentación de cada imagen, ya que las variables de entorno varían según la imagen utilizada.
Dockerfile
Un Dockerfile es un archivo de texto con instrucciones que permiten construir una
imagen Docker personalizada. Cada imagen se construye sobre una imagen previa, que puede
ser oficial de Docker o una personalizada.
Ejemplo
Para construir una imagen a partir de un Dockerfile se utiliza el siguiente comando:
ENTRYPOINT y CMD
ENTRYPOINT define el comando base del contenedor, mientras que CMD proporciona
argumentos por defecto que pueden sobreescribirse al ejecutar el contenedor.
Ejemplo
Teniendo el siguiente Dockerfile:
Podemos utilizar el siguiente comando para hacer un sleep de 5 segundos, que es el valor por defecto definido en la imagen:
O podemos modificar el valor:
Redes en Docker
Para permitir la comunicación entre contenedores, es necesario configurar una red
interna. Docker permite crear redes personalizadas con el comando
docker network create mi-nueva-red, y los contenedores que pertenecen a la misma red
pueden comunicarse entre sí utilizando su nombre como dominio. Para crear un contenedor
en una red específica podemos utilizar el siguiente comando:
Docker ofrece diferentes modos de red:
| Tipo | Descripción |
|---|---|
| bridge | Red por defecto. Docker asigna IPs internas a cada contenedor. Los contenedores pueden comunicarse entre sí y se accede desde el host mediante mapeo de puertos. |
| host | El contenedor usa directamente la red del host, sin aislamiento de red. |
| none | Sin conectividad de red, completamente aislado. |
Además, se pueden crear redes personalizadas que permiten especificar el rango de
direcciones IP y otros parámetros. En lugar de usar --link (considerado legacy), se
recomienda crear redes definidas por el usuario, que proporcionan resolución de nombres
interna (los contenedores se referencian por nombre en lugar de por IP) y conectividad
automática entre todos los contenedores de la misma red.
Docker Compose
Docker Compose es una herramienta que permite definir y gestionar múltiples contenedores
como un conjunto de servicios interconectados. Utiliza un archivo de configuración
docker-compose.yml en formato YAML para especificar la configuración de los servicios,
redes, volúmenes y otros aspectos relacionados con los contenedores, simplificando la
gestión de aplicaciones complejas compuestas por varios contenedores.
Ejemplo
En este ejemplo se definen dos servicios: uno para la aplicación (mi-app), que se
construye a partir del contexto del directorio actual y mapea el puerto 3000, y otro
para MongoDB (mongodb), que utiliza una imagen preexistente, mapea el puerto 27017
y establece las credenciales de acceso mediante variables de entorno.
Para iniciar los servicios definidos en el archivo se ejecuta docker compose up, que
descarga las imágenes necesarias, crea los contenedores y los pone en funcionamiento.
Para detener y eliminar los servicios, incluidos los contenedores, redes y volúmenes
asociados, se utiliza docker compose down. Otros comandos útiles son
docker-compose scale servicio=num_instancias para escalar servicios y
docker-compose logs servicio para consultar los registros.
Se recomienda consultar el historial de versiones de Docker Compose para conocer las diferencias de sintaxis y mejoras entre versiones.
Persistencia de datos con volúmenes
En Docker, los volúmenes permiten la persistencia de datos en los contenedores. Esto significa que, incluso si un contenedor se elimina, los datos asociados a los volúmenes permanecen disponibles, lo cual resulta especialmente útil cuando se desea mantener información a través de reinicios o actualizaciones de contenedores.
Los volúmenes pueden ser de diferentes tipos. Los volúmenes anónimos carecen de nombre y no pueden referenciarse explícitamente desde otros contenedores. Los volúmenes de host permiten especificar qué carpeta del sistema anfitrión se monta dentro del contenedor. Los volúmenes nombrados disponen de un nombre y pueden referenciarse en otros contenedores o en múltiples servicios.
Para montar un volumen directamente desde la línea de comandos se utiliza la opción -v.
Ejemplo
Para mapear el directorio del host al directorio del contenedor, podemos utilizar el comando:
Warning
Para añadir un volumen a un contenedor existente, es necesario recrear el contenedor con el comando del volumen.
También podemos definir docker-compose.yml con volúmenes.
Ejemplo
En este ejemplo, el servicio mongodb utiliza un volumen nombrado llamado
mongo-data para almacenar los datos persistentes de la base de datos. Este volumen
se monta en el directorio /data/db del contenedor, lo que asegura que los datos de
MongoDB se conserven incluso si el contenedor es detenido o eliminado. Docker se
encarga de gestionar la creación y almacenamiento de dicho volumen de forma
completamente automatizada.
Todos los ficheros de Docker se encuentran en /var/lib/docker. Docker cachea las
capas intermedias de las imágenes, incluso entre diferentes Dockerfiles, lo que
ahorra espacio y tiempo de construcción. En ese mismo directorio podemos ver los
volumenes disponibles.
Registros de imágenes
Existen registros públicos y privados para almacenar y gestionar imágenes de Docker:
| Registro | Tipo | Características clave |
|---|---|---|
| Docker Hub | Público/Privado | El más grande, con imágenes oficiales y despliegues automatizados |
| Amazon ECR | Gestionado (AWS) | Integración con ECS, EKS y Fargate, con pago por uso |
| Azure Container Registry | Gestionado (Azure) | Geo-replicación y soporte de Helm charts |
| Google Artifact Registry | Gestionado (GCP) | Sucesor de GCR, con escaneo de vulnerabilidades y gestión IAM |
| GitHub Container Registry | Gestionado | Integrado con GitHub Actions para CI/CD |
| Harbor | Open source | RBAC y firmado de imágenes |
| JFrog Artifactory | Universal | Soporta Docker, Helm y otros formatos |