Balanceando Apps de Spring Boot con NGINX en Docker

En este artículo se hará uso de un NGINX para balancear 3 nodos desplegando una App de Spring-Boot en cada nodo.

Índice de contenidos

1. Introducción

La motivación de este tutorial surgió porque queríamos hacer una maqueta de la arquitectura de un cliente con docker, lo más real posible. La arquitectura a maquetar con docker era la siguiente:

¡Vamos allá!

2. Entorno

El tutorial está escrito usando el siguiente entorno:
  • Hardware: Portátil MacBook Pro Retina 15′ (2,5 Ghz Intel Core i7, 16GB DDR3)
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Entorno de desarrollo: Eclipse Neon.2 Release (4.6.2)
  • Docker version 17.03.1-ce, build c6d412e
  • Docker Machine version 0.10.0, build 76ed2a6
  • Docker Compose version 1.11.1, build 7c5d5e4

3. Levantando un solo nodo

Como una primera aproximación, vamos a levantar un contenedor (solo un nodo) que despliegue nuestra app, la cual se conecta a una BBDD que esta en otro contenedor.

Nuestra app simplemente hará una select y mostrará un mensaje diferente dependiendo del nodo que realice la select. El código de la app lo tenéis en mi repositorio de github.

Para levantar la BBDD bastaría con ejecutar el siguiente comando desde un terminal (posicionados en el directorio padre en ./src/main/resources/DockerNginx3Nodos/ del directorio /data/):

En el contenedor de BBDD tendremos una tabla que será de donde sacará el mensaje a mostrar cada nodo.

Una vez creado el contenedor de BBDD, necesitamos que pertenezca a una red. Los futuros nodos podrán hacer uso de la BBDD si también pertenecen a esta red. Los comando necesarios para crear la red y añadir el contenedor de BBDD son los siguientes:

Para esta primera aproximación y ver que nuestra app conecta con la BBDD, bastará con ejecutar el siguiente comando desde el directorio target de nuestro proyecto.

Si todo ha ido bien, al acceder a esta url nos debería mostrar el siguiente mensaje:

4. Balanceando 3 nodos con NGINX

El objetivo de un balanceador es ofrecer un punto de entrada y distribuir la carga entre los diferentes nodos a balancear. Como estamos trabajando con docker y para no tener que crear una máquina docker por cada nodo, vamos a desplegar cada contenedor en una única máquina docker (IP 192.168.99.100) desplegándose cada App en diferentes puertos, tal como se puede ver en la siguiente imagen.

4.1. Dockerfile de cada nodo

Para nuestro ejemplo, nuestros nodos van a tener las siguientes características:

  • CentOS release 6.8 (Final)
  • OpenJDK version “1.7.0_131” 64-Bit

Para levantar cada contenedor en el puerto indicado, bastaría con modificar el Dockerfile que se muestra a continuación, poniendo el puerto en las partes marcadas entre asteriscos y establecer el ID_NODO equivalente en la propiedad ‘nodo.numero’.

Haciendo estos cambios, tendremos 3 ficheros Dockerfile (uno por cada nodo).

4.2. Un vistazo al nginx.conf

Nuestro fichero de configuración de NGINX es el siguiente:

A continuación, comentaremos alguna de las propiedades de aparecen en el fichero:

  • Upstream: Con la directiva upstream definimos un pool de servidores. En cada servidor podemos definir propiedades como timeout para el fallo, número de intentos, peso (capacidad), etc. También podemos definir el mecanismo de balanceo (round-robin, least-connected, ip-hash). En nuestro caso, hemos elegido least-connected, donde la petición se asignará al servidor con el menor número de conexiones activas.
  • Con respecto a las propiedades del servidor de NGINX destacamos:
    • Listen: Puerto en el que escucha el servidor.
    • Resolver: Se le fija en servidor DNS.
    • Pass_proxy: Indicamos que todas las peticiones hechas a / serán gestionadas por el pool de servidores.
    • Proxy_redirect: Al indicarle off, estamos indicando que no utilizaremos un proxy inverso.
    • Proxy_set_header: Con esta directiva podemos establecer diferentes cabeceras con información del proxy.

4.3. Docker-compose final

Nuestro docker-compose.yml quedaría de la siguiente forma:

Lo más destacable de este docker-compose.yml podría ser lo siguiente:

  • Como se ha declarado la BBDD e incluido a cada nodo dentro de su red. No hemos incluido la BBDD en el compose (aunque se podría), puesto que ya la había creado el contenedor e incluido a la red en el apartado 3.
  • El mapeo de puertos, tanto para la parte de docker (docker se expone la app) como para la parte host (que tiene que cuadrar con los puertos puestos en el pool se servidores en el nginx.conf)
  • Cada nodo, como se ha dicho en el apartado 4.1, tiene su propio Dockerfile
  • Como enlazamos los cada nodo en el balanceador.
  • Los volumenes compartidos, que corresponder al jar de la app de Spring Boot a desplegar y el application.yml respectivamente.

4.4. Un balanceador dockerizado funcionando

Ya solo nos queda levantar nuestro contenedores y ver que nuestro nginx balancea correctamente con el siguiente comando (desde src/resources/DockerNginx3Nodos):

Si accedemos a esta url podemos ver que nos responde:

Y si realizamos muchas peticiones, vemos que cambia a:

Vemos que nuestro balanceador funciona correctamente si miramos las trazas en el terminal (ejecutando el comando anterior sin -d)

5. Conclusiones

Con este tutorial me he dado cuenta de la cantidad de posibilidades que tiene docker, dándonos la posibilidad de maquetar casi cualquier arquitectura (teniendo en cuenta la penalización en el rendimiento).

También me ha servido para conocer un poco más de cerca un balanceador como NGINX.

6. Referencias