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
- 2. Entorno
- 3. Levantando un solo nodo
- 4. Balanceando 3 nodos con NGINX
- 5. Conclusiones
- 6. Referencias
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
- http://nginx.org/en/docs/http/load_balancing.html.
- https://distinctplace.com/2017/04/19/nginx-resolver-explained/.
- https://stackoverflow.com/questions/39067295/docker-compose-external-container.
- https://www.linode.com/docs/web-servers/nginx/configure-nginx-for-optimized-performance.
- https://www.digitalocean.com/community/tutorials/how-to-configure-nginx-with-ssl-as-a-reverse-proxy-for-jenkins.
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.
Buenas tardes Jesús.
Intento contestar a tus preguntas.
1) Tienes toda la razón, en el tutorial se utilizo la misma docker-machine para que no quedara muy largo. Para el tema de cluster entre nodos con docker se suele usar más Swam. Te recomiendo que ele eches un vistazo al tutorial de un compañero mio.
http://adictosaltrabajo.com/tutoriales/docker-compose-machine-y-swarm/
Puede que sea interesante rehacer el tutorial desde ese enfoque, y ya no quedaría tan pesado, ya que se apoyaría en el anterior.
2) Podrías optar por swarm como te indico en el apartado anterior o crear un red multihost (aunque no siempre se puede)
https://docs.docker.com/engine/userguide/networking/get-started-overlay/ (multihost)
3) Si se puede sin problema, depende que como empaquetes la aplicación de SprintBoot (con el tomcat o cualquier otro starter embebido o directamente el war).
Te dejo un enlace donde se explica.
http://servicesblog.redhat.com/2016/03/28/starting-with-devops-deploying-applications-on-your-docker-jboss-eap-image/
Muchas gracias por tu comentario. Seguro que de aquí salen futuros tutoriales.
Cualquier cuestión o aclaración, no dudes en preguntar.
Un saludo.