====== 02 - Docker II ======
En el tema anterior vimos lo básico para trabajar con Docker. En este tema, veremos aspectos más avanzados como la creación de imágenes, la gestión de las redes en Docker y una forma más sencilla de crear varios contenedores conectados entre sí.
===== Creación de imágenes =====
Hay veces que nos interesa crear imágenes si ninguna de las hay cumple todos nuestros requisitos. Este proceso se divide en dos partes:
* Creación del archivo de configuración de la imagen
* Construcción de la imagen
==== DockerFile ====
Para gestionar la configuración de una imagen de Docker, necesitamos crear un archivo especial llamado **Dockerfile**.
Un Dockerfile es un archivo de texto que contiene un conjunto de instrucciones para construir una imagen de Docker de manera automática. Cada instrucción en un Dockerfile ejecuta un comando en el sistema operativo base, y su resultado forma parte de la imagen final. Esto permite definir, de forma declarativa, todos los pasos necesarios para que un contenedor se configure y ejecute de la manera deseada, incluyendo la instalación de paquetes, la copia de archivos, la exposición de puertos y la definición del comando de inicio.
Un Dockerfile generalmente sigue la siguiente estructura:
1. FROM
2. LABEL
3. RUN
4. COPY / ADD
5. WORKDIR
6. ENV
7. EXPOSE
8. CMD / ENTRYPOINT
* ** FROM **: Establece la imagen base sobre la cual se construirá la nueva imagen. Toda imagen Docker comienza con esta instrucción. Se puede especificar una versión particular de la imagen base.
FROM ubuntu:20.04
* ** LABEL **: Se utiliza para agregar metadatos a la imagen, como el autor, la versión, una descripción, etc. Esto puede ayudar a identificar y gestionar las imágenes.
LABEL maintainer="tu_nombre@example.com" version="1.0"
* ** RUN **: Ejecuta comandos en la imagen durante la construcción. Suele utilizarse para instalar paquetes, configurar el entorno, o cualquier otra tarea que deba realizarse en la imagen.
RUN apt-get update && apt-get install -y nginx
* ** COPY / ADD **: Permiten copiar archivos desde el sistema local al sistema de archivos de la imagen. La diferencia principal es que ADD puede también manejar URL y archivos comprimidos.
COPY . /app
* ** WORKDIR **: Establece el directorio de trabajo para las siguientes instrucciones en el Dockerfile. Es útil para evitar escribir rutas largas en varias instrucciones.
WORKDIR /app
* ** ENV **: Se utiliza para definir variables de entorno que estarán disponibles en la imagen final y en los contenedores creados a partir de ella.
ENV NODE_ENV=production
* ** EXPOSE **: Configura el puerto en el que la aplicación dentro del contenedor está diseñada para escuchar. Aunque no publica el puerto fuera del contenedor, sirve como una referencia.
EXPOSE 8080
* ** CMD / ENTRYPOINT **: Definen los comandos que se ejecutará cuando se inicie un contenedor basado en la imagen. CMD proporciona un comando predeterminado, mientras que ENTRYPOINT establece un comando principal que no se puede sobrescribir fácilmente.
CMD ["nginx", "-g", "daemon off;"]
Así, un ejemplo completo de un Dockerfile podría ser:
# Usamos una imagen base de Ubuntu
FROM ubuntu:20.04
# Instalamos paquetes necesarios
RUN apt-get update && apt-get install -y \
curl \
vim
# Establecemos el directorio de trabajo
WORKDIR /app
# Copiamos los archivos de nuestra aplicación al contenedor
COPY . .
# Exponemos el puerto que usará la aplicación
EXPOSE 8080
# Definimos el comando que se ejecutará al iniciar el contenedor
CMD ["bash"]
Es importante que el archivo de configuración se llame exactamente Dockerfile, sin ninguna extensión. Docker busca automáticamente un archivo con este nombre para construir la imagen. Un nombre diferente o la inclusión de una extensión podría provocar errores o hacer que Docker no encuentre el archivo.
==== Docker build ====
Una vez que tenemos el Dockerfile con la configuración deseada, el siguiente paso es construir la imagen a partir de ese archivo. Para ello, se utiliza el comando **docker build**. Este comando procesa el Dockerfile y genera una imagen que luego puede ser utilizada para crear contenedores.
La sintaxis básica del comando es la siguiente:
docker build -t nombre_imagen:tag ruta_dockerfile
Donde:
* **-t nombre_imagen:tag**: Asigna un nombre y una etiqueta (tag) a la imagen que se va a construir. El nombre ayuda a identificar la imagen, y la etiqueta es opcional, pero se suele utilizar para especificar una versión (por ejemplo, v1.0 o latest). Si no se especifica una etiqueta, Docker asigna latest por defecto.
* **ruta_dockerfile**: Ruta al directorio que contiene el Dockerfile. Lo normal es estar en el directorio donde está el fichero Dockerfile por lo que sea pondrá un punto indicando que es el directorio actual.
Por ejemplo, si estamos en el mismo directorio donde tenemos nuestro Dockerfile, podríamos crear una imagen de la siguiente forma:
docker build -t mi_aplicacion:1.0 .
===== Gestión de redes =====
Docker incluye un sistema de redes que permite a los contenedores comunicarse entre sí y con el mundo exterior. Las principales ordenes para trabajar con redes de Docker son:
^ Comando ^ Descripción ^
| [[https://docs.docker.com/reference/cli/docker/network/create/|docker network create]] | Crea una red |
| [[https://docs.docker.com/reference/cli/docker/network/rm/|docker network rm]] | Elimina una red |
| [[https://docs.docker.com/reference/cli/docker/network/ls/|docker network ls]] | Lista las redes existentes |
| [[https://docs.docker.com/reference/cli/docker/network/connect//|docker network connect]] | Conecta un contenedor a una red |
| [[https://docs.docker.com/reference/cli/docker/network/disconnect/|docker network disconnect]] | Desconecta un contenedor de una red |
| [[https://docs.docker.com/reference/cli/docker/network/inspect/|docker network inspect]] | Muestra los detalles de una red |
==== Crear una red ====
Por defecto, Docker crea varias redes cuando se instala, y los contenedores nuevos se conectan automáticamente a una de estas redes según sea necesario.
Además, podmos crear nuestras propias redes para organizar los contenedores según las necesidades de nuestra aplicación. Esto se realiza con el comando **docker network create**:
docker network create mi_red
Esto crea una nueva red llamada mi_red, que será una red de tipo **bridge** por defecto. Los contenedores conectados a esta red pueden comunicarse entre ellos por nombre, usando el nombre del contenedor como si fuera un hostname.
Si queremos cambiar el tipo de red, podemos usar el parámetro **--driver** indicando el tipo de red:
docker network create --driver tipo_red mi_red
Los tipos de redes más comunes son:
* **bridge**: Red por defecto, donde los contenedores pueden comunicarse entre ellos en la misma red y con el host a través de NAT.
* **host**: Los contenedores comparten la pila de red con el host, sin aislamiento de red.
* **overlay**: Utilizado principalmente en configuraciones de Docker Swarm para permitir la comunicación entre varios hosts.
* **macvlan**: Permite asignar una dirección MAC a cada contenedor, haciéndolo aparecer como un dispositivo físico en la red.
* **none**: Desconecta el contenedor de todas las redes.
==== Añadir y eliminar contenedores de una red ====
Una vez que has creado una red, puedes añadir contenedores a esta red, tanto en el momento de su creación como después.
Para añadir un contenedor a una red en el momento de su creación utilizamos la opción **--network** con el comando **docker run**:
docker run -d --name mi_contenedor --network mi_red nginx
Este comando crea un contenedor llamado mi_contenedor basado en la imagen nginx y lo conecta a la red mi_red.
Para añadir un contenedor existente a una red usamos el comando **docker network connect**:
docker network connect mi_red mi_contenedor
Esto conecta el contenedor mi_contenedor a la red mi_red.
Para eliminar un contenedor de una red utiliza el comando **docker network disconnect**:
docker network disconnect mi_red mi_contenedor
Esto desconecta el contenedor mi_contenedor de la red mi_red.
==== Eliminar una red ====
Para eliminar una red, usamos el comando **docker rm**:
docker rm mi_contenedor
Docker no te permitirá eliminar una red si todavía hay contenedores conectados a ella. Si intentas hacerlo, recibirás un mensaje de error indicando que la red no puede ser eliminada porque está en uso.\\ \\
Para eliminar una red, primero debes desconectar todos los contenedores asociados a esa red o detener y eliminar los contenedores. También se puede utilizar el parámetro **-f** para forzar la eliminación de la red, desconectando automáticamente todos los contenedores que estén asociados a ella.
==== Inspeccionar una red ====
Para obtener información detallada sobre una red, como los contenedores conectados a ella, puedes utilizar el comando docker network inspect:
docker network inspect mi_red
Este comando muestra detalles como la configuración de la red, los contenedores que la utilizan, sus direcciones IP dentro de la red, y más:
[
{
"Name": "kea_dhcp-net",
"Id": "6cdc6ee7b4bc543dfb877d587579285dcc57bedc6289460b4f9bbb6b5b510956",
"Created": "2024-07-24T11:46:09.349440923+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.2.0/24",
"Gateway": "172.18.2.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"806bb109a752a2dcfd7f81fc531e516728a1f002a2c6e3890e25a6fbca7c56c3": {
"Name": "dhcp-mysql",
"EndpointID": "e2e2ceaccc99b91a84466808297e23fbeaac1c9d20134e34161d544fef9af03a",
"MacAddress": "02:42:ac:12:02:03",
"IPv4Address": "172.18.2.3/24",
"IPv6Address": ""
},
"80f038e5572004df1d566ec650be56a342f0e8e9e094f2b0d84d36c384981327": {
"Name": "dhcp-kea-server",
"EndpointID": "2fc6bbf08db6b5d29b8ffc9e4a788926ec54e255f661de7b026037cdb7df75d4",
"MacAddress": "02:42:ac:12:02:02",
"IPv4Address": "172.18.2.2/24",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "dhcp-net",
"com.docker.compose.project": "kea",
"com.docker.compose.version": "1.25.0"
}
}
]
===== Docker Compose =====
[[https://docs.docker.com/compose/|Docker Compose]] es una herramienta que permite definir y gestionar aplicaciones multicontenedor en Docker. Utiliza archivos de configuración en formato [[https://es.wikipedia.org/wiki/YAML|YAML]] para describir los servicios, redes y volúmenes que conforman una aplicación. Con un solo comando, puedes crear y arrancar todos los servicios definidos en el archivo, facilitando la orquestación de aplicaciones complejas.
Para instalarlo, basta seguir las instrucciones de su [[https://docs.docker.com/compose/install/|página oficial]]
==== Estructura básica de un archivo docker-compose.yml ====
Un archivo **docker-compose.yml** define la configuración de una aplicación multicontenedor. Aquí se presentan las secciones principales:
name: myapp
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
networks:
- mi_red
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ejemplo
MYSQL_DATABASE: mi_basededatos
volumes:
- db_data:/var/lib/mysql
networks:
- mi_red
networks:
mi_red:
volumes:
db_data:
* **name**: Se utiliza para definir el nombre del proyecto. Este nombre se utiliza para agrupar y gestionar los contenedores, redes y volúmenes asociados con el proyecto Docker Compose.
* **services**:Define los servicios o contenedores que forman parte de la aplicación. En este caso, hay dos servicios: web (basado en Nginx) y db (basado en MySQL).
* **web**:
* **image**: Define la imagen Docker que se usará para este servicio, en este caso nginx:alpine.
* **ports**: Especifica el mapeo de puertos, conectando el puerto 8080 del host con el puerto 80 del contenedor.
* **volumes**: Monta un volumen desde el sistema local (./html) a una ubicación en el contenedor (/usr/share/nginx/html).
* **networks**: Conecta el servicio a la red mi_red.
* **db**:
* **image**: Utiliza la imagen mysql:5.7.
* **environment**: Establece variables de entorno necesarias para la configuración de MySQL, como la contraseña del root y el nombre de la base de datos.
* **volumes**: Define un volumen persistente para los datos de la base de datos.
* **networks**: Conecta el servicio a la red mi_red.
* **networks**:Define las redes personalizadas que los servicios utilizarán. Aquí se crea una red llamada mi_red.
* **volumes**: Define los volúmenes que pueden ser utilizados por los servicios, como el volumen db_data para almacenar los datos de MySQL.
==== Comandos básicos de Docker Compose ====
^ Comando ^ Descripción ^
| [[https://docs.docker.com/reference/cli/docker/compose/up/|docker compose up]] | Inicia los servicios definidos en el archivo docker-compose.yml. Si los contenedores no existen, los crea; si ya existen, los inicia. Si se agrega el parámetro **-d** ejecutará los servicios en segundo plano (detached mode) |
| [[https://docs.docker.com/reference/cli/docker/compose/down/|docker compose down]] | Detiene y elimina los contenedores, redes y volúmenes (si se utiliza el párametro **-v**) creados por docker-compose up |
| [[https://docs.docker.com/reference/cli/docker/compose/start/|docker compose start]] | Inicia los contenedores que ya han sido creados por docker-compose up, pero que están detenidos. A diferencia de up, **no crea nuevos contenedores** |
| [[https://docs.docker.com/reference/cli/docker/compose/stop/|docker compose stop]] | Detiene los contenedores en ejecución, **pero no los elimina**. Los contenedores pueden reiniciarse con docker-compose start |
| [[https://docs.docker.com/reference/cli/docker/compose/ps/|docker compose ps]] | Muestra el estado actual de los contenedores gestionados por Docker Compose, indicando si están en ejecución, detenidos, etc. |
===== Ejercicios =====
** Ejercicio 1 **
* Crea una imagen de Docker basada en php:8.0-apache que copie el contenido de un archivo index.html en la carpeta /var/www/html del contenedor. El contenido del archivo será:
Hello World!
* Además, actualiza el SO e instala el editor de texto Nano
* Construye la imagen con **docker build** y asígnale el nombre **smr_apache**, mapeando el puerto 11211 local al 80 del contenedor.
* Ejecuta un contenedor basado en la imagen creada y verifica que puedes ver la web en el navegador al acceder al puerto 11211.
* Entra dentro del contenedor y comprueba que puedes editar el archivo index.html con Nano
** Ejercicio 2 **
* Crea una imagen basada en Mysql con **smr** como password de root
* Construye la imagen con el nombre smr_mysql
* Crea otro contenedor basado en phpmyadmin. Añade como parámetros:
PMA_ARBITRARY=1
UPLOAD_LIMIT=64
* Construye esta segunda imagen con el nombre smr_phpmyadmin
* Crea una red Docker llamada **smr_red**
* Construye dos contenedores basados en las imágenes anteriores y conéctalos a la red recién creada. Los nombres serán **mysql_server** y **phpmyadmin**. Además, el contenedor phpmyadmin mapeará el puerto local 22322 al 80 del contenedor
* Abre tu navegador y accede a http://localhost:22322. Comprueba que se carga la página de login de phpmyadmin y que puedes acceder a ella (el nombre del servidor será el del contenedor de Mysql)
* Inspecciona la red y comprueba que ambos contenedores están conectados a la misma
** Ejercicio 3 **
* Modifica el ejercicio anterior para hacer todo con Docker Compose
* Crea un volumen para persistir los datos de Mysql
Si quieres ampliar tus conocimientos sobre Docker, puedes bajarte la imagen [[https://hub.docker.com/r/docker/getting-started|getting-started]] y seguir su tutorial