Balanceando Apps de Spring Boot con NGINX en Docker

2
10647

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/):

$> docker run --name mysql -p 3306:3306 -v ${Ruta_Padre_Data}/data:/docker-entrypoint-initdb.d -e MYSQL_ROOT_PASSWORD=root -d mysql:5.6

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:

$> docker network create dbnet
$> docker network connect dbnet mysql

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.

$> java -jar sampleApp-0.0.1-SNAPSHOT.jar --nodo.numero=001

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’.

FROM centos:6.8

MAINTAINER ddelcastillo <ddelcastillo@autentia.com>
 
# Install packages
RUN yum install -y unzip wget curl git
 
# install Java 7
RUN su -c "yum --assumeyes install java-1.7.0-openjdk-devel"

# create Jar Folder
RUN su -c "mkdir -p /tmp/jar"
 
# Environment variables 
ENV HOME /root/tmp
ENV JAVA_HOME /usr/lib/jvm/java-1.7.0-openjdk.x86_64
ENV PATH $JAVA_HOME/bin:$PATH
 
VOLUME /tmp/jar
COPY ./jar/ /tmp/jar

VOLUME /tmp/jar/config
WORKDIR /tmp/jar

EXPOSE **10001**
 
ENTRYPOINT ["java","-jar","-Dserver.port=**10001**","/tmp/jar/app.jar","--nodo.numero=**001**"]

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:

worker_processes auto;
 
events { worker_connections 1024; }
 
http { 
    upstream node-app {
              least_conn;
              server 192.168.99.100:10001 weight=10 max_fails=3 fail_timeout=30s;
              server 192.168.99.100:10002 weight=10 max_fails=3 fail_timeout=30s;
              server 192.168.99.100:10003 weight=10 max_fails=3 fail_timeout=30s;
    }
 
    server {
        listen 80;
        server_name     test.com;
        location / {
            resolver           8.8.8.8 valid=300s;
            resolver_timeout   10s;
            proxy_pass         http://node-app;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }
}  

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:

 
version: "2"
networks:
  dbnet:
    external:
      name: dbnet
 
services:
    node1:
        build: 
          context: . 
          dockerfile: ./Dockerfile1
        networks:
          - dbnet
        ports:
          - 10001:10001
        volumes:
          - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/:/tmp/jar/
          - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/config/:/tmp/jar/config/
    node2:
      build: 
        context: . 
        dockerfile: ./Dockerfile2
      networks:
         - dbnet
      ports:
         - 10002:10002
      volumes:
         - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/:/tmp/jar/
         - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/config/:/tmp/jar/config/
    node3:
      build: 
        context: . 
        dockerfile: ./Dockerfile3
      networks:
        - dbnet
      ports:
        - 10003:10003
      volumes:
        - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/:/tmp/jar/
        - /Users/ddelcastillo/Downloads/sampleApp/src/main/resources/DockerNginx3Nodos/jar/config/:/tmp/jar/config/
    proxy:
      build:
        context:  ./nginx
        dockerfile: Dockerfile
      ports:
        - "80:80"
      links:
          - node1:node1
          - node2:node2
          - node3:node3

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):

$> docker-compose up -d

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

2 COMENTARIOS

  1. Muy bien, me surgen algunas dudas, siempre cuando una aplicación llega a un cierto punto , en el que ya no se puede optimizar, el siguiente paso era/es usar un cluster, pero ahora con docker, ¿como queda esto ? .

    * Veo que se hace un balanceo de carga pero esta en el mismo servidor, la mejor opción si usamos docker es tener un maximo de N nodos en una maquína fisica/virtual con docker y otros tantos nodos de docker en otra maquína fisica/virtual?

    * ¿ Este tipo de conexión entre instancias de docker existe?.

    * se que spring boot tiene embebido un contenedor de aplicaciones, pero también es posible ejecutarlo de forma clasica, ya que por ejemplo existen contenedores de aplicaciones empresariales que normalmente usan organizaciones grandes(jboss, was, etc) y allí, ¿como entra Docker?.

    Buena explicación.
    Saludos.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad