viernes, 20 de julio de 2018

Laboratorio - Integración Contínua (II)

Seguimos con la creación y configuración del 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 y 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

Vamos ahora con la instalación de Jenkins.


Instalación

Java 8

Como único pre-requisito hemos de tener instalado java 8 en nuestro sistema para poder instalar Jenkins. Según la documentación oficial, la versión recomendada es "OpenJDK JDK / JRE 8 - 64 bits". 

En la página oficial de OpenJDK nos dicen como hacerlo:
    sudo apt install openjdk-8-jdk

Tras aceptar la descarga e instalación validamos
    java -version

  nos devolverá algo parecido a 

    openjdk version "1.8.0_171"

    OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.18.04.1-b11)

    OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)



Podemos ver la ruta del comando java con "which java", que nos devolverá "/usr/bin/java". 
La ruta de instalación del jdk se obtiene con "update-alternatives --list java", lo cual nos devuelve 
    /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java

  donde la ruta al directorio de instalación del jdk es "/usr/lib/jvm/java-8-openjdk-amd64/". Más adelante lo necesitaremos.


Maven

Instalamos también maven, en su última versión. Nos hará falta para compilar las aplicaciones que subamos a nuestro entorno CI.
    sudo apt install maven

Validamos
    mvn -version

  que nos devuelve
    Apache Maven 3.5.2
    Maven home: /usr/share/maven
    Java version: 1.8.0_171, vendor: Oracle Corporation
    Java home: /usr/lib/jvm/java-8-openjdk-amd64/jre
    Default locale: es_ES, platform encoding: UTF-8
    OS name: "linux", version: "4.15.0-23-generic", arch: "amd64", family: "unix"


Jenkins

Comenzamos la instalación de Jenkins propiamente dicha. Como siempre, nos basamos en la página oficial del producto.

Lanzamos los siguientes comandos

  Primero añadimos el repositorio jenkins a nuestra lista de repositorios
    wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
    sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'

  Descargamos la lista de packages disponibles en el repo añadido
    sudo apt update

    Descargados 15,3 kB en 1s (13,0 kB/s)

    Leyendo lista de paquetes... Hecho

  Lanzamos la instalación
    sudo apt install jenkins


Post-instalación

El directorio donde se ha instalado Jenkins es "/var/lib/jenkins"

Estos son los servicios principales que se instalan:


El daemon instalado se levantará en el arranque del ubuntu, por lo que no deberemos preocuparnos de arrancarlo de forma manual. El script de arranque de dicho daemon lo podemos ver en "/etc/init.d/jenkins". 

Si necesitamos parar, arrancar, reiniciar o ver el estado de Jenkins hay dos vías

    sudo service jenkins status/start/stop/restart

    sudo systemctl status/start/stop/restart jenkins


Vemos también un proceso java asociado a Jenkins. Se trata del java 8 instalado en el el apartado de pre-requisitos. Vemos que el puerto por defecto es el 8080, el habitual de Tomcat. Esto es así porque la misma consola es una aplicación "war" que se despliega en un tomcat incorporado en la misma instalación de Jenkins.

En mi caso cambiaré el puerto ya que es posible que lo necesitemos libre para el despliegue de aplicaciones, etc... que vayamos haciendo en los pasos siguientes. Para ello edito el fichero "/etc/default/jenkins"
    sudo vi /etc/default/jenkins

    y modifico el parámetro "HTTP_PORT=8080" por "HTTP_PORT=8787"

Si en algún momento necesitase modificarlo de nuevo usaría este mismo procedimiento. No obstante, ojo que en la configuración CI necesitaremos configurar webhook's en GitLab que apunten a Jenkins, por lo que cualquier cambio de puerto obligará a actualizar todos los webhoolk's configurados.

Una vez cambiado reiniciamos Jenkins

    sudo systemctl restart jenkins

Si revisamos el proceso tras el cambio veremos el cambio de puerto
    ps -ef | grep jenkins | grep httpPort

    ...... --webroot=/var/cache/jenkins/war --httpPort=8787



Consola

Para acceder a la consola Jenkins ponemos la url "http://localhost:8787" en el navegador de nuestro ubuntu

En el primer acceso, nos aparece una pantalla en la que nos indica que el acceso está bloqueado y nos muestra dónde obtener el password inicial. Está en la ruta "/var/lib/jenkins/secrets/initialAdminPassword". Por tanto lanzamos "sudo cat /var/lib/jenkins/secrets/initialAdminPassword", lo cual nos muestra un literal alfanumérico parecido al que aparece en mi instalación: "db9815186c804b87aac809aa093ad223" 

Lo introducimos en el campo de texto y pulsamos "Continue"


Nos aparece una pantalla de bienvenida proponiéndonos la instalación de una serie de plugins recomendados o bien de selección libre. De momento vamos a seguir las sugerencias iniciales e instalamos los plugins que Jenkins nos propone. Es posible que más adelante debamos añadir algún plugin más. En la zona de Administración podremos hacerlo sin ningún problema.

A continuación nos pide la creación de un usuario Admin para no usar el por defecto. Procedemos proporcionando los datos que nos pide. Tras finalizar accedemos a la consola habitual de Jenkins usando el nuevo usuario creado:



Configuraciones extra

Si accedemos al menu de Administración (Manage Jenkins), apartado de configuración del sistema (Configure System) veremos un warning en el apartado "Jenkins Location", donde aparece "http://localhost:8787/". Lo cambiamos por la ip fijada en la entrada anterior (la podemos obtener con "ifconfig"). Finalmente salvamos.

Accedemos ahora a la opción "Global Tool Configuration" del mismo menu "Manage Jenkins". Aquí configuraremos el jdk y maven, indicando las rutas de instalación

    la de java8 recordad que se puede obtener con "update-alternatives --list java" y eliminando del path obtenido desde /jre en adelante.


    la de maven la podemos obtener del comando "mvn -version"


En otras entradas deberemos acceder de nuevo y realizar alguna configuración más.


Plugin GitLab

Accedemos al apartado del "Plugin Manager" del "Manage Jenkins". En la pestaña de "Available" filtramos por "gitlab". Marcamos el check del plugin "GitLab" y pulsamos sobre "Download now and install after restart".

Ahora mismo no nos es necesario, pero lo será en las siguientes entradas.


Prueba
Vamos a crear un job de tipo pipeline a modo de ejemplo y validación de que nuestro entorno funciona.

Creamos primero una vista (conjunto de jobs) pulsando sobre "New View" en el menú de la izquierda. Ponedle el nombre que deseéis y seleccionar uno de los dos tipos de vistas que se presentan, en mi caso escojo la "List View". En el formulario que nos aparece a continuación lo dejamos sin modificar (salvo que queramos ponerle una descripción) y pulsamos el botón inferior de "OK".

A continuación, pulsamos sobre "New Item" del menú lateral izquierdo, asegurándonos de que en la barra de navegación aparezca la lista creada anteriormente. De esta forma, el job que creemos estará dentro de dicha lista.

Bien, en el item a crear, le damos un nombre, seleccionamos Pipeline (es el tipo de job que usaremos con todos los proyectos), y pulsamos OK (el check "Add to current view" ha de estar marcado).

Se nos presenta un formulario con multitud de opciones. De momento, para el ejemplo sólo vamos a introducir un código de ejemplo, que habitualmente lo tendremos en un fichero Jenkinsfile que acompañará a nuestros proyectos.

En el apartado Pipeline nos aseguramos que en el combo "Definition" aparece seleccionado "Pipeline script". En el textarea "Script" introducimos

    pipeline {
        agent any
tools { 
            maven "mvn35" 
            jdk "java8"
        }
    
        stages {
            stage('maven') {
                steps {
                    echo "Inicio maven"
                    sh 'mvn --version'
                    echo "Fin maven"
                }
            }
        
            stage('java') {
                steps {
                    echo "Inicio java"
                    sh 'java -version'
                    echo "Fin java"
                }
            }
        }
    }

Pulsamos sobre "Save". 

El apartado de agent es obligatorio y le decimos donde va a ejecutar los comandos que vienen a continuación en los stages. En nuestro caso es en el propio sistema donde está instalado jenkins. Podríamos haber puesto (aún no ya que aún no hemos instalado Docker):

    agent {
        docker { image 'java:openjdk-8-jdk-alpine' }
    }

a nivel del stage java y eso quería decir que el comando "java -version" se habría ejecutado en un contenedor levantado "al vuelo" a partir de la imagen "java:openjdk-8-jdk-alpine". Lo veremos más en detalle en esta serie de entradas o entradas posteriores sobre otros temas.

En el apartado de tools indicamos que vamos a usar maven y java, informando el nombre dado en la configuración descrita en los apartados respectivos vistos más arriba. Es decir, las variables informan la home de java y maven. A paritr de ahí, los comandos java y mvn se nutren de dichas "home's".

A continuación definimos dos pasos o stages, el de maven y el de java, lanzando únicamente el comando que verifica la versión usada en cada caso.

Idealmente tendremos algo como lo siguiente:


Accedemos al job y pulsamos sobre "Build Now", lo cual lanzará la ejecución del job. Tras finalizar veremos el resultado OK (todos los stages están en verde):


Podemos ver el log accediendo al desplegable del "#1" y seleccionando "Console Output" o bien, de forma resumida pulsando sobre cada stage, por ejemplo en el de java podemos ver la salida (los "echo's" y el "java -version")



De momento es suficiente. En la próxima entrada veremos cómo instalar y usar GitLab. Haremos una prueba más real de lo que será nuestro entorno CI integrando ambos elmentos, GitLab y Jenkins.



Más Info
Home Jenkins: https://jenkins.io/
Documentación de Jenkins: https://jenkins.io/doc/

martes, 17 de julio de 2018

Laboratorio - Integración Contínua (I)

Iniciamos una serie de entradas donde expondremos la forma de crear un entorno de Integración Continua en nuestro ordenado, a modo de laboratorio.

Estos serán 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 y 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
En esta primera entrada instalaremos Ubuntu en VMWare Player.


Ubuntu
Nos descargamos la imagen ubuntu completa, con desktop incluido. En estos momentos la versión disponible es la 18.04 TLS (ubuntu-18.04-desktop-amd64.iso).

Una vez descargada, arrancamos VMWare Player. Pulsamos sobre "Create a New Virtual Machine"


A continuación seleccionamos la iso descargada y pulsamos "next":


Informamos el usuario de que se creará con la instalación. En Ubuntu habitualmente no usaremos root, usaremos este usuario definido, y cualquier acción que necesite permisos de administrador lo haremos con "sudo comando...."



En el siguiente paso indicamos el nombre de la máquina virtual y la ubicación de los ficheros de dicha máquina virtual.


En el siguiente paso sólo modificamos el tamaño del disco a usar por la máquina virtual. En nuestro caso indicamos 40 GB.


En el siguiente paso aparece un resumen de la instalación. Pulsaremos "Customize Hardware" para cambiar algunos valores.

En Memory asignamos 6 GB de RAM si nuestro equipo tiene 12 GB. Si disponemos de 8 GB, asignaremos la mitad, 4 GB.

En el apartado de "Processors" asignamos dos cores y marcamos el checkbox "Virtualice Intel VT..." (esto nos permitirá instalar Docker y Minikube, en otro caso nos daría error)


El adaptador de red lo dejamos como NAT (por defecto). Aceptamos y volvemos a la pantalla de resumen:


Pulsamos sobre "Finish", comenzando el proceso de instalación:


Tras unos minutos acaba presentándonos la pantalla de login. Introducimos las credenciales configuradas más arriba y accedemos a nuestro Ubuntu.


Lo más probable es que nos indique que tiene que actualizaciones pendientes. Aceptamos para que las instale. Nos pedirá password ya que las actualizaciones requieren permisos de administrador.

En todo caso, si no nos pide actualizar el sistema lo haremos de forma manual. Abrimos un terminal (será nuestro compañero en el resto de entradas) y ejecutamos "sudo apt update" (para actualizar la lista de paquetes). No puede devolver algo como lo siguiente:

    userblog@ubuntu:~$ sudo apt update
    [sudo] password for userblog: 
    Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]
    Hit:2 http://us.archive.ubuntu.com/ubuntu bionic InRelease                              
    Get:3 http://us.archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
    Get:4 http://us.archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB]
    Fetched 247 kB in 1s (232 kB/s)                               
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    15 packages can be upgraded. Run 'apt list --upgradable' to see them.

En este caso, ejecutamos "sudo apt upgrade".  

    userblog@ubuntu:~$ sudo apt upgrade
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    Calculating upgrade... Done
    The following packages will be upgraded:
      evolution-data-server evolution-data-server-common gnome-disk-utility gnome-initial-setup    libcamel-1.2-61
      libebackend-1.2-10 libebook-1.2-19 libebook-contacts-1.2-2 libecal-1.2-19 libedata-book-1.2-25 libedata-cal-1.2-28 libedataserver-1.2-23 libedataserverui-1.2-2 squashfs-tools unattended-upgrades
    15 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
    Need to get 0 B/3,770 kB of archives.
    After this operation, 37.9 kB of additional disk space will be used.
    Do you want to continue? [Y/n] 

  introducimos "Y" y pulsamos intro


Configuración adicional
Antes de tener el entorno base deberemos instalar algún software adicional o realizar alguna configuración extra.


Idioma

Si en el terminal introducimos el guión "-" y nos presenta la barra "/" deberemos actualizar manualmente la configuración de idioma en "Settings/Region & Language":



    En la pestaña "Language" arrastramos hacia arriba el idioma "Español" instalado como primera opción:

    Y en la pestaña "Regional Formats" seleccionamos "Español"


Tras finalizar será necesario reiniciar el sistema para que los cambios surten efecto. Tras dicho reinicio, si abrimos el terminal (es aconsejable ponerlo añadirlo a Favoritos para que aparezca en la barra lateral) ya encontraremos las teclas habituales en su sitio.


curl

Para poder lanzar peticiones http a través de la línea de comandos instalaremos curl. Nos será necesario para lazar de forma rápida peticiones a servicios rest que hayamos creado y necesitemos probar.

Instalamos desde el terminal lanzando:
    sudo apt install curl

Validamos con "curl -v http://www.google.es". Nos tiene que devolver el contenido html de la página de google


ifconfig

Para poder lanzar el comando ifconfig (y otros) necesitaremos instalar el paquete "net-tools". Lanzamos en el terminal:
    sudo apt install net-tools

ahora ya podremos lanzar "ifconfig" obteniendo algo parecido a

    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 192.168.153.138  netmask 255.255.255.0  broadcast 192.168.153.255
    ...
    lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
            inet 127.0.0.1  netmask 255.0.0.0
    ...

La que nos interesa es la interfaz "ens33". Más adelante fijaremos la ip para evitar cambios imprevistos.


Fijación de ip

Fijamos una ip para nuestro ubuntu virtualizado. En la vesión 18.04 se puede hacer mediante netplan. En nuestro caso creamos el fichero "sudo vi /etc/netplan/01-netcfg.yaml" e introducimos la siguiente configuración:


Actualizamos la configuración con 
    sudo netplan apply

y revisamos con "ifconfig"
    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 192.168.153.201  netmask 255.255.255.0  broadcast 192.168.153.255
    ....

Volvemos a validar con "curl -v http://www.google.es" para verificar que la configuración es correcta y tenemos salida a internet.


ssh

Es posible que queramos acceder a nuestro Ubuntu vía ssh (putty, etc..) para poder subir ficheros (sftp), o simplemente para lanzar comandos sin necesidad de acceso al desktop. Para poder hacerlo nesitamos instalar openssh
    sudo apt install openssh-server

Tras su instalación se instala un daemon que podemos ver arrancado:
    userblog@ubuntu:~$ ps -ef | grep sshd
    root      11308      1  0 11:44 ?        00:00:00 /usr/sbin/sshd -D

Probamos el acceso con "ssh userblog@192.168.153.201" desde un terminal cygwin de nuestro windows (o con putty, etc...)




Final

De momento esto es todo. En las siguientes entradas iremos instalando y configurando todos los elementos necesarios para poder disponer de un entorno de laboratorio con Integración Continua.

domingo, 15 de julio de 2018

Redis - Externalización de sesiones

Redis es muchas cosas..., un almacén de datos estructurados en memoria, un gestor de sesiones extenalizado, una forma de compartir datos entre aplicaciones, ....

Nos centramos en esta entrada en la función de gestión de sesiones.

En el ecosistema de microservicios cloud, el tema de sesiones puede ser un problema para la transformación de aplicaciones monolíticas en microservicios. Habitualmente todas las aplicaciones monolíticas, de una u otra forma, persisten datos de manera que se permita una navegación fluida para un usuario dado.

Por ejemplo, todas las aplicaciones protegidas por login, han de proveer una gestión de datos que garantice una navegación con "estado" mediante un protocolo, como http, sin estado. Al menos han de guardar el usuario logado (al margen de algunos datos asociados al mismo), asignar una sesión http con su caducidad, etc..., de forma que pueda navegar por aquellos apartados permitidos en la web protegida.

Cuando pasamos a microservicios cloud, donde las buenas prácticas prohíben usar sesiones de servidor, qué soluciones podemos utilizar. Bien, una de ellas es Redis.

Utilizaremos para esta prueba

  • Un contenedor docker Redis
  • Un microservicio Spring boot
  • Entorno Kubeadm de prueba.

Todo el código lo podés encontrar en el gitlab


Pod
Usaremos una image docker proporcionada por Bitnami
El contenido del redis.yaml (deploy y service) es este:

    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: redis
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            app: redis
            role: master
            tier: backend
        spec:
          containers:
            - name: redis-master
              image: bitnami/redis:latest
              ports:
                - containerPort: 6379
              env:
                - name: ALLOW_EMPTY_PASSWORD
                  value: "yes"
    ---
    kind: Service
    apiVersion: v1
    metadata:
      name: redis
      labels:
        name: redis
    spec:
      type: NodePort
      ports:
      - port: 6379
        protocol: TCP
        targetPort: 6379
      selector:
        app: redis


Podríamos configurar varias réplicas, pero para nuestra prueba nos basta con una, la del pod master.
Como vemos, el puerto expuesto es el 6379. Es a este puerto al que haremos apuntar nuestra app.

Para levantar el pod lanzaremos el comando
    kubectl apply -f redis.yaml

Tras unos instantes ya podemos ver el pod levantado
    kubectl get pods



Revisamos el arranque
    kubectl logs -f redis-587d9bd764-hzbzj


Podemos acceder al pod para ver la información que iremos generando
    kubectl exec -it redis-587d9bd764-hzbzj bash

Una vez dentro lanzamos el cliente redis
    redis-cli monitor

    nos devuelve un OK y queda a la espera de accesos



Spring boot
Construyamos un microservicio básico de prueba con Spring boot

pom.xml

Como dependencias específicas redis debemos añadir

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>

Incorporamos también swagger para facilitar las pruebas

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>


Java

Añadimos una clase de configuración con el siguiente contenido


    @Configuration
    @EnableRedisHttpSession
    public class RedisConfig {

        private @Value("${spring.redis.host}") String redisHost;
        private @Value("${spring.redis.port}") int redisPort;

        @Bean
        public LettuceConnectionFactory connectionFactory() {
           return new LettuceConnectionFactory(redisHost, redisPort);
        }
    }

Para usar la session lo hacemos de la forma habitual

    HttpSession misession= request.getSession(true);
    misession.setAttribute("variable", "valor");
    etc...

El endpoint que explicamos en la prueba de más abajo tiene este código

    @GetMapping("/fecha")
     public @ResponseBody String date(HttpServletRequest request) {
log.info("Acceso al metodo rest 'fecha'");
return "Hola desde el RestController en fecha <br><h2>" + 
            new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + 
            " !!!<h2>" + "<hr/>" + "<h3>app: '" + appName + "'</h3>" + "<p>host name / ip: <b>" + 
            Utils.getIp() + "</b></p>" + "<p>Session: <b>" + 
            request.getSession().getId() + "</b></p>" + "<hr/><hr/>";
    }

Sólo queremos que muestre el server (pod en el caso de kubernetes) desde el que se ejecuta, la hora y la session http.


application.yml

Las propiedades específicas son estas (application.yml)

    spring:
      application:
        name: redis-session
      profiles:
        active: pro
      session:
        store-type: redis
      redis:
        host: redis
        port: 6379 

En host hemos puesto el nombre "redis" que se corresponde con el servicio creado según el fichero "redis.yaml" descrito más arriba.


Dockerfile

Este es su contenido:

    FROM java:openjdk-8-jdk-alpine

    # add el jar con el nombre del "finalName" del pom.xml
    ADD redis-session.jar /app.jar

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

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

lo necesitamos para poder construir la imagen a desplegar en kubeadm.
Debemos hacer un "mvn clean install" para generar el artefacto "redis-session.jar".

copiamos el jar a nuestro ubuntu y lanzamos el siguiente comando
    sudo docker build -t "redis-session:1.0.0" .

 
desde el directorio que contiene el Dockerfile y el redis-session.jar (el nombre de la imagen siempre ha de ser en minúsculas).
Validamos con "sudo docker images" (en extras veremos como eliminar el molesto sudo)



Deployment y service

Creamos, a partir de la imagen nuestro deploy y service. Para ello necesitamos el siguiente yaml (redis-session.yaml):

    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: redis-session
      labels:
        name: redis-session
    spec:
        replicas: 3
        template:
          metadata:
            labels:
              app: redis-session
              tier: backend
          spec:
            containers:
              - name: redis-session
                image: redis-session:1.0.0
                ports:
                  - containerPort: 8080
                readinessProbe:
                  httpGet:
                    path: /redis-session/readiness
                    port: 8080
                  initialDelaySeconds: 60
                  timeoutSeconds: 5  
                livenessProbe:
                  httpGet:
                    path: /redis-session/liveness
                    port: 8080
                  initialDelaySeconds: 60
                  timeoutSeconds: 5
            
    ---
    kind: Service
    apiVersion: v1
    metadata:
      name: redis-session
      labels:
        name: redis-session
        tier: backend
    spec:
      type: LoadBalancer
      ports:
      - port: 8080
        protocol: TCP
      selector:
        app: redis-session

        tier: backend

Aplicamos:
    kubectl apply -f redis-session.yaml

Importante, usamos tres réplicas, que nos generarán tres pods que darán servicio a las peticiones que reciba nuestra aplicación.

Podemos revisarlo con "kubectl get pods"



También hemos añadido dos "probes", el readinessProbe (indica a kubernetes cuando un pod está preparado para recibir peticiones) y livenessProbe (indica a Kubernetes si un pod está activo). Se corresponden con dos endpoints ubicados en el "MonitorController.java".



Pruebas
Para acceder desde fuera del ubuntu lanzamos el siguiente comando en nuestro ubuntu:
    sudo kubectl port-forward svc/redis-session 9090:8080

y mapeo desde VirtualBox de Windows:

Ahora, desde un navegador de nuestro windows tenemos varios endpoints arrancados para probar:
el de swagger: http://localhost:9090/redis-session/swagger-ui.html

endpoint "fecha" que nos permite ver el juego de sesiones
    http://localhost:9090/redis-session/fecha

Si nos centramos en este último endpoint y probamos obtenemos el siguiente resultado:


Si ahora paramos el pod que nos ha respondido (vemos su id) con
    kubectl delete pod id

Se levantará de forma automática uno nuevo en su sustitución. Si ahora refrescamos en nuestro navegador (el mismo que hemos usado antes) veremos el cambio de pod que responde, pero seguiremos viendo la misma session id:


Es decir, mientras no cerremos navegador, aunque se paren todos los pods de nuestro microservicio y se levanten otros en su sustitución, la session permanecerá inalterable, ya que su gestión, se está derivando hacia el pod de redis. Si introducimos datos en session, estos los seguiremos encontrando ahí. Está todo persistido en el pod de redis.

Si me permitís una imagen gráfica (con sólo dos pods spring boot) esto es lo que está pasando:



Podemos jugar ahora con swagger con los endpoints "addDataSession" y "getDataSession" del "main-controller", parando pods, etc... Cada vez que hagamos un delete del pod deberemos relanzar el "sudo kubectl port-forward..." descrito más arriba.

También lo podemos hacer vía curl de la siguiente forma.Para insertar datos en session (hemos de grabar la session en fichero):
    curl -X PUT -c sessionfile http://localhost:9090/redis-session/addDataSession?name=Antonio
    Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0

Ahora recuperamos el dato enviando la session grabada en el sessionfile:
    curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
    respuesta: ["Antonio"]

De nuevo añadimos datos, esta vez el comando es:
curl -X PUT -b sessionfile -c sessionfile http://localhost:9090/redis-session/addDataSession?name=es
Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0

curl -X PUT -b sessionfile -c sessionfile http://localhost:9090/redis-session/addDataSession?name=panadero
Session grabada: 760d26e8-3ab1-4103-bada-8fe8d999a1d0

Como vemos, siempre se nos devuelve el mismo valor de session (760d26e8-3ab1-4103-bada-8fe8d999a1d0). No nos importa que pod pueda estar respondiendo.

Finalmente recuperamos todo:
    curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
    ["Antonio","es","panadero"]

Eliminamos los tres pods "kubectl delete pods id1 id2 id3". Tras unos segundos se reinician y volvemos a levantar el port-forward y lanzamos de nuevo la consulta, obteniendo el mismo resultado
    curl -X GET -b sessionfile http://localhost:9090/redis-session/getDataSession
    ["Antonio","es","panadero"]

Todas estas peticiones dejan logs en el pod de redis, que mediante su acceso "kubectl exec -it id_pod bash" y posterior lanzamiento del cliente redis "redis-cli monitor" vemos:



Recordad que podemos tracear el pod mediante "kubectl logs -f id". Ahí podremos ver el pod que recibe la petición, etc...

Además de lo dicho, también veremos en los logs de cada pod, cada 30 segundos, la salida que se generan con la monitorización que hemos introducido (liveness y readiness).



Extra
Si queremos usar redis en kubeadm desde nuestro entorno de desarrollo local deberemos realizar estas acciones previas

La primera, lanzar el siguiente comando
    sudo kubectl port-forward svc/redis 6379:6379
 
    es decir, damos acceso al puerto expuesto por el servicio (svc/redis), el 6379, desde fuera

Ahora, en nuestro VirtualBox (donde corre el ubuntu con kubeadm) configuramos el mapeo de puertos:


Por otro lado, si no queremos usar siempre "sudo" para la ejecución de cualquier comando "docker XXX" podemos incluir nuestro usuario ubuntu en el grupo "docker" de esta forma
    sudo usermod -aG docker $(whoami)

Deberemos cerrar sesión y abrir una nueva para que sea efectiva la nueva configuración. De esta forma, ahora para usar comandos del tipo "docker images", "doker ps", etc... no necesitamos añadir "sudo" delante.


Más info
Por supuesto, todo el código está disponible en
    https://gitlab.com/gincol-blog/redis-session.git

Como siempre, la información principal está en las páginas oficiales
  Spring:  https://projects.spring.io/spring-data-redis/
  Redis:   https://redis.io/
               https://redis.io/commands/monitor
               https://docs.bitnami.com/virtual-machine/components/redis/
               https://docs.bitnami.com/kubernetes/get-started-kubernetes/

Otras funciones Redis:
    https://spring.io/guides/gs/spring-data-reactive-redis/
    https://spring.io/guides/gs/messaging-redis/

jueves, 5 de julio de 2018

Kubeadm - Laboratorio Kubernetes

Explicamos en esta entrada la instalación y uso de un cluster kubernetes mínimo para testeo previo a uso de clouds corporativos o profesionales. Dicho cluster sólo tendrá un nodo, el master.

Lo haremos con Kubeadm. La página de referencia es "https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/".

En otra entrada explicaremos la instalación y uso de otro laboratorio que usamos habitualmente en nuestras pruebas: Minikube.


Configuración
Para la realización de este pequeño manual hemos utilizado un Ubuntu Server (18.04 de 64 bits), sin entorno gráfico, virtualizado sobre VirtualBox. Consultar la página oficial de descargas de Ubuntu.

No nos detenemos en la instalación ya que es la habitual de cualquier iso sobre VirtualBox.


Kubeadm
Según la documentación, los requerimientos de instalación son los siguientes:

  • Ubuntu, CentOS, Debian....
  • 2 GB de RAM
  • 2 CPU's
  • Conexión de red.
Comencemos


Instalación
Fijamos una ip para nuestro ubuntu virtualizado. En la vesión 18.04 se puede hacer mediante netplan. En nuestro caso creamos el fichero "/etc/netplan/01-netcfg.yaml" e introducimos la siguiente configuración:


Aplicamos
    sudo netplan apply

  podemos validar con "ifconfig" de forma que vemos la ip configurada.

Más adelante usaremos la ip configurada (192.168.1.70) en el arranque de kubeadm.

Instalamos Docker. Para ello ejecutamos:
    sudo apt-get update
    sudo apt-get install -y docker.io

Validamos ejecutando "sudo docker version", lo cual devuelve, en el momento de hacer esta entrada

    Client:
     Version:       17.12.1-ce
     API version:   1.35
    ...

Ahora instalamos 
  • kubeadm: el comando para levantar nuestro cluster de prueba
  • kubelet: el daemon que corre en cada nodo del cluster y permite el arranque de pods, etc...
  • kubectl: el cliente que permite hablar con nuestro cluster
Lanzamos los siguientes comandos
    sudo apt-get update && sudo apt-get install -y apt-transport-https curl
    sudo curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

Creamos el fichero siguiente:
    sudo vi /etc/apt/sources.list.d/kubernetes.list

    añadimos

    deb http://apt.kubernetes.io/ kubernetes-xenial main 

    y grabamos (:wq + enter)

Finalmente
    sudo apt-get update
    sudo apt-get install -y kubelet kubeadm kubectl


Inicio
Habilitamos el servicio docker
    sudo systemctl enable docker.service

Deshabilitamos swap (espacio de intercambio), en otro caso no nos arrancará
    sudo swapoff -a

Arrancamos informando la ip fijada más arriba
    sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --apiserver-advertise-address=192.168.1.70 --ignore-preflight-errors=all

conifiguramos kubectl
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config

Instalamos Calico como proveedor de red
    kubectl apply -f  https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubeadm/1.7/calico.yaml

Revisamos
    watch kubectl get pods --all-namespaces

Cuando veamos todos los pods en running proseguimos (Ctrl + c)



En un entorno real, en el nodo master no deberíamos instalar ningún pod. Sin embargo, al ser un entorno de test, hemos de habilitar esta posibilidad (deshabilitada por defecto)

    sudo kubectl taint nodes --all node-role.kubernetes.io/master-

Nos devuelve "node/ubuntu18system untainted".

Vemos los nodos activos
    sudo kubectl get nodes -o wide


Si hubiésemos de configurar un entorno real con más nodos, en el arranque de kubeadm se indica como deberíamos proceder para hacer el join del resto de nodos. En nuestro caso nos aparece el siguiente log:

    You can now join any number of machines by running the following on each node as root:

    kubeadm join 192.168.1.70:6443 --token 7z00kz.h3zrsijjih4hf3cg --discovery-token-ca-cert-hash sha256:97540b4036babb5c314c02b82fd6319b24eba3a2f4e987bde860b43b7d0ef4ce

No lanzaremos dicho comando en ningún lugar ya que sólo estamos configurando un único nodo.


Prueba
Finalmente validaremos la instalación de una imagen, levantando un pod y accediendo desde nuestro windows.

Utilizaremos una imagen nginx para ello.

Creamos el deployment
    kubectl create deployment nginx --image=nginx

Creamos el servicio exponiendo el puerto 80
    kubectl create svc nodeport nginx --tcp=80:80

Revisamos deployment y servicio con
    kubectl get deployments
    kubectl get svc

Vemos la ip y puerto que nuestro kubeadm ha generado para el servicio
    kubectl describe service nginx


Ahora, para poder acceder desde nuestro windows al pod del kubeadm dentro del ubuntu virtualizado en Virtual Box hemos de recoger todos los datos necesarios y realizar dos configuraciones extras:


  • Port forward dentro de ubuntu

               sudo kubectl port-forward svc/nginx 90:80

          dentro de ubuntu podemos validar ahora que accedemos a nginx con
               curl -v http://127.0.0.1:90


  • Ahora, configuramos reenvío de puertos en nuestro VirtualBox:




          Como se puede ver, hemos configurado el acceso a nginx.
          También un acceso ssh para trabajar de forma más cómoda (putty...) en la consola ubuntu.

Ahora ya podemos ver nuestro pod nginx desde windows con http://localhost:90



Más info
Se puede encontrar más información en los siguientes enlaces:
https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/
https://kubernetes.io/docs/concepts/cluster-administration/addons/
https://docs.projectcalico.org/v3.1/getting-started/kubernetes/

Port forward en kubernetes
https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/

Configuración estática ip en ubuntu
https://websiteforstudents.com/configure-static-ip-addresses-on-ubuntu-18-04-beta/