Desarrollo de microservicios con Spring Boot y Docker

5
43430

Siguiendo con la serie de tutoriales dedicados a Docker, vamos a ver cómo desplegar un microservicio desarrollado con Spring Boot en un contenedor Docker.
Posteriormente veremos como escalar y balancear este microservicio a través de HAProxy.

0. Índice de contenidos

1. Introducción

En el tutorial Introducción a los microservicios del gran José Luis
vimos una introducción al concepto de microservicios, cuales son sus ventajas e inconvenientes y cuando podemos utilizarlos.

El objetivo que perseguimos con el presente tutorial es desarrollar un microservicio con Spring Boot,
empaquetarlo dentro de una imagen Docker, dentro de la fase de construcción de maven, y una vez podamos
levantarlo, ver una posibilidad de escalabilidad gracias a Docker Compose y HAProxy.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 15' (2.3 GHz Intel Core i7, 16GB DDR3 SDRAM)
  • Sistema Operativo: Mac OS X El Capitan 10.11
  • Software: Docker 1.11.1, Docker Machine 0.7.0, Docker Compose 1.7.1
  • Software: Spring Boot 1.4.0.M3

3. El Microservicio

El objetivo del tutorial no es tanto el desarrollo de microservicios con Spring Boot, sino su empaquetamiento y despliegue,
por tanto vamos a implementar un microservicio
‘tonto’ cuya única funcionalidad es devolvernos un mensaje de hola.

El código lo podeís encontrar en mi cuenta de github aquí, lo primero que deberíamos implementar es un
@RestControler como el que describimos a continuación:

  package com.autentia;

  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RestController;

  @RestController
  public class MicroServiceController {


      private final AddressService service;

      @Autowired
      public MicroServiceController(AddressService service) {
          this.service = service;
      }

      @RequestMapping(value = "/micro-service")
      public String hello() throws Exception {

          String serverAddress = service.getServerAddress();
          return new StringBuilder().append("Hello from IP address: ").append(serverAddress).toString();
      }


  }

Como podemos observar es un ejemplo muy sencillo, hemos declarado un controlador rest, al cual le hemos inyectado un servicio que recupera la IP del servidor, y devuelve un string del tipo
«Hello from, IP address xx.xx.xx.xx»

La clase principal de nuestro microservicio encargada levantar un tomcat embebido con nuestro microservicio tendría un aspecto parecido a este:

  package com.autentia;

  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;

  @SpringBootApplication
  public class MicroServiceSpringBootApplication {

  	public static void main(String[] args) {

  		SpringApplication.run(MicroServiceSpringBootApplication.class, args);
  	}
  }

Podemos levantar nuestro servicio de la siguiente manera:

    mvn clean spring-boot run
  

01_spring-boot-run

Una vez levantado el microservicio podemos invocarlo de la siguiente manera:

 curl http://localhost:8080/micro-service
    

02_spring-boot-run

Hasta ahora nada impresionante … pasemos al siguiente punto

4. Dockerizar el microservicio

En este apartado vamos a ver como podemos ‘empaquetar’ nuestro microservicio dentro de un contenedor docker,
para ello vamos a usar el plugin de maven spotify/docker-maven-plugin.

Antes de meternos de lleno en el uso de este plugin, vamos a generar un Dockerfile de nuestro microservicio,
para ello nos creamos un directorio src/main/docker y creamos nuestro Dockerfile de la siguiente manera:

FROM frolvlad/alpine-oraclejdk8:slim
MAINTAINER jpacheco@autentia.com
ADD micro-service-spring-boot-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
  

Repasemos el Dockerfile:

  • FROM: Tomamos como imagen base frolvlad/alpine-oraclejdk8
    esta imagen está basada en Alpine Linux que es una distribución Linux de sólo 5 MB, a la cual se le ha añadido la OracleJDK 8.
  • ADD: Le estamos indicando que copie el fichero micro-service-spring-boot-0.0.1-SNAPSHOT.jar al contenedor con el nombre app.jar
  • EXPOSE: Exponemos el puerto 8080 hacia fuera (es el puerto por defecto en el que escuchará el tomcat embebido de nuestro microservicio)
  • ENTRYPOINT: Le indicamos el comando a ejecutar cuando se levante el contenedor, como podemos ver es la ejecución de nuestro jar

El siguiente paso en añadir el plugin a nuestro pom.xml de la siguiente manera



  <properties>
  	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  	<java.version>1.8</java.version>
  	<docker.image.prefix>autentia</docker.image.prefix>
  </properties>
   .....

   <plugins>
     <plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
     <plugin>
       <groupId>com.spotify</groupId>
       <artifactId>docker-maven-plugin</artifactId>
       <version>0.4.10</version>
       <configuration>
         <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
         <dockerDirectory>src/main/docker</dockerDirectory>
         <serverId>docker-hub</serverId>
         <registryUrl>https://index.docker.io/v1/</registryUrl>
         <forceTags>true</forceTags>
         <imageTags>
           <imageTag>${project.version}</imageTag>
         </imageTags>
         <resources>
           <resource>
             <targetPath>/</targetPath>
             <directory>${project.build.directory}</directory>
             <include>${project.build.finalName}.jar</include>
           </resource>
         </resources>
       </configuration>
       <executions>
         <execution>
           <id>build-image</id>
           <phase>package</phase>
           <goals>
             <goal>build</goal>
           </goals>
         </execution>
         <execution>
           <id>push-image</id>
           <phase>install</phase>
           <goals>
             <goal>push</goal>
           </goals>
           <configuration>
             <imageName>${docker.image.prefix}/${project.artifactId}:${project.version}</imageName>
           </configuration>
         </execution>
       </executions>
     </plugin>

En el apartado properties definimos:

  • docker.image.prefix: que indica el prefijo de la imagen a generar

En el apartado configuration de la sección de plugins definimos los siguiente parámetros :

  • imageName: Nombre de la imagen (prefijo + artifactId del proyecto)
  • dockerDirectory: Directorio en el que se encuentra el Dockerfile definido anteriormente
  • serverId: Identificador del registry de Docker (opcional: si queremos realizar un docker push a nuestro registry)
  • registryUrl: URL del registry de Docker (opcional: si queremos realizar un docker push a nuestro registry)
  • imageTag: Definimos las tags de nuestra imagen
  • resource: Le indicamos el recurso que vamos a empaquetar dentro de la imagen (‘targetPath’ path base de los recursos, ‘directory’ directorio de los recursos, ‘include’ incluimos el jar resultante )

En el apartado executions vinculamos los goals del plugin a las fases de maven:

  • build-image: Vinculamos a la fase package de maven, el goal docker:build que construye la imagen con el microservicio
  • build-image: Vinculamos a la fase de install de maven, el goal docker:push que sube nuestra imagen al registro de docker

Una vez configurado podemos ejecutar alguno de los goals del plugin:

 mvn clean package

03_spring-boot-build

Como se puede ver en los logs, después de realizar el empaquetado se construye la imagen. Comprobamos si se han generado la imagen:

 docker images

04_spring-boot-build

Podemos observar que en nuestro registro local, están disponibles tanto la imagen base de la que hemos partido frolvlad/alpine-oraclejdk8:slim,
como nuestra imagen con los tags 0.0.1-SNAPSHOT y latest. El siguiente paso es arrancar un contenedor a partir de nuestra imagen

 docker run -d -p 8080:8080 --name microservicio jpacheco/micro-service-spring-boot:0.0.1-SNAPSHOT

Con esto arrancamos nuestro contenedor, podemos comprobarlo ejecutando

docker ps

05_spring-boot-start

Una vez levantado el contenedor accedemos a nuestro servicio de manera análoga a la anterior sustituyendo ‘localhost’ por la IP de nuestro docker-machine

 curl http://192.168.99.100:8080/micro-service
  

06_spring-boot-start

Podemos observar que la IP que devuelve es la IP interna del contenedor 172.17.0.2.

El último paso que nos quedaría para completar el ciclo seria realizar el ‘push’ de nuestra imagen a nuestro docker registry (es este ejemplo usaremos docker-hub,
como hemos definido en el pom.xml con los parámetros: serverId, registryUrl), nos faltaría añadir nuestras credenciales de docker-hub en el settins.xml de maven

    <server>
      <id>docker-hub</id>
      <username>myuser</username>
      <password>mypassword</password>
      <configuration>
        <email>user@company.com</email>
      </configuration>
    </server>
  

Ya estamos listos para realizar un ‘push’ de nuestra imagen. Recordar que hemos vinculado el push a la tarea maven ‘install’

mvn install

Podemos acceder a nuestra cuenta de docker-hub y comprobar que se ha creado nuestra imagen

07_spring-boot-registry

La cosa se empieza a poner interesante … ya tenemos nuestro microservicio empaquetado en un contenedor
y disponible en nuestro registry

5. Escalando la solución

El siguiente paso que vamos a estudiar, es como podemos lanzar varias instancias de nuestro microservicio
y como podemos balancear el trafico entre ellas.

Para esto vamos a usar HAProxy que es una herramienta open source que actúa como balanceador de carga (load balancer) ofreciendo alta disponibilidad,
balanceo de carga y proxy para comunicaciones TCP y HTTP.

Como no podía ser de otra manera, vamos a usar Docker para levantar el servicio HAProxy
Para ello vamos a usar docker-compose para definir tanto el microservicio, como el balanceador

    microservice1:
      image: 'jpacheco/micro-service-spring-boot:latest'
      expose:
        - "8080"
    microservice2:
      image: 'jpacheco/micro-service-spring-boot:latest'
      expose:
        - "8080"
    loadbalancer:
      image: 'dockercloud/haproxy:latest'
      links:
        - microservice1
        - microservice2
      ports:
        - '80:80'
  

Como podemos ver en el fichero docker-compose.yml, hemos definido 2 instancias de nuestro microservicio (microservice1, microservice2) y un balanceador (loadbalancer) con enlaces a
los microservicios definidos anteriormente. Lo que conseguimos con esta imagen de HAProxy es exponer el puerto 80 y
redirigir a los 2 microservicios expuestos en el 8080 usando una estrategia round-robin.
Levantamos nuestro entorno con:

    docker-compose up -d
  

y podemos observar como se levantan los 3 contenedores

08_spring-boot-compose1

Vamos a invocar a nuestro microservicio a través del balanceador:

08_spring-boot-compose2

Como podemos observar en la consola, el balanceador va accediendo cada vez a una instancia del microservicio, logrando el balanceo de carga que íbamos buscando … No está mal no?, el ejemplo va tomando ‘cuerpo’.

Pero.. ¿y si queremos levantar más instancias de nuestro microservicio? ¿Tenemos que modificar el docker-compose.yml, y añadir ‘microservice3…microserviceN’?

Revisando la documentación de la imagen HAProxy encontramos una solución a esta problemática, la idea es levantar una primera instancia del balanceador y del microservicio
y posteriormente en función de las necesidades, levantar más instancias del microservicio y que el balanceador se reconfigure para añadirlas. Veamos como quedaría el docker-compose.yml

    version: '2'
    services:
       microservice:
        image: 'jpacheco/micro-service-spring-boot:latest'
        expose:
          - '8080'
       loadbalancer:
        image: 'dockercloud/haproxy:latest'
        links:
          - microservice
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
        ports:
          - '80:80'
  

Repasemos las lineas más destacadas:

  • version: ‘2’ estamos indicando que use la v2 de docker-compose (necesaria para este ejemplo)
  • service: Tag raíz del que cuelgan nuestros contenedores
  • microservice: loadbalancer Definición de nuestros contenedores
  • volumes: El contenedor de HAProxy necesita acceder al ‘docker socket’ para poder detectar nuevas instancias y reconfigurarse

Vamos a levantar nuestros contenedores:

  docker-compose -f docker-composeV2.yml up -d

09_spring-boot-compose3

Vemos como se han levantado una instancia del balanceador y otra del microservicio. Ahora vamos a escalar nuestro microservicio añadiendo 2 instancias más:

    docker-compose -f docker-composeV2.yml scale microservice=3
  

10_spring-boot-compose4

Comprobamos que se han creado 2 nuevas instancias de nuestro microservicio, ahora vamos a probar que estas instancias se han añadido al balanceador:

11_spring-boot-compose5

Como podemos ver, cada petición es atendida por una instancia distinta …. podríamos ir añadiendo instancias según vayamos necesitando, bastaría con ejecutar
docker-compose -f docker-composeV2.yml scale microservice=<Instancias_vivas+Nuevas>

6. Conclusiones

Como hemos podido ver a lo largo del tutorial, la combinación de Spring Boot y Docker nos permite desarrollar facilmente microservicios, incluso montar infraestructuras que
permitan su escalabilidad. El siguiente paso sería investigar la posibilidad de escalarlo a través de un cluster de máquinas usando herramientas como Docker Swarn
o Kubernetes, pero eso os lo dejo a vosotros 😉

Un saludo.

7. Referencias

5 COMENTARIOS

  1. ¡Muchas gracias por tu post! Siempre son de utilidad. Sin duda, tenemos que empezar a desenvolvernos con los microservicios. Son una tendencia que cada día más se están convirtiendo en realidad. A nosotros, Chakray, nos encanta WSO2 como opción para implementar microservicios en una gran empresa.

    De hecho, tenemos un ebook sobre cómo implementar una arquitectura de microservices con esta tecnología. ¡Espero que lo encontréis interesante!. Os dejo el link:

    http://www.chakray.com/conoce-como-implementar-una-arquitectura-de-microservices/

  2. Hola, quisiera saber que pasa con el consumo de RAM al empaquetarlo en un docker, lo digo por que un microservicio construido con spring-boot, al instanciar su propio contenedor consume alrededor de 500 a 600 mb, en un docker esto se incrementa?, es posible tener más de un microservicio en un docker?

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