viernes, 24 de agosto de 2018

Laboratorio - Integración Contínua (VII)


Séptima entrada en la creación de nuestro laboratorio CI. Recuerdo los pasos:
  1. Instalación de Ubuntu en VMWare Player en Windows 7
  2. Instalación de Jenkins en Ubuntu
  3. Instalación GitLab en Ubuntu
  4. Instalación de Docker Minikube en Ubuntu
  5. Desarrollo de un microservicio en Eclipse Windows (1) y (2)
    1. Angular 6 frontend
    2. SpringBoot 2.x backend
    3. Mongo Replicaset (3 replicas)
  6. Despliegue CI en Minikube utilizando la infraestructura configurada
    1. Pipeline - Jenkinsfile
    2. GitLab webhook
    3. Dockerfile
    4. Deploy y Service yaml
    5. Secret
    6. Ingress
Seguimos en el paso 5. En la anterior entrada establecimos la estructura base. Asimismo indicamos cómo enlazar el contenido angular estático con el contenido dinámico de spring boot, todo ello en un único artefacto. Esto nos facilitará las cosas cuando debamos desplegar nuestra aplicación en un entorno cloud, minikube en nuestro caso.

En esta nueva entrada vamos a finalizar los entornos "loc" (normal de toda la vida) y "des" (local con docker). Incorporaremos el código necesario para realizar la gestión de usuarios prometida. Mostraremos el Dockerfile y docker-compose y lanzaremos una prueba en un entorno docker local.

En una próxima entrada detallaremos la configuración necesaria para abordar el paso final de despliegue en minikube.

El código compartido en GitLab lo hemos actualizado para dar cumplimiento a esta entrada.

Empecemos que hay trabajo.


Nueva Configuración
Detallamos la nueva configuración de los tres proyectos, ci-root, ci-backend y ci-frontend

ci-root
Añadimos las siguientes properties en el pom.xml

    <swagger.version>2.9.2</swagger.version>
    <mongeez.version>0.9.6</mongeez.version>
    <flapdoodle.version>2.0.3</flapdoodle.version>
    <docker.folder>docker</docker.folder>

Incorporamos swagger para poder acceder vía web-ui a los diferentes endpoints.
Incorporamos mongeez para una sencilla ejecución de sentencias DDL y DML sobre mongo.
Incorporamos flapdoodle para proporcionar mongo embebido en el entorno "loc".

Para lanzar los comandos docker en el entorno "des" hemos configurado algunos profiles maven, que junto a un .bat nos va a facilitar la ejecución sin salir de eclipse.

Veamos un ejemplo para el profile "docker-compose-up". El resto es muy semejante:

<profile>
  <id>docker-compose-up</id>
  <build>
  <plugins>
  <plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.1</version>
  <executions>
  <execution>
  <id>up</id>
  <phase>pre-integration-test</phase>
  <goals>
  <goal>exec</goal>
  </goals>
  </execution>
  </executions>
  <configuration>
  <executable>${project.basedir}/${docker.folder}/Docker.bat</executable>
  <arguments>
  <argument>up</argument>
  </arguments>
  </configuration>
  </plugin>
  </plugins>
   </build>
</profile>

Invocaremos el profile de la forma habitual, bien desde eclipse (lo aconsejable):


bien línea de comandos: mvn exec:exec -P docker-compose-up

Para que funcione nos falta una cosa, el archivo .bat. En la carpeta "docker" hemos de ubicar el fichero "Docker.bat". Debe tener una "entrada" por cada profile. Para el caso del "docker-compose-up" sería:

    parte común:

    @echo off
    set accion=%1
    
    rem #####
    rem se ha de especificar el nombre de docker-machine que se desee
    rem #####
    
    set machine=NOMBRE_MACHINE
    echo INICO
    echo accion=%accion%
    echo docker-machine=%machine%

    IF "%accion%"=="create" goto create
    IF "%accion%"=="remove" goto remove
    IF "%accion%"=="start" goto start
    IF "%accion%"=="stop" goto stop
    IF "%accion%"=="up" goto up
    IF "%accion%"=="down" goto down

    parte específica:

    :up
    echo DESPLEGANDO APLICACION EN DOCKER-MACHINE %machine%
    @FOR /f "tokens=*" %%i IN ('docker-machine env %machine%') DO @%%i
    docker-compose up -d --build
    for /f %%i in ('docker-machine ip %machine%') do set IP=%%i
    echo $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
    echo APLICACION ARRANCADA EN http://%IP%
    echo $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
    goto fin

    :fin
    echo FIN

Alguien puede decir, oye, paso de esta construcción tan complicada. Bien, ningún problema, luego con cada lanzamiento deberá hacerse vía línea de comandos, etc... De esta forma haces el trabajo inicial y luego fiesta!! Al final, cuando tienes estos profiles, para los siguientes proyectos sólo has de hacer copy&paste. Sólo es una forma de facilitar los múltiples despliegues en un entorno "des" como paso previo al "dev", y por supuesto, previo a los entornos reales.

En el mismo proyecto ci-root debemos incorporar, en la misma carpeta docker, los Dockerfile de la aplicación y del mongo.

Dockerfile-app:

    FROM java:openjdk-8-jdk-alpine

    # add el jar con el nombre del "finalName" del pom.xml
    ADD ci-backend/target/ci-backend.jar /app.jar

    # se modifica la fecha a la actual
    RUN sh -c 'touch /app.jar'

    # comando a ejecutar
    CMD ["java", "-jar", "/app.jar"]


Dockerfile-db:


    FROM mongo:latest
    
    EXPOSE 27017

    CMD ["mongod"]

El fichero docker-compose.yml (para el entorno des) sería el siguiente:

    version: '3.5'
    services:
      ci-app:
        build:
          context: .
          dockerfile: ./docker/Dockerfile-app
        image: ci-app-image
        container_name: ci-app-container
        environment:
          - "SPRING_PROFILES_ACTIVE=des"
        ports:
          - "9090:8080"
        links:
          - ci-db

      ci-db:
        build:
          context: .
          dockerfile: ./docker/Dockerfile-db
        image: ci-db-image
        container_name: ci-db-container
        volumes:
          - "ci-data:/data/db"
        ports:
          - "27017:27017"

    volumes:
      ci-data:


Al ejecutarse mongo en modo "normal" hemos de proveer un volumen de forma que se de persistencia a los cambios que se realicen en el uso de la aplicación.

Con "links" informamos el contenedor de mongo "ci-db" del cual depende. Se crea un enlace de forma que desde el contenedor de la aplicación se "ve" el contenedor de mongo.

El puerto de la aplicación será el 9090, que mapea el 8080 interno del tomcat embebido. Así evitamos usar el 8080 desde "fuera" por si en nuestro ordenador tenemos algún servicio levantado ya previamente en dicho 8080.

En cuanto a mongo si que lo levantamos en el mismo puerto, tanto fuera como dentro del contenedor. Si ya tenemos un mongo levantado en nuestro windows podemos levantarlo en otro puerto


ci-backend
Añadimos algunas dependencias en el pom.xml

<!-- Swagger -->
  <dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>${swagger.version}</version>
  </dependency>
  <dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>${swagger.version}</version>
  </dependency>

  <!-- Mongo -->
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
  </dependency>
  <dependency>
  <groupId>de.flapdoodle.embed</groupId>
  <artifactId>de.flapdoodle.embed.mongo</artifactId>
  </dependency>
 
    <!-- Mongeez for js script mongodb -->
  <dependency>
  <groupId>org.mongeez</groupId>
  <artifactId>mongeez</artifactId>
  <version>${mongeez.version}</version>
    </dependency>

Añadimos la clase del modelo User, según el siguiente fragmento:
    @Document(collection = "user")
    public class User {

        @Id 
        ObjectId databaseId;
        private String userId;
        private String username;
        private String password;
        ....
        // constructor, getters y setters....

Añadimos el fichero "db/sql/V1/V1_00__initial_data.js" dentro de la carpeta resources con este contenido:

    //mongeez formatted javascript
    //changeset system:v1_1
    db.counters.insert({_id: "user", seq: 0})

    db.createCollection("user", { capped : true, autoIndexId : true });
    db.user.createIndex({username: 1}, { unique: true });
    db.user.insert({userId: '0', username: "admin", password: "admin"});

Añadimos el fichero "mongeez.xml" en la raíz del resources, apuntando al fichero anterior:

    <changeFiles>
        <file path="db/sql/V1/V1_00__initial_data.js"/>
    </changeFiles>

Cuando arranca la aplicación, se carga el fichero js según la ruta descrita en el fichero mongeez.xml y se lanzan las sentencias que allí se encuentren. En el entorno "loc" esto se hará con cada ejecución ya que mongo se ejecuta en modo embedded, gracias a la librería "de.flapdoodle.embed.mongo". En los entornos "des" y "dev" al utilizar mongo "normal", esta carga sólo se realiza una única vez. Esto se consigue al generarse de forma "automática" una tabla gestionada por mongeez en nuestro esquema:


Tal como se puede ver en la captura anterior, Mongeez crea la colección user y además la colección "mongeez" donde lleva la cuenta de ficheros js (con DDL, DML, etc...) lanzados.


Añadimos el Repository y el Controller. No los detallo ya que son muy sencillos y se puede revisar el código descargado desde GitLab.


El fichero "application-loc.yml" quedaría ahora así: 
    server:
      port: 8080
  
    spring:
      application:
        name: ci-backend
      profiles:
        active: loc
      jpa:
        show-sql: true
     data:
       mongodb:
         host: localhost
         port: 27017
         database: ci
         uri: mongodb://localhost:27017/ci

    management:
      endpoints:
       web:
         exposure:
include: '*'

sequence:
users:
name: user

    swagger:
      basepackage: es.gincol.blog     
  
    mongeez:
      initialLoadingFile: mongeez.xml

El fichero "application-des.yml" es muy semejante, las diferencias serían:
    spring:
      ...
      data:
        mongodb:
          host: 192.168.99.100
          port: 27017
          database: ci
          uri: mongodb://192.168.99.100:27017/ci
      autoconfigure:
        exclude: org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration

El host representa la ip de la docker-machine en la que correrá mongo. Además, se excluye en "des" el uso de Mongo Embedded.


ci-frontend
En GitLab tenéis todo el código. Entrar en detalles sería hacer dos o tres entradas más sólo de esta parte, y no es el objetivo de este manual.

Como resumen, se han usado servicios, routes, guard, .... 

La aplicación sólo hace una gestión simple de usuarios. Da de alta, elimina y edita usuarios. 
En la edición:
  • si se mantiene el mismo username y se modifica el password hace un update.
  • si se modifica el username se dará de alta un nuevo usuario.

Según lo explicado, el acceso a la aplicación se deberá hacer a partir del artefacto conjunto (estáticos angular y backend) generado. La url en "loc" sería "http://localhost:8080". 

No obstante, cuando estamos en fase de desarollo se hace un poco tedioso ante cualquier pequeño cambio haber de genera el artefacto, y volverlo a lanzar, etc... Por ello, si sólo estamos modificando código angular se puede hacer lo siguiente:
  • lanzar la parte estática por su lado, con "ng serve" o "npm start". 
  • arrancar el proyecto ci-backend como spring boot
  • Acceder entonces con "http://localhost:4200" (puerto por defecto del arranque angular)

En esta configuración, cualquier cambio en angular lo podemos probar sin necesidad de compilar y generar todo el artefacto. Sólo cuando lo tengamos ya validado podemos probar el resultado final "todo en uno".

Por este motivo, de facilitar la actualización de contenidos angular en desarrollo, los endpoints del backend se han configurado como GET (los de recuperación) o POST (los de modificación) ya que en otro caso aparecían errores CORS. Al parecer, los métodos invocados "http.delete(....)" (@DeleteMapping en el backend) están limitados en los mismos navegadores haciendo inviable la ejecución normal de la aplicación. Esta limitación se presenta cuando lanzamos angular por un lado, y spring boot por otro. Cuando se lanzan de forma conjunta como un único producto ya no hay lugar para errores de tipo CORS.

Se puede consultar la página de mozilla donde se especifican las limitaciones comentadas en un escenario "CORS".

En todo caso, mis pruebas en este escenario las he realizado con Chrome. Con firefox el comportamiento no es muy estable.

Con el acceso "http://localhost:8080" funciona siempre sin problemas tanto en firefox como en chrome.


Pruebas
Realizaremos pruebas para el entorno "loc" (normal) y "des" (docker-compose)

entorno "loc"
Las pruebas las haremos con el acceso final "http://localhost:8080". Por tanto, previamente debemos haber generado el artefacto para este entorno. Lo haremos tal como decíamos en la entrada anterior con "mvn clean install -Dentorno=loc" o bien como decíamos más arriba, desde el mismo eclipse, a partir de un maven build previamente grabado.

Una vez finalizado lanzamos la aplicación desde eclipse o bien desde línea de comandos de alguna de las siguientes maneras:

  • desde eclipse, desde "ci-backend" como spring boot aplication
  • desde la raíz del proyecto "ci-backend" 
    • con "mvn spring-boot:run -Dspring-boot.run.profiles=loc"
    • o con "set SPRING_PROFILES_ACTIVE=loc && mvn spring-boot:run"
  • desde la carpeta target del proyecto "ci-backend" con "java -jar ci-backend.jar --spring.profiles.active=loc"

Finalmente acedemos:




Bajo el menú de "Usuarios" podremos lanzar la gestión de altas, modificaciones y bajas.


entorno "des"
De la misma forma generamos el artefacto teniendo en cuenta que ahora "-Dentorno=des".

Por tanto, antes de lanzar docker-compose, creamos el artefacto con "mvn clean install -Dentorno=des" desde la raíz de ci-root (o bien desde un build de eclipse previamente configurado).

Arrancamos nuestra docker-machine,  
  • desde línea de comandos "docker-machine start NOMBRE"
  • desde el profile que hemos configurado en el pom.xml del ci-root, configurándolo en eclipse como se muestra en la captura:


  • desde la raíz del ci-root con el comando "mvn exec:exec -P docker-machine-start"

Ahora lanzamos el docker-compose. También habría varias formas de lanzarlo:
  • desde un profile maven que hemos configurado en el pom.xml, a imagen del profile de arranque de la docker-machine descrito anteriormente. En este caso, el profile sería "docker-compose-up"
  • desde la raíz del ci-root con el comando "mvn exec:exec -P docker-compose-up"
  • cargamos las variables de la docker-machine arrancada "@FOR /f "tokens=*" %i IN ('docker-machine env NOMBRE') DO @%i", y ahora desde la raíz del proyecto ci-root con "docker-compose up -d --build"

Para ver los logs de los contenedores levantados "docker-compose logs -f" o bien "docker logs -f CONTAINER_NAME".

Finalmente accedemos a la url "http://192.168.99.100:9090"




Y la gestión de usuarios, en concreto el alta:




Recordad que si tocamos algo de código (angular o java) habremos de generar un nuevo artefacto y volver a lanzar la ejecución en docker. En "des" no basta con modificarlo sólo, se trata de un entorno más complejo y previo a "dev". Necesario, a mi entender, para no ir "a oscuras" a "dev".

El entorno "dev", que veremos en la siguiente entrada, también es necesario para poder evaluar los ficheros yaml específicos de un entorno cloud kubernetes.


Aclaración
De momento no hemos configurado mongo en replica. Lo haremos en la siguiente entrada, la que tiene más sentido al centrarnos en el despliegue en el entorno kubernetes proporcionado pon minikube.

La entrada siguiente será la última de esta serie, y nos centraremos en el entorno "dev", que hemos convenido como nuestro entorno previo final, donde probaremos el entorno CI de nuestro laboratorio.




Anterior 

No hay comentarios:

Publicar un comentario