Despliegue Canary en funciones lambdas
Tags: aws, lambda, canary, despliegue
Published: 2022-12-05 06:44:53

En esta ocasión se realizará un ejemplo sencillo para comprender los fundamentos de un despliegue Canario usando servicios de AWS, este tipo de despliegue está siendo muy usado en la industria por el crecimiento de serverless y contenedores.

## ¿Qué es el despliegue de tipo Canary?

El despliegue Canary consiste en desplegar nuevas versiones de software de manera incremental, es decir, durante un tiempo una porción de usuarios acceden a la nueva versión desplegada y otro grupo de usuarios siguen consumiendo a la versión anterior, los usuarios sirven como carnada para consumir los servicios e indicar que todo está marchando bien y esto permite seguir incrementando de forma gradual a la última versión desplegada. Si durante el uso existe un error, se realiza un rollback dejando en la versión anterior y sin afectar a los demás usuarios.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tou10h25j0mkbzhv6y4t.png)

## Ejemplo práctico

Para el ejemplo práctico se crea dos funciones lambdas y se usa el servicio de Api Gateway para poder realizar pruebas mediante Postman y evidenciar el despliegue incremental Canary.

**Requisitos previos**

- Cuenta de AWS y tener configurado AWS CLI y SAM CLI
- Conocimientos esenciales cloudformation, lambdas y api gateway
- Manejo de Postman y Jmeter
- NodeJs con typescript

**Inicio de proyecto**

Se inicia el proyecto a partir de una plantilla básica proporcionada en SAM CLI, ejecutando el siguiente comando:

``` sh
sam init
```
Seguir las instrucciones

![Config 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0r6ferofbxihz1mohdga.png)

![Config 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdc0d49kvekj59agb7w7.png)

Se genera un proyecto básico de un hello world, se borra la entrada principal app.ts y todos los test. Se crea una estructura de carpetas de la siguiente manera:

> Nota: la carpeta hello-world ahora es app.

![Estructura de proyecto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkfwn0jxpeqpl6iz4tsl.png)

En el directorio donde está definido el package.json instalar las dependencias:

``` sh
npm install
```

A continuación se muestra el código de ejemplo de cada función, no se está conectando a ninguna base de datos para este ejemplo, pero ya depende de lo que necesitemos, el objetivo es demostrar el despliegue:

**createCustomer.ts**

``` ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { Customer } from '../models/Customer';

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
    const customer: Customer = JSON.parse(event.body ?? '{}');
    //TODO: generate id for customer
    customer.id = '1234';
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Customer created',
            customer
        }),
    };
};
```

**getCustomerById.ts**

``` ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { Customer } from '../models/Customer';

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {

    const id = event.pathParameters?.id;

    const customer: Customer = {
        id: id ?? '',
        name: 'John Doe',
        email: 'doe@qw.com',
        age: 30
    };

    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Customer found',
            customer
        }),
    };
};
```
**Customer.ts**

En models solo se tiene definido un tipo de dato Customer:

``` ts
export type Customer = {
    id: string;
    name: string;
    email: string;
    age: number;
};
```

**Archivo template.yaml**

Listo ahora que está todo el código fuente, es necesario configurar la infraestructura a desplegar en AWS.

Se realiza las configuraciones globales de las funciones y de la api:

![Globals](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdxnsdkznc9lo07uterp.png)

> Nota: La propiedad DeploymentPreference configura a la función lambda para realizar el despliegue incremental. Para conocer todos los tipos que existen se puede revisar el siguiente enlace: [Deploy Canary.](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/automating-updates-to-serverless-apps.html)

En los recursos se definen las Lambdas y el ApiGateway a utilizar:

![Image properties](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iukfyi83cd5elura3jx1.png)

> Nota: Se configura un alias de producción para simular sobre ese escenario.

Se muestra toda la configuración en template.yaml

``` yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  init-app

  Sample SAM Template for customers
  
Globals:
  Function:
    CodeUri: app
    Timeout: 10
    Tracing: Active
    Runtime: nodejs16.x
    Architectures:
      - x86_64
    DeploymentPreference:
      Enabled: true
      Type: 'Linear10PercentEvery1Minute'
  Api:
    TracingEnabled: True

Resources:

  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: v1
      Variables:
        version_alias: 'production'
      EndpointConfiguration:
        Type: REGIONAL 
               
  CreateCustomerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/functions/createCustomer.handler
      FunctionName: !Sub ${AWS::StackName}-create-customer
      AutoPublishAlias: production
      Events:
        CreateCustomer:
          Type: Api
          Properties:
            Path: /customers
            Method: post
            RestApiId: !Ref ApiGateway
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        EntryPoints: 
        - src/functions/createCustomer.ts
  
  GetCustomerByIdFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/functions/getCustomerById.handler
      FunctionName: !Sub ${AWS::StackName}-get-customer-by-id
      AutoPublishAlias: production
      Events:
        CreateCustomer:
          Type: Api
          Properties:
            Path: /customers/{id}
            Method: get
            RestApiId: !Ref ApiGateway
    Metadata: 
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        EntryPoints: 
        - src/functions/getCustomerById.ts

```

> Nota: Algunos recursos como permisos y roles se crea detrás de escena gracias a la ayuda de SAM.

**Despliegue de stack**

En el directorio principal en donde está la plantilla template.yaml ejecutar los siguientes comandos para empaquetar y desplegar:

``` sh
sam build
sam deploy --guided --capabilities CAPABILITY_NAMED_IAM
```

![Pasos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3gw31f5iuhifythj04g.png)

Se revisa en la consola de cloudformation para ver los recursos creados en el stack:

![Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wcxkckjtu6coagkv6lzi.png)

Si se revisa una de las funciones podemos ver el alias y una tercera versión creada, esto debido a que anteriormente se hizo unos despliegues de prueba, si es primer despliegue desde cero estará una versión 1:

![Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/77ypvll9w5len8died4w.png)

Para el ejemplo se toma la función que simula crear un Cliente para realizar las pruebas, revisar el recurso de ApiGateway para obtener la URL a consultar:

![Url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jn3qtdb4iws89hprajd3.png)

Se comprueba el funcionamiento en Postman:

![Postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0y2i9qktoozihrhnaoky.png)

**Actualizar código para evidenciar el despliegue Canary**

Para poder ver la ejecución de Canary hay que editar el código y desplegar, se podrá ver que de a poco va incrementando el peso de la versión.

En este caso se edita la función que simula crear un Cliente.

Se instala las siguientes dependencias y se edita el código como se muestra:

``` sh
npm i short-uuid
```

![Code actualizado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p82av7evqixn6p7st5ug.png)

Una vez realizado los cambios se debe ejecutar los comandos:

``` sh
sam build
sam deploy --capabilities CAPABILITY_NAMED_IAM
```

**Prueba de Canary**

El despliegue tipo Canary empieza, por cada minuto se incrementa de forma lineal un 10% para pasar a la versión 4.

![Canary despliegue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y04g6509ftbnk6g45bil.png)

Para poder evidenciar que la carga se está distribuyendo entre las dos versiones se realiza una prueba de carga con Jmeter.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clgkedxvtnl40sfv939c.png)

> Nota: Si no conoces sobre Jmeter para pruebas de carga puedes revisar el siguiente [recurso](https://www.youtube.com/watch?v=T8mWjZmVQPU).

En los logs de la función de Cloudwatch se puede observar como ha realizado llamadas entre las dos versiones 3 y 4.

![Jmeter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lmml0riaak3vy0lqsk2y.png)

Espero hayas aprendido las bases sobre canary con este sencillo ejemplo en funciones lambdas, gracias.

## Referencias

- https://kryptonsolid.com/que-es-la-prueba-de-canario-despliegue-canario/
- https://www.youtube.com/watch?v=mOjMHTcfuIw
- https://www.ibm.com/docs/es/urbancode-deploy/6.2.4?topic=processes-modeling-blue-green-rolling-canary-deployments