Serverless compose

Seguimos hablando de arquitectura serverless, estaba vez una nueva herramienta se lanzó recientemente, que realmente está muy interesante y que a las personas que trabajamos con estructuras mono-repo será de mucha ayuda, para dar un poco de contexto, cuando hablamos de microservicio ya he hablado bastante sobre el tema de las dependencias y como cada servicio debe ser totalmente independiente, sin embargo, cuando se despliega se puede requerir algún valor de otro microservicio, esto sencillamente porque un servicio necesita encontrar a otro, lo cual puede llegar a generar algunas 'dependencias' a la hora de realizar despliegues, en el caso de serverless framework, existen algunas estrategias apalancadas sobre cloudformation, el inconveniente con esto, es que este es un universo entero, y esto puede hacer un poco complicado como referenciar alguna salida de un servicio a otro, por eso llega al juego serverless compose Ver Figura 1, el cual es básicamente un orquestador de proyectos realizados para serverless con la finalidad de  facilitar su despliegue a producción, en diferentes tecnologías ya existen un montón de orquestadores similares, para no ir muy lejos podemos referenciar uno muy conocido que es docker-compose este artículo lo dedicaremos a hablar un poco sobre esta nueva tool

Figura 1: Serverless Compose

CloudFormation en serverless

Una de las cosas interesantes que hace serverless framework es utilizar por debajo todo el stack de CloudFormation, generando una abstracción para que el desarrollador no tenga esa complejidad, este beneficio nos lleva a que en cierto momento del despliegue vamos a necesitar exportar algunas variables que otros servicios requieren, por ejemplo tenemos los siguientes archivos

service: libs
frameworkVersion: '3'
custom:
  scripts:
    hooks:
      'before:package:initialize': python3 scripts/rename_folder_before.py
      'before:package:finalize': python3 scripts/rename_folder_after.py
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
layers:
  sharedLibs:
    path: .
    compatibleRuntimes:
      - python3.8
    compatibleArchitectures:
      - x86_64
resources:
  Outputs:
    SharedLibsExport:
        Value:
          Ref: SharedLibsLambdaLayer
        Export:
          Name: SharedLibsLambdaLayer-${opt:stage, 'dev'}

plugins:
  - serverless-plugin-scripts
shared/serverless.yml
service: mylambdaprojectv2

frameworkVersion: '3'

provider:
  layers:
    - ${cf:libs-dev.SharedLibsExport}
    - arn:aws:lambda:us-east-1:770693421928:layer:Klayers-p38-pandas:1
  tracing:
    apiGateway: true
    lambda: true
  deploymentBucket:
    name: ejemplo-test
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: us-east-1
  environment:
    API: ${file(./config.${opt:stage, 'dev'}.json):API}
    TABLE_NAME: ${file(./config.${opt:stage, 'dev'}.json):TABLE_NAME}

  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - xray:PutTraceSegments
            - xray:PutTelemetryRecords
          Resource: "*"

functions:
  register:
    handler: service.register.register
    timeout: 10
    events:
      - http:
          path: register
          method: post
          cors: true


package:
  exclude:
    - node_modules/**
    - venv/**


plugins:
  - serverless-deployment-bucket
  - serverless-python-requirements


user/serverless.yml

este es el ejemplo perfecto para la necesidad de usar compose, en el ejemplo anterior tenemos dos archivos de dos proyectos en el mismo repo, shared/serverless.yml el cual consiste en la creación de un layer, es decir, una capa compartida entre diferentes lambdas, si ven, al final del código se hace un export de ese layer, aquí se utiliza CloudFormation, luego en user/serverless.yml en el layer se utiliza la siguiente instrucción

 ${cf:libs-dev.SharedLibsExport}

esta instrucción importa del CloudFormation la variable que se exportó en el layer, esto puede volverse tan complejo como se quiera, por ejemplo traer esta variable por diferentes ambientes, y una validación que no  se tiene en cuenta, es que necesariamente el invocarla, no garantiza que existe, entonces después del despliegue esto puede generar error, por esto manejar la importación de variables se vuelve complejo, porque la creación y el uso de las variables no están sincronizadas, para esa sincronización utilizaremos serverless-compose

Serverless Compose

serverless-compose esta herramienta nos ayudará a realizar múltiples despliegues utilizando la estrategia mono-repo, solamente  referenciando un archivo centralizado el cual agrupa las referencias a los demás proyectos, el ejemplo anterior lo mejoraríamos de la siguiente forma

service: mylambdaprojectv2

frameworkVersion: '3'

provider:
  layers:
    -${param:my-layer}
  tracing:
    apiGateway: true
    lambda: true
  deploymentBucket:
    name: ejemplo-test
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
  region: us-east-1
  environment:
    API: ${file(./config.${opt:stage, 'dev'}.json):API}
    TABLE_NAME: ${file(./config.${opt:stage, 'dev'}.json):TABLE_NAME}

  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - xray:PutTraceSegments
            - xray:PutTelemetryRecords
          Resource: "*"

functions:
  register:
    handler: service.register.register
    timeout: 10
    events:
      - http:
          path: register
          method: post
          cors: true


package:
  exclude:
    - node_modules/**
    - venv/**


plugins:
  - serverless-deployment-bucket
  - serverless-python-requirements




user/serverless.yml
service: libs
frameworkVersion: '3'
custom:
  scripts:
    hooks:
      'before:package:initialize': python3 scripts/rename_folder_before.py
      'before:package:finalize': python3 scripts/rename_folder_after.py
provider:
  name: aws
  runtime: python3.8
  lambdaHashingVersion: 20201221
layers:
  sharedLibs:
    path: .
    compatibleRuntimes:
      - python3.8
    compatibleArchitectures:
      - x86_64
resources:
  Outputs:
    SharedLibsExport:
        Value:
          Ref: SharedLibsLambdaLayer
        Export:
          Name: SharedLibsLambdaLayer-${opt:stage, 'dev'}

plugins:
  - serverless-plugin-scripts
shared/serverles.yml

de estos archivos el que cambio fue el del usuario, ahora se usa la instrucción

layers:
    -${param:my-layer}

esta instrucción viene del siguiente archivo que agregamos a la raíz y llamaremos serverless-compose.yml

services:
  shared-layer:
    path: src/shared/libs

  user:
    path: src/user
    params:
      my-layer: ${shared-layer.SharedLibsExport}
    dependsOn:
      - shared-layer

este archivo tiene todas las configuraciones compartidas, adicional para que sea compatible serverless con servelress-compose, se tiene que usar la versión  > v3.15.0, este archivo serverless-compose.yml tiene las siguientes instrucciones

  • services inicia la configuración y abre el espacio para agregar el nombre de los servicios, este es irrelevante y puede ser cualquier nombre
  • path es la ubicación del proyecto, o donde se puede encontrar el archivo serverless.yml
  • params, son todos los valores que se exportan usando CloudFormation, y que se usaran como valores dependientes entre servicios
  • dependsOn le indica a serverless que ese servicio depende de 1 o más servicios que primero deben ser desplegados
  • config es un parámetro opcional el cual es usado si el archivo de serverless tiene un nombre diferente, ejemplo serverless.api.yml

para ejecutar el compose nos ubicamos en donde se encuentra el archivo y ejecutamos

sls deploy --stage dev

con esto se realiza el despliegue de la siguiente forma

Figura 2: Despliegue con serverless-compose

si ven shared-layer se despliega de primero, esto debido a la dependencia, y seguidamente  se despliega el servicio que es dependiente, hay otros comandos interesantes los cuales se pueden encontrar en la documentación, por ejemplo uno que me gusta mucho es

serverless logs

cuando se desea bajar el stack igualmente se ejecuta el comando

sls remove
Figura 3: Removiendo los servicios con dependencias

y también los remueve en orden de dependencias, primero los no dependientes, luego los dependientes, este enfoque definitivamente es muy interesante y otorga una solución bastante usable para estos problemas de variables y referencias entre servicios, finalmente les dejo el repositorio con todo el código usado

Conclusiones

Orquestar servicios siempre será una necesidad, esta herramienta es totalmente amigable para integrarse con entornos de ci-cd, y permite compartir dependencias de despliegue de una forma sencilla, el roadmap de serverless-compose apunta a tener otros beneficios interesantes, uno de ellos el despliegue multi región. En conclusión,  este tool es algo me parece, es un gran acierto, que además suple  las necesidades de los devs y ops

¿Te gusto el post y quisieras poder aplicar lo aprendido?

Únete al equipo de Simetrik AQUI