Lecciones

Cómo desplegar una aplicación basada en microservicios

Escrito por Sergio Alberto Martínez | 30 de marzo de 2020

El propósito de este artículo es mostrar cómo se realiza un despliegue de una aplicación basada en microservicios inicialmente en un entorno Docker y luego desplegarlo en un sistema de orquestación de contenedores Openshift/OKD.

La arquitectura propuesta

La arquitectura se compone por microservicios de 2 clases, el primero es Redis (Caché) y el segundo tipo una API REST Stateless replicable que provee acceso a la base de datos en Memoria RAM Redis en sus dos modos de trabajo HashOperations (Base de datos NoSQL) con operaciones GET, POST, PUT, DELETE y ListOperations (Listas/Colas) con operaciones POP y PUSH.

La aplicación

El esquema de la API rest es el siguiente:

  • Conseguir todos los objetos bajo la clave como un mapa
    GET: /api/v1/{redis_key}
  • Conseguir un objeto bajo la clave y su hash
    GET: /api/v1/{redis_key}/{hash_key}
  • Agregar un nuevo objeto, el método responde con su hash, el valor objeto debe ir dentro del body
    POST: /api/v1/{redis_key}
  • Actualizar un objeto con el hash específico, el valor del objeto debe ir dentro del body.
    PUT: /api/v1/{redis_key}/{hash_key}
    Nota: Al actualizar el objeto el hash se conserva
  • Eliminar el objeto bajo la clave y son el hash específico DELETE: /api/v1/{redis_key}/{hash_key}

Haciendo uso de esta API es posible almacenar datos temporalmente haciendo uso del poder de Redis de forma NoSQL. 

Operaciones con listas o colas

  • POP: Traer el último ingresado tiene como parámetro opcional right: false: para hacer LPOP o LPUSH, true: para hacer RPOP o LPUSH
    GET: /api/v1/list/{redis_key}
  • PUSH: agregar un elemento a la lista, también tiene como parámetro a right POST: /api/v1/list/{redis_key}

Haciendo uso de esta API es posible implementar colas tipo LIFO: Last In/First Out y FIFO: First In/First Out. 

Información general de la API

Importante: Cada redis_key solo se puede usar en un esquema, es decir, como una cola o como una base de datos NoSQL.

  • Esta API cuenta con un Job para realizar un snapshot cada 30 minutos de sí misma, el tiempo es configurable en application.yaml mediante una expresión cron. Mirese Variables de entorno
  • Para este ejemplo el controlador principal maneja los datos de la manera siguiente:
    K: redis_key del tipo String
    H: hash_key del tipo Integer
    V: value del tipo Object

Variables de entorno

  • REDIS_DB_HOST: Especifica la IP o Nombre del Servidor Redis, por defecto es localhost.
  • REDIS_DB_PORT: Especifica el Puerto del Servidor Redis, por defecto es 6379.
  • REDIS_DB_PASSWORD: Especifica la contraseña del Servidor Redis, por defecto no tiene valor.
  • REDIS_DB_DUMP_CRON: Especifica la expresión cron para el guardado automático Servidor
    Redis, por defecto es 0 */30 * * * *, esta expresión como esta específica que se le envía una orden para hacer un Snapshot en disco al Servidor Redis cada 30 minutos.

Las expresiones cron en Spring constan de 6 campos que especifican una condición a cumplir.

  1. Segundo: Valores de 0 a 59, admite usar el separador / para especificar una periodicidad Ej: */5 significa cada 5 segundos.
  2. Minuto: Valores de 0 a 59, admite usar el separador / para especificar una periodicidad.
  3. Hora: Valores de 0 a 23, admite usar el separador / para especificar una periodicidad, también admite el separador - que indica un rango Ej: 8-10 significa de 8AM a 10AM.
  4. Día del mes: Valores de 0 a 31, admite los separadores - y /.
    Mes: Valores de 1 a 12.
  5. Día de la semana: Acepta valores MON TUE WED THU FRI SAT SUN, también acepta el separador - y el comodín ?, Ej: MON-FRI significa de Lunes a Viernes.

Trabajo futuro

  • Se puede copiar el controlador y cambiar los tipos de datos en uno nuevo de acuerdo a las necesidades.
  • Aún falta implementar el tiempo de vida de los registros.
  • También sería útil agregar implementación para usar Redis en clúster y usar su servicio Sentinel, todo esto se puede hacer programáticamente.

Diagrama de clases

Las fuentes están en repositorio OKD-Despliegue

Despliegue de la aplicación

Archivos de despliegue con docker-compose

Con los siguientes dos archivos y con Docker Compose podemos:

  • Descargar el código fuente del repositorio.
  • Construir el artefacto de la API.
  • Construir y desplegar los microservicios en contenedores.

/docker-compose.yml

version: '3'
services:
  redis-api:
    build:
      dockerfile: Dockerfile
      context: ./redis-api
    ports:
      - "8080:8080"
    networks:
      - academia-pragma
    restart: unless-stopped
    deploy: # Este elemento es soportado por docker-swarm, no por docker-compose
      replicas: 3
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
    environment:
      - REDIS_DB_HOST=redis-db
      - REDIS_DB_PORT=6379
      - REDIS_DB_PASSWORD=
      - REDIS_DB_DUMP_CRON=0 */1 * * * *
  redis-db:
    image: redis
    ports:
      - "6379:6379"
    networks:
      - academia-pragma
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
    volumes:
      - redis-data:/data
  load-balancer:
    image: nginx
    ports:
      - "8080:8080"
    networks:
      - academia-pragma
    restart: unless-stopped
networks:
  academia-pragma:
    driver: bridge
volumes:
  redis-data:

/redis/Dockerfile

 

# Clonando repositorio
FROM alpine/git
WORKDIR /app
RUN ["git", "clone", "https://git.pragma.com.co/sergio.martinez/OKD-Despliegue.git"]

# Construccion de artefacto con Maven
FROM maven:3.5-jdk-8-alpine
WORKDIR /app
COPY --from=0 /app/OKD-Despliegue/redis /app
# Necesario para agregar la configuracion de proxy para que maven pueda conectarse
COPY --from=0 /app/OKD-Despliegue/proxy-settings.xml /root/.m2/settings.xml
RUN mvn install

# Construcción de la imagen del contenedor
ARG REDIS_DB_HOST=app_redis_db
ARG REDIS_DB_PORT
ARG REDIS_DB_PASSWORD
ARG REDIS_DB_DUMP_CRON
FROM registry.redhat.io/redhat-openjdk-18/openjdk18-openshift
COPY --from=1 /app/target/redis-0.0.1-SNAPSHOT.jar app.jar
USER root
RUN chmod a+r app.jar
USER jboss
ENV REDIS_DB_HOST=$REDIS_DB_HOST
ENV REDIS_DB_PORT=$REDIS_DB_PORT
ENV REDIS_DB_PASSWORD=$REDIS_DB_PASSWORD
ENV REDIS_DB_DUMP_CRON=$REDIS_DB_DUMP_CRON
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","./app.jar"]

El primer objetivo es correr la aplicación en Docker.

docker-compose up

Despliegue en Openshift

El siguiente objetivo es desplegar la arquitectura en Openshift de manera manual.  Para hacer el despliegue en Openshift, tendríamos las posibilidades siguientes:

  • Construir la imagen con Docker y publicarla en un registry privado para luego desplegarla.
  • Desplegar directamente desde el código.

Despliegue usando la interfaz web y subiendo la imagen a un registro de imágenes

Construcción de la imagen

Con la imagen construida mediante el archivo Dockerfile Véase archivos de construccion

docker compose build .

Publicar la imágen en registry privado o en el registry de Openshift

docker login registry


docker tag redis-api registry/cuenta/redis-api
docker push registry/cuenta/redis-api

Despliegue usando archivos de Openshift

REDIS DATABASE

Variables de entorno

Cada campo está codificado en Base64

apiVersion: v1
data:
  DATABASE_SERVICE_NAME: cmVkaXM=
  MEMORY_LIMIT: NTEyTWk=
  NAMESPACE: b3BlbnNoaWZ0
  REDIS_PASSWORD: a2FwcGE=
  REDIS_VERSION: My4y
  VOLUME_CAPACITY: MUdp
kind: Secret
metadata:
  name: redis-conf-secret
type: Opaque

Imagen de Redis

apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  labels:
    app: redis-db
  name: redis-db
spec:
  replicas: 1
  selector:
    app: redis-db
    deploymentconfig: redis-db
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      labels:
        app: redis-db
        deploymentconfig: redis-db
    spec:
      containers:
        - envFrom:
            - secretRef:
                name: redis-conf-secret
          image: >-
            docker-registry.default.svc:5000/openshift/redis:latest
          imagePullPolicy: Always
          name: redis-db
          ports:
            - containerPort: 6379
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /var/lib/redis/data
              name: redis-db-1
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
        - emptyDir: {}
          name: redis-db
  test: false

Servicio

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis-db
  name: redis-db
spec:
  clusterIP: 172.30.184.126
  ports:
    - name: 6379-tcp
      port: 6379
      protocol: TCP
      targetPort: 6379
  selector:
    deploymentconfig: redis-db
  sessionAffinity: None
  type: ClusterIP

REDIS API

Variables de entorno

apiVersion: v1
data:
  REDIS_DB_HOST:
  REDIS_DB_PORT: 6379
  REDIS_DB_PASSWORD:
  REDIS_DB_DUMP_CRON: 0 */1 * * * *
kind: Secret
metadata:
  name: redis-conf-secret
type: Opaque


Imagen de REDIS API

apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  labels:
    app: redis-api
  name: redis-api
spec:
  replicas: 3
  selector:
    app: redis-api
    deploymentconfig: redis-api
  strategy:
    activeDeadlineSeconds: 21600
    resources: {}
    rollingParams:
      intervalSeconds: 1
      maxSurge: 25%
      maxUnavailable: 25%
      timeoutSeconds: 600
      updatePeriodSeconds: 1
    type: Rolling
  template:
    metadata:
      labels:
        app: redis-api
        deploymentconfig: redis-api
    spec:
      containers:
        - envFrom:
            - secretRef:
                name: redis-conf-secret
          image: >-
            sergioamza/redis-api:latest
          imagePullPolicy: Always
          name: redis-api
          ports:
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8443
              protocol: TCP
            - containerPort: 8778
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  test: false
  triggers:
    - type: ConfigChange
    - imageChangeParams:
        automatic: true
        containerNames:
          - redis-api
        from:
          kind: ImageStreamTag
          name: 'redis-api:latest'
      type: ImageChange

Servicio

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis-api
  name: redis-api
spec:
  clusterIP: 172.30.150.51
  ports:
    - name: 8080-tcp
      port: 8080
      protocol: TCP
      targetPort: 8080
    - name: 8443-tcp
      port: 8443
      protocol: TCP
      targetPort: 8443
    - name: 8778-tcp
      port: 8778
      protocol: TCP
      targetPort: 8778
  selector:
    deploymentconfig: redis-api
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

Enrutamiento

apiVersion: route.openshift.io/v1
kind: Route
metadata:
  labels:
    app: redis-api
  name: redis-api
spec:
  port:
    targetPort: 8080-tcp
  to:
    kind: Service
    name: redis-api
    weight: 100
  wildcardPolicy: None

Conclusiones

  • Usando Openshift / OKD se pudo realizar un despliegue de una arquitectura de microservicios de forma ágil, con la seguridad como prioridad, debido al enfoque que tiene Openshft / OKD en ese aspecto.
  • Se obtuvo una arquitectura equivalente tanto en Docker como en OKD/Openshift.