Cómo desplegar una aplicación basada en microservicios
por Sergio Alberto Martínez, el 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.
- Segundo: Valores de 0 a 59, admite usar el separador / para especificar una periodicidad Ej: */5 significa cada 5 segundos.
- Minuto: Valores de 0 a 59, admite usar el separador / para especificar una periodicidad.
- 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.
- Día del mes: Valores de 0 a 31, admite los separadores - y /.
Mes: Valores de 1 a 12. - 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.