Microservicios con Okteto stack
Tags: java, kubernetes, k8s, okteto
Published: 2021-10-28 04:39:05

---
title: Microservicios con Okteto stack
published: true
date: 2021-09-10 00:00:00 UTC
tags: java,kubernetes,k8s,okteto
canonical_url: https://jorge.aguilera.soy/blog/2021/okteto-stack.html
---

En este blog ya he escrito algunas entradas sobre la plataforma de Okteto ([http://okteto.com](http://okteto.com)) y cómo utilizarla para desplegar pequeños proyectos (de forma gratuita) en Kubernetes de una forma simple.

Básicamente Okteto son dos productos diferentes pero relacionados:

- 

una herramienta a "instalar" en tu Kubernetes que permite al desarrollador sincronizar su proyecto en un pod del cluster así como poder depurar la aplicación directamente en el mismo (entre otras cosas)

- 

un servicio kubernetes con diferentes planes de precios incluida una capa gratuita con suficientes recursos como para desplegar servicios básicos en real (ideal en mi opinión para el aprendizaje de k8s)

En estos post he descrito cómo puedes desplegar desde un static site de fotos (sí, soy culpable, pero a cambio aprendí el concepto de volumenes y cómo definirlos y gestionarlos en k8s), una aplicación Grails (o SpringBoot) etc. todos ellos usando el descriptor propio de k8s (que es bastante verbose) .Así mismo, en todos estos ejemplos siempre ha sido desplegar una aplicación simple y en algunos casos usar una instancia Postgre desplegada mediante el interface gráfico.

En este post voy a contar otra de las funcionalidades con las que cuenta este servicio, okteto stack, y que es propia de él, es decir, no es válida para otro cluster k8s que no sea Okteto, al menos hasta donde yo se.

Okteto stack es muy parecido a un docker-compose (de hecho salvo algunas particularidades podrías usar un docker-compose) donde podemos definir las características principales de nuestro(s) servicio(s) e incluso generar la imagen al estilo de este. La ventaja que tiene es que nos permite desplegar en el cluster de kubernetes servicios sin necesidad de la verbosidad de este (ficheros de cientos de líneas de kubernetes se convierten en unas pocas con Okteto stack).

## Objetivo

Para verlo en su conjunto vamos a hacer el ejercicio de desplegar 2 microservicios en el mismo namespace de tal forma que:

- 

uno de ellos, API, va a realizar la labor de hacer de ApiGateway, enrutando las llamadas al otro servicio.

- 

el otro servicio, Customer-Service, va a persistir entidades en una base de datos "bajo su control"

Vamos a usar lo menos posible características propias de Kubernetes, aunque se podría usar. La idea es no añadir más complejidad en este ejemplo

<dl>
<dt>NOTE</dt>
<dd>
<p>He creado un repositorio donde puedes bajarte el código e intentar desplegarlo en tu cluster. Simplemente
descargarlo de <a href="https://gitlab.com/jorge-aguilera/okteto-stack">https://gitlab.com/jorge-aguilera/okteto-stack</a> y sigue las instrucciones del README</p>
</dd>
<dt>INFO</dt>
<dd>
<p>Para poder ejecutar y desplegar este ejemplo vas a necesitar una cuenta en Okteto así como la herramienta de
consola <code>okteto-cli</code></p>
</dd>
</dl>

## Customer Service

Customer service es un microservicio destinado a guardar y devolver entidades de Customer para lo que usará una base de datos PostgreSQL.

En un proyecto típico de Docker tendriamos un docker-compose similar a:

okteto-stack.yml

```
services:

  dbcustomers:
    image: okteto.dev/dbcustomers
    build:
      context: .
      dockerfile: DockerDatabase
      args:
        - DATABASE_NAME
        - DATABASE_USERNAME
        - DATABASE_PASSWORD
    ports:
      - 5432
    volumes:
      - data_customers:/var/lib/postgresql/data/

  customer-service:
    image: okteto.dev/customer-service
    build:
      context: .
      dockerfile: DockerApp
    ports:
      - 8080
    environment:
      - DATABASE_HOST
      - DATABASE_NAME
      - DATABASE_USERNAME
      - DATABASE_PASSWORD
    depends_on:
      dbcustomers:
        condition: service_healthy

volumes:
  data_customers:
```

y lo podríamos desplegar mediante `docker-compose build && docker-compose up -d`

Como puedes observar el build requiere de dos ficheros de docker: DockerDatabase y DockerApp

DockerDatabase

```
FROM postgres:latest

ARG DATABASE_NAME
ARG DATABASE_USERNAME
ARG DATABASE_PASSWORD

ENV POSTGRES_HOST_AUTH_METHOD=trust
ENV POSTGRES_PASSWORD=${DATABASE_PASSWORD}
ENV POSTGRES_DB=${DATABASE_NAME}
ENV POSTGRES_USER=${DATABASE_USERNAME}

ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 5432
CMD ["postgres"]
```

DockerApp

```
FROM gradle:7.2.0-jdk11 AS build
COPY . /home/gradle
RUN gradle build -x check

FROM openjdk:16-alpine
WORKDIR /home/app
COPY --from=build /home/gradle/build/docker/layers/libs /home/app/libs
COPY --from=build /home/gradle/build/docker/layers/resources /home/app/resources
COPY --from=build /home/gradle/build/docker/layers/application.jar /home/app/application.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/home/app/application.jar"]
```

<dl>
<dt>INFO</dt>
<dd>
<p>Obviamente todo esto puede ser más simplificado, por ejemplo no necesitarias construir una imagen para Postgre y
podrías usar la standard. Así mismo tampoco necesitarías el DockerApp si construyes y subes la imagen a tu repo
mediante las herramientas que uses habitualmente.</p>
</dd>
</dl>

Si en el directorio del servicio customer ejecutamos:

`okteto deploy stack`

y todo va bien, habrás desplegado un stack `customers` donde se está ejecutando dos pods: un postgresql y un servicio

## API

Api sigue el mismo principio pero su fichero es más simple puesto que no necesita base de datos. Sin embargo su okteto-stack.yml ya no es compatible con docker-compose porque vamos a incluir una funcionalidad propia de esta plataforma:

okteto-stack.yml

```
services:
  api:
    image: okteto.dev/api
    build:
      context: .
      dockerfile: DockerApp
    ports:
      - 8080

endpoints:
  - path: /
    service: api
    port: 8080
```

Si en el proyecto `api` ejecutamos:

`okteto stack deploy`

veremos que aparece un nuevo stack `api`en nuestro namespace con un sólo pod el cual además ofrece un endpoint
abierto a internet en `https://api-TUNAMESPACE.cloud.okteto.net/`

## Probando

Si hemos conseguido desplegar los dos stacks ahora podriamos ejecutar peticiones REST a API el cual las enrutará a Customer (yo utilizo `httpie` pero puedes usar curl, postman o la herramienta que uses habitualmente)

`http https://api-TUNAMESPACE.cloud.okteto.net/api name=test`

`http https://api-TUNAMESPACE.cloud.okteto.net/api`

Si no hay ningún error el primer comando habrá creado un customer con el name igual a test y la segunda llamada nos devolverá una lista de un sólo elemento

## Actualizando

Una vez validado que nuestros dos stacks se encuentran funcionando y hablan entre sí, vamos a realizar un cambio en uno de ellos y redesplegarlo sin necesidad de actualizar el otro.

Por ejemplo, vamos a editar `CustomerController.java` en el proyecto de `customer-service` y vamos a cambiar la línea 27

`return customerRepository.save(customerEntity);`

por

`return customerEntity;`

(Simplemente vamos a hacer que ya no se puedan añadir más customers pero sin devolver un error)

Para redesplegar la actualización ejecutaremos desde el proyecto de `customer-service`:

`okteto stack deploy --build`

Lo cual va a volver a generar y subir la imagen actualizada para después redesplegar el servicio. Una vez desplegado podremos repetir los pasos de intentar añadir un nuevo customer

`http https://api-TUNAMESPACE.cloud.okteto.net/api name=otro`

`http https://api-TUNAMESPACE.cloud.okteto.net/api`

y deberíamos ver que aunque el post para crear el customer "otro" no ha dado error en realidad no ha sido guardado y el get nos sigue devolviendo una lista con un sólo elemento.

Lo interesante de este cambio es ver que API no ha sido afectado y seguía ejecutándose.

## Microservicios

Si abrimos la configuración de API (application.yml) podemos ver que este está enrutando las llamadas a un servicio del que sólo sabe el nombre:

```
micronaut:
  application:
    name: api
  http:
    services:
      customers:
        url: http://customer-service:8080
```

Es decir, API hará de proxy hacia un host que responda a `customer-service` el cual corresponde con el del stack customer. En una solución más compleja se podría usar sistemas de descubrimiento de servicios pero para este ejemplo es suficiente.

Otra de las ventajas de esta aproximación es que podemos agrupar los servicios por dependencias (por ejemplo customer-service y su base de datos) pudiendo actualizar un stack sin tener que actuar en los otros.

## Conclusión

En un petproject en el que estoy trabajando he empezado a usar esta funcionalidad y me está siendo muy valiosa para poder tener separados los diferentes servicios a la vez que defino en cada uno las dependencias. Así por ejemplo tengo unos stacks de infraestructura (Kafka y Databases) y otros para cada servicio

### Kafka

```
name: kafka

services:

  kafdrop:
    image: obsidiandynamics/kafdrop:3.28.0-SNAPSHOT
    ports:
      - 9000
    environment:
      - KAFKA_BROKERCONNECT=kafka:9092
      - JVM_OPTS=-Xms16M -Xmx48M -Xss180K -XX:-TieredCompilation -XX:+UseStringDeduplication -noverify

  zookeeper:
    image: docker.io/bitnami/zookeeper:3-debian-10
    ports:
      - 2181
    volumes:
      - data_zookeeper:/bitnami
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes

  kafka:
    image: docker.io/bitnami/kafka:2-debian-10
    ports:
      - 9092
    volumes:
      - data_kafka:/bitnami
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - ALLOW_PLAINTEXT_LISTENER=yes
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092

volumes:
  data_zookeeper:
    driver_opts:
      size: 1Gi
  data_kafka:
    driver_opts:
      size: 2Gi
```

### Databases

```
name: databases

services:

  dbcustomer:
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust
    image: okteto.dev/dbcustomer
    build:
      context: .
      dockerfile: DockerfileCustomer
      args:
        - USERNAME=$USERNAME
        - PASSWORD=$PASSWORD
    ports:
      - 5432
    volumes:
      - data_customer:/var/lib/postgresql/data/

  dbcore:
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust
    image: okteto.dev/dbcore
    build:
      context: .
      dockerfile: DockerfileCore
      args:
        - USERNAME=$USERNAME
        - PASSWORD=$PASSWORD
    ports:
      - 5432
    volumes:
      - data_core:/var/lib/postgresql/data/

volumes:
  data_customer:
    driver_opts:
      size: 1Gi
  data_core:
    driver_opts:
      size: 1Gi
```