8. Despliegue avanzado en Servidor

En el tema anterior vimos lo básico para empezar a usar Docker. En este tema vamos a ver aspectos mas avanzados de Docker y trucos a tener en cuenta para usarlo.

Mas información:

An Updated Performance Comparison of Virtual Machines and Linux Containers

Build your own images

Docker for dummies

Logs en Docker

10 Examples of how to get Docker Container IP Address

Creación de imágenes

Podemos crear nuestras propias imágenes si ninguna de las que hay hace lo que nosotros necesitamos. Para ello usaremos el comando docker buildx build.

El comando docker buildx build se utiliza para construir imágenes de contenedor Docker a partir de un Dockerfile. A continuación se presentan las principales características y uso de este comando.

Donde:

--tag nombre_imagen:etiqueta: Nombre y tag de la imagen que se va a crear

--file ruta_del_dockerfile: Ruta al fichero que contiene el Dockerfile. Si no se indica se usará el fichero Dockerfile del directorio actual.

ruta_contexto_ficheros: Ruta donde se encuentran los ficheros a copiar dentro de la imagen. Esto es importante ya que las rutas absolutas del COPY empiezan en /ruta/al/contexto

Ejemplo de uso: Crea la imagen llamada mi_aplicacion_web:1.0 sabiendo que el fichero Dockerfile está en la carpeta actual y los ficheros a copiar están en el directorio actual.

Estructura Básica del Dockerfile

Un Dockerfile contiene instrucciones para la construcción de una imagen. Aquí se muestra una estructura básica:

- FROM: Indica la imagen base desde la cual se construirá la nueva imagen. La versión es opcional.

- RUN: Ejecuta comandos en una nueva capa del contenedor. Se utiliza para instalar paquetes, configurar el entorno y realizar otras acciones durante la construcción de la imagen.

- COPY: Copia archivos desde la ruta de origen en la máquina host hacia el contenedor en la ruta de destino.

La ruta absoluta del origen del copia es a partir de la ruta ruta_al_contexto del comando de buildx

- EXPOSE: Informa a Docker que el contenedor escuchará en el puerto especificado en tiempo de ejecución. No publica el puerto en el host.

- WORKDIR: Establece el directorio de trabajo para cualquier instrucción posterior en el Dockerfile.

- ENV: Define variables de entorno dentro del contenedor.

- CMD: Especifica el comando predeterminado que se ejecutará cuando el contenedor se inicie.

Las diferencias entre RUN y CMD es que RUN se ejecuta cuando se crea la imagen con el comando docker build ,es decir configurándola mientras que CMD se ejecuta cuando se crea el contenedor con el comando docker container run

Ejemplo: Ejecutar app java

Una imagen basada en el JDK 17

Copia el fichero de la máquina real que está en /opt/peliculas/target/japp.jar dentro de loa imagen en la carpeta /opt/app

Ejecuta el japp.jar

Docker Compose

Docker Compose es una herramienta que permite definir y administrar aplicaciones Docker de múltiples contenedores. Utiliza un archivo YAML para configurar los servicios, las redes y los volúmenes, facilitando la definición y el despliegue de aplicaciones complejas.

Estructura Básica de un archivo docker-compose.yml

Un archivo docker-compose.yml define la configuración de una aplicación multi-contenedor. Aquí se presentan las secciones principales:

Comandos Básicos de Docker Compose

Iniciar la aplicación basada en la configuración de docker-compose.yml.

Detener y eliminar los contenedores definidos en el archivo docker-compose.yml.

- Muestra el estado de los contenedores definidos en el archivo docker-compose.yml.

Ejemplo de Uso

Supongamos un archivo docker-compose.yml para una aplicación web con un servicio de frontend y otro de base de datos:

Para iniciar esta aplicación, ejecutamos:

Esto levantará los contenedores de frontend y base de datos con la configuración especificada.

Docker Compose simplifica la gestión de aplicaciones Docker complejas, permitiendo definir y coordinar múltiples servicios con facilidad.

Para pasar un script de docker al formato de docker compose se puede usar la herramienta: https://it-tools.tech/docker-run-to-docker-compose-converter

Estructura de ficheros

Una posible estructura de los fichero sería esta:

/FacturaDespliegue
docker-compose.yml
deploy.sh
/mysql
/volumes
/data
/logs
/build
Dockerfile
/src
my.cnf
/java
/volumes
/logs
/build
Dockerfile
/src
/opt
/app
facturas.jar
/compilador
/volumes
/app
pom.xml
/src
/scripts
/build
Dockerfile
/src
/opt
/java
....
/maven
...
/app

Dockerfile

Ejecutarlo todo:

Gestión de Redes

Docker permite crear redes aisladas entre los distintos contenedores. De esa forma , si un contenedor es atacado, no se tendrá acceso por red al resto de los contenedores sino únicamente a los de su propia red.

Orden	Explicación
docker network create	Crea una red
docker network rm	Borrar una red
docker network ls	Ver las redes que hay
docker network connect	Conecta un contenedor a una red
docker network disconnect	Desconecta un contenedor de una red

Para trabajar con redes, lo que debemos hacer es

Primero crear la red:

docker network create nombreDeLaRed

Conectar un contenedor a esa red

docker network connect nombreDeLaRed nombreDelContenedor

También podemos conectar la red al contenedor al usar docker container run con la opción --network=nombreDeLaRed

docker container run \
-dit \
-v /opt/mariadb:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-p 4000:3306 \
--network nombreDeLaRed \
--name prueba_mariadb \
mariadb:10.1

Proxy con Nginx

¿Recuerdas la cabecera Host de HTTP? Servía para indicar a que servidor web iba dirigida la petición ya que en una misma máquina se ponían varios servidores web.

Pues vamos ahora a configurar Docker para que todos los servidores compartan el mismo puerto (el 80). Para ello vamos a usar un servidor proxy llamado Nginx. Obviamente, Nginx va estar dentro de otro contenedor de Docker. La imagen a usar es jwilder/nginx-proxy con tag 0.7.0.

Para arrancar el servidor usamos la siguiente orden:

sudo docker container run \
-dit \
-p 80:80 \
--restart always \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
--name nginx-proxy \
nginxproxy/nginx-proxy:1.1.0

Como vemos no tiene nada de especial excepto la línea -v /var/run/docker.sock:/tmp/docker.sock:ro pero solo decir que es para que Nginx pueda acceder al Docker real de la máquina real.

Y ahora , ¿Como arrancamos nuestro servidores web? Pues simplemente hay que añadir dos parámetros y quitar uno.

Y hay que quitar el parámetro de -p puertoExterno:puertoInterno ya que ahora el contenedor no se va a exponer al exterior.

Hay que añadir:

-e VIRTUAL_PORT=puertoInterno: Siendo puertoInterno donde está escuchando nuestro servidor Web. Esa información la sabremos por la documentación

-e VIRTUAL_HOST=nombreDominio: Siendo nombreDominio el nombre del dominio que queremos que gestione el servidor que estamos arrancando.

Veamos el siguiente ejemplo:

sudo docker container run \
-dit \
-v /home/alumno/lorenzo/httpd:/var/www/html \
-e VIRTUAL_PORT=80
-e VIRTUAL_HOST=lorenzo.daw2.pve3.fpmislata.com \
--name lorenzo_apache \
php:8.1-apache-bullseye

Vemos que el servidor está escuchando en el puerto 80 y va a gestionar las peticiones del dominio lorenzo.daw2.pve3.fpmislata.com que lleguen a Nginx.

Los servidores de Base de datos u otros contenedores que no son el servidor web no tienen que hacer nada especial por usar Nginx ya que no se accede a ellos a través del proxy sino directamente a ellos o únicamente de forma interna.

Mas información:

Running an NGINX Reverse Proxy with Docker and Let's Encrypt on Google Compute Engine

Automated Nginx Reverse Proxy for Docker

How to setup NGINX with automatic HTTPS in Docker

Host multiple websites with HTTPS on a single server

El fichero hosts

Ahora para que funcione el proxy con nginx ya no podemos acceder a la máquina real con la IP sino que habrá que usar el nombre de dominio. Si no tenemos acceso a un nombre de dominio real, podemos simular nosotros uno modificando el fichero hosts de la máquina donde vayamos a hacer la petición.

El fichero hosts simplemente contiene IPs y nombre e dominio que usará nuestro ordenador. De esa forma simularemos tener un dominio.

En los siguientes artículos se explica como modificar el fichero hosts

Archivo hosts

¿Cómo se usa el archivo “hosts”?

Ejercicios

Ejercicio 1:Crear la imagen

Crea una imagen de docker basada en eclipse-temurin:17.0.10_7-jdk que haga lo siguiente:

Con RUN:Crear el directorio /opt/app.

Con CMD:Que ejecute un jara llamado japp.jar

La imagen se llamará tu-nombre-java-app.

Crea un contenedor con esa imagen de forma que con -v esté el japp.jar en /opt/app

Ejercicio 2:Sin crear imagen

En la imagen eclipse-temurin:17.0.10_7-jdk ya existe la carpeta /tmp y en docker container run se puede poner al final de la línea la orden a ejecutar.

Así que ejecuta un jar de la siguiente manera:

En la orden docker container run faltan más parámetros que hay que añadir.

Ejercicio 3:Maven

Ahora vamos a generar el jar en base al código fuente, para ello vamos a usar una imagen que ya tiene maven instalado: Docker image maven y concretamente maven:3.9.6-eclipse-temurin-17.

Para hacerlo hay que poner el código fuente en /usr/src/mymaven y especificar que el directorio de trabajo será ese, para ello usaremos el argumento -w. Al final del todo indicaremos la orden de maven a ejecutar.

En la orden docker container run faltan más parámetros que hay que añadir.
¿Que opciones usarás? -d -i -t ????

Ejercicio 4:Deploy

Has un script en la máquina real llamado deploy.sh que haga lo siguiente:

Se baje el código fuente de git

Genere el jar pero se ejecutará maven dentro del contenedor de docker cuya imagen es: maven:3.9.6-eclipse-temurin-17

Ejecute el jar dentro del contenedor de docker cuya imagen es: eclipse-temurin:17.0.10_7-jdk

Ejercicio 5:MySQL

Crea ahora una base de datos MySQL en un contenedor docker de forma que sea la que estás usando en el módulo de servidor. Rellena la base de datos con los datos.

Ejercicio 6:Deploy proyecto

Compila y desplega el proyecto que usas en el módulo de servidor de forma que se genere el jar en un contenedor de docker y se ejecute en un contenedor de docker. Este último contenedor deberá conectarse al contenedor de MySQL que has creado.

Ejercicio 7:Deploy proyecto

Modifica el proyecto de Maven de forma que tambien se haga lo necesario para "compilar" la parte de Angular. Esa parte la vimos en el tema 3 en el apartado de maven

Hasta aquí han sido los ejercicios para el despliegue automático, ahora vamos a mejorar cuestiones relacionadas con Docker.

Ejercicio 8: MySQL compañero

Prueba ahora a modificaar el código de forma que te conectes al MySQL de tu compañero. Deberá dejarte.

Ejercicio 9:Red

Crea ahora una red llamada mi-nombre-mi-proyecto.

Modifica la creación de los contenedores para que estén asociados a la red que acabas de crear.

Prueba que todo sigue funcionando.

Ejercicio 10: MySQL compañero

Prueba ahora a modificaar el código de forma que te conectes al MySQL de tu compañero. No debería dejarte ya que estáis en redes distintas.

Ejercicio 11: Docker compose

Modifica el script deploy.sh de forma que en vez de crear directamente los 2 contenedores de MySQL y Java se use docker compose up.

Para ello deberás crear un fichero llamado docker-compose.yml