1. Introducción
Hola a todos y bienvenidos a un nuevo post de nuestro querido blog! En esta ocasión veremos como instalar un proxy inverso en Docker con el fin de balancear la carga entre las peticiones que llegan desde internet a los microservicios que podamos tener dentro de la red interna de nuestra organización. En el post Spring Cloud Zuul analizamos como llevar a cabo la creación de un proxy inverso dentro del universo de Spring Cloud. Sin embargo en esta ocasión nos apoyaremos en Nginx. Veremos como hacerlo de dos formas. En primer lugar haciéndolo manualmente configurando el servidor nginx a través de su fichero de configuración default.conf. Y en segundo lugar haciéndolo dinámicamente haciendo uso de la imagen docker jwilder/nginx-proxy
que realizará la configuración del balanceo de forma agnóstica teniendo en cuenta el número de instancias de un servicio dentro de Docker.
Podéis ampliar información sobre el servidor nginx y el balanceo de carga haz click aquí
Además os podéis descargar el código de ejemplo de mi GitHub aquí.
Tecnologías empleadas:
- Docker 17.06.2-ce
- jwilder/nginx-proxy
- nginx
- Java 8
- Spring Boot 1.5.7
- Gradle 3.1
2. Antes de empezar…
Vamos a crear una imagen Docker de un microservicio de SpringBoot de la misma forma que se hizo en el post Spring Boot Docker con el fin de arrancar varias instancias para proceder a realizar el balanceo de carga sobre éstas.
group 'com.jorgehernandezramirez.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.7.RELEASE") classpath('se.transmode.gradle:gradle-docker:1.2') } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'maven' apply plugin: 'docker' sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.springboot.reverseproxy.Application" } repositories { mavenCentral() } dependencies { compile("org.springframework.boot:spring-boot-starter-web") } task buildDocker(type: Docker, dependsOn: build) { push = project.hasProperty('push') applicationName = jar.baseName tag = 'jorgehernandezramirez/spring-boot-docker-reverse-proxy' dockerfile = file('src/main/docker/Dockerfile') doFirst { copy { from jar into stageDir } } }
Fichero Dockerfile que utilizará el plugin gradle-docker
para proceder a construir la imagen.
FROM frolvlad/alpine-oraclejdk8:latest VOLUME /tmp ADD reserveproxy-1.0-SNAPSHOT.jar app.jar RUN sh -c 'touch /app.jar' ENTRYPOINT [ "sh", "-c", "java -jar /app.jar" ]
Main que arranca SpringBoot
package com.jorgehernandezramirez.springboot.reverseproxy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Exponemos la url /random que devolverá un número aleatorio que se genera en el arranque del contexto de Spring y que nos permitirá identificar en qué contenedor nos encontramos
package com.jorgehernandezramirez.springboot.reverseproxy.rest; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class RandomController { private Double randomValue; public RandomController(){ //Para Spring } @PostConstruct public void initialization(){ this.randomValue = Math.random(); } @RequestMapping("/random") public Double getRandomValue() { return randomValue; } }
Cronstruimos nuestra imagen docker y la almacenamos dentro del registro de imágenes locales
Verificamos finalmente que se creado la imagen correctamente
3. Configuración manual del proxy inverso – nginx
Antes de empezar arrancamos dos instancias de nuestro microservicio de forma manual de tal forma que una escuche en el puerto 8080 y la otra en el puerto 8081. Para ello hacemos
java -jar -Dserver.port=8080 build/libs/reserveproxy-1.0-SNAPSHOT.jar java -jar -Dserver.port=8081 build/libs/reserveproxy-1.0-SNAPSHOT.jar
Se muestra el fichero docker-compose.yml en donde se define el servidor nginx. Además se monta la configuración en el directorio /etc/nginx/conf.d
Para llevar a cabo el balanceo de carga es necesario utilizar la directiva upstream
que nos permitirá definir las diferentes instancias que tenemos arrancadas de nuestro microservicio. En nuestro caso una escucha en el puerto 8080 y la otra en el puerto 8081
upstream miupstream { server 192.168.1.34:8080; server 192.168.1.34:8081; } server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location /proxy { proxy_pass http://miupstream/; } }
Nótese que debemos atacar al subpath /proxy
para que se redirija al proxy inverso definido en el upstream
Arrancamos el servidor
Atacamos al proxy inverso sucesivas veces a la url /random
obteniendo en ocasiones el número aleatorio generado por un contenedor
0.2152137939205018
y obteniendo en ocasiones el número aleatorio generado por el otro contenedor
0.7152468169848423
4. Configuración dinámica del proxy inverso – jwilder/nginx-proxy
El principal problema que tenemos en la configuración manual es que necesitamos conocer previamente el número de instancias de nuestro servicio de tal forma que si queremos añadir una nueva, el proxy inverso necesitaría una actualización de su fichero de configuración y un reinicio para contemplarlas. Lo que se pretende ahora es que la creación y borrado de nuevas instancias sea agnóstica para nosotros, de tal forma que el proxy inverso pueda cambiar su configuración dinámicamente. Para ello haremos uso de la imagen jwilder/nginx-proxy
.
Se muestra el fichero docker-compose.yml en donde se define el proxy inverso (jwilder/nginx-proxy) y los microservicios en Spring Boot.
my-spring-boot-example: image: jorgehernandezramirez/spring-boot-docker-reverse-proxy:1.0-SNAPSHOT ports: - "8080" environment: VIRTUAL_HOST: 'localhost' nginx: image: jwilder/nginx-proxy volumes: - "/var/run/docker.sock:/tmp/docker.sock:ro" ports: - "80:80"
Es importante destacar los siguientes aspectos en la configuración:
- Se monta el fichero docker.sock en modo lectura sobre el proxy inverso. Dicho fichero contiene información relevante sobre los contenedores que se encuentran arrancados dentro de Docker y servirá para que nuestro proxy inverso pueda realizar la configuración adecuada sobre el fichero de configuración de nginx que se debe encontrar dentro del filesystem del contenedor en
/etc/nginx/conf/default.conf
- Los contenedores sobre los que queremos que se realice el balanceo deberán establecer a través de la variable de entorno
VIRTUAL_HOST
el nombre del upstream para el que se quiere que se configure dicho balanceo.
Arrancamos dos instanacias del servicio my-spring-boot-example y una del servicio nginx
Atacamos al proxy inverso sucesivas veces a la url /random
obteniendo en ocasiones el número aleatorio generado por un contenedor
0.1152737919206015
y obteniendo en ocasiones el número aleatorio generado por el otro contenedor
0.6159248969818463