Skip to main content

Command Palette

Search for a command to run...

AWS Application Composer

Diseñe visualmente y cree rápidamente aplicaciones sin servidor

Updated
7 min read
AWS Application Composer

En Noviembre pasado se realizó el AWS re:Invent 2022 el máximo evento de AWS que se realizó en Las Vegas, Nevada que nos proporcionó una gran cantidad de anuncios, nuevos servicios y actualizaciones.

AWS Application Composer

Uno de esos servicios nuevos llamo mi atencion: AWS Application Composer (https://docs.aws.amazon.com/application-composer/latest/dg/what-is-composer.html ), un servicio que permite desde una interfaz Drag-and-Drop arrastrar y soltar distintos servicios asi como interconectarlos para poder crear una arquitectura cloud serverless, entregando el código en cloudformation.

De esta manera podemos concentrarnos en diseñar nuestra arquitectura y dejar que Application Composer construya cómo se logra, sin necesidad de ser un experto en AWS CloudFormation.

Podemos usarlo creando un proyecto de cero o cargando uno ya existente, en ambos casos tenemos 2 modos disponibles: conectados o no conectados,si elegimos el estado conectado los cambios que hagamos en las fuentes se verán reflejados en AWS Application Composer y viceversa, los cambios realizados en AWS Application Composer se reflejarán en el código fuente de nuestra aplicación.

Después de diseñar la aplicación serverless con AWS Application Composer, podemos usar cualquier servicio compatible con AWS CloudFormation para la implementación.

En mi caso, utilice: AWS SAM, que me permite realizar depuración y pruebas locales, así como ayudarme con el empaquetado y despliegue de mi aplicación.

La aplicación que desarrollé es un servicio de procesamiento de video. Para ello emplee un servicio que me ayuda con esta tarea que es AWS Elemental MediaConvert (https://aws.amazon.com/es/mediaconvert/) que funciona tomando un video como entrada y, transcodificando el video a una o más versiones diferentes y luego coloca estas versiones en otro bucket S3.

Para hacer la aplicación serverless, utilice un bucket S3 en donde se cargan los archivos a procesar, cada vez que se detectaba el evento cuando se cargaba un video al bucket, éste se enviaba a una cola SQS que era consumida por una función Lambda (está basada en NodeJS versión 18) que se encarga de llamar a AWS Elemental MediaConvert para el procesamiento del video.

Manos a la obra!

1- Configuración de AWS CLI

Necesitamos configurar AWS CLI en nuestro entorno local.

http://docs.aws.amazon.com/cli/latest/userguide/installing.html

Tenemos que tener nuestras credenciales de seguridad (Access keys)

https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey_CLIAPI

Luego ejecutamos desde una terminal en su sistema:

aws configure

La CLI de AWS solicita varias cosas:

Las credenciales de usuario (Access keys)

La región que usaremos: us-east-1

El formato de salida predeterminado: json.

2 - Obtener el endpoint de la API de MediaConvert

Para ello iremos al panel del AWS Elemental MediaConvert y luego iremos al menú lateral izquierdo en la sección Account, este endpoint lo usaremos para una variable de entorno de nuestra función lambda

3- Crear un rol: media-convert-role

Necesitamos crear un rol para nuestra función lambda, MediaConvert necesita tener acceso a S3, así como a API Gateway. Sin esta función, MediaConvert simplemente no se ejecutará cuando intente invocarlo desde Lambda.

Iremos al panel IAM y luego a Roles, haremos clic en Crear rol

Seleccionamos AWS Service y luego en el select buscamos MediaConvert, clic en el botón Siguiente

AWS ya ha predefinido qué políticas necesita para este rol.

Elegimos Siguiente

Establecemos el nombre

Asigne a su función el nombre media-convert-role y luego haga clic en Crear rol.

Necesitaremos el ARN del rol para una variable de entorno de nuestra función lambda.

4 - Bucket de carga de videos a procesar

Desde AWS Application Composer tenemos el primer bucket que es donde se cargaran los videos a procesar, el nombre de este bucket por defecto es el nombre de nuestra pila o stack junto con el número de nuestra cuenta de AWS:

BucketName: !Sub ${AWS::StackName}-bucket-${AWS::AccountId}

Lo podemos encontrar en la pestaña Template o bien desde nuestras fuentes en template.yaml y buscar dentro de recursos a nuestro Bucket, podemos cambiarlo a otro nombre de nuestra preferencia.

5- Nuestra cola SQS

Cada vez que un video se cargue al bucket S3 definido en el paso 4, este evento es enviado a una cola SQS que se creará cuando despleguemos nuestro proyecto.

6- La función Lambda

En el caso de nuestra función Lambda podemos indicar varios parámetros dentro de AWS Application Composer, seleccionando el elemento y luego la opción Details que mostrará más opciones en el lateral derecho de la pantalla

Podemos especificar el runtime de nuestra Lambda, así como la ubicación del código de nuestra función (src/Function) y el método que se ejecuta cuando nuestra función es invocada (index.handler)

También podemos especificar otros valores desde esta pantalla, como es el caso de las políticas de permisos:

Version: '2012-10-17'

Statement:

- Effect: Allow

Action:

- iam:GetRole

- iam:PassRole

- mediaconvert:*

Resource: arn:aws:iam::619747668685:role/media-convert-role

Y también las variables de entorno que necesitamos especificar:

  • MEDIA_ENDPOINT: el endpoint de mediaConvert

  • MEDIA_ROLE: el ARN del rol previamente creado

  • TRANSCODED_VIDEO_BUCKET: el nombre de nuestro bucket de salida donde almacenaremos los videos procesador por MediaConvert

Todas las modificaciones que hagamos desde AWS Application Composer las veremos reflejadas en nuestro archivo local template.yaml

También debemos definir nuestro rol para la función lambda: aws-sam-lambda-role

El cual haremos referencia en nuestro archivo local template.yaml

El código fuente de la función lambda es:

'use strict';

const AWS = require('aws-sdk')

// Obtiene el valor de la variable de entorno MEDIA_ENDPOINT que está configurada en template.yml
const mediaConvert = new AWS.MediaConvert({
    endpoint: process.env.MEDIA_ENDPOINT
});
// Obtiene el nombre del bucket donde se enviaran los videos procesados de la variable de entorno TRANSCODED_VIDEO_BUCKET que está configurada en template.yml
const outputBucketName = process.env.TRANSCODED_VIDEO_BUCKET;

exports.handler = async (event) => {

    const s3Event = JSON.parse(event.Records[0].body)

    try {
        const key = s3Event.Records[0].s3.object.key;
        const sourceKey = decodeURIComponent(key.replace(/\+/g, ' '));
        const outputKey = sourceKey.split('.')[0];

        // Establece la ubicación del video de entrada para la definición de trabajo de MediaConvert
        const input = 's3://' + s3Event.Records[0].s3.bucket.name + '/' + key;

        // Create S3 service object
        const outputBucket = new AWS.S3({apiVersion: '2006-03-01'});

        // Create the parameters for calling createBucket
        var bucketParams = {
          Bucket : outputBucketName
        };

        // call S3 to create the bucket
        outputBucket.createBucket(bucketParams, function(err, data) {
          if (err) {
            console.log("Error", err);
          } else {
            console.log("Success", data.Location);
          }
        });

        // Establece el bucket de salida para los nuevos archivos de video
        const output = 's3://' + outputBucketName + '/' + outputKey + '/';


        const job = {
          //  Obtiene el ARN del rol MediaConvert que se especifica en template.yml
            "Role": process.env.MEDIA_ROLE,
            "Settings": {
                "Inputs": [{
                    "FileInput": input,
                    // Especifica el selector de audio para la definición de trabajo de MediaConvert. De manera predeterminada, nombrará una sola pista de audio en el video.
                    "AudioSelectors": {
                        "Audio Selector 1": {
                            "SelectorType": "TRACK",
                            "Tracks": [1]
                        }
                    }
                }],
                "OutputGroups": [{
                    "Name": "File Group",
                    "Outputs": [{
                        "Preset": "System-Generic_Hd_Mp4_Avc_Aac_16x9_1920x1080p_24Hz_6Mbps",
                        "Extension": "mp4",
                        "NameModifier": "_16x9_1920x1080p_24Hz_6Mbps"
                    }, {
                        "Preset": "System-Generic_Hd_Mp4_Avc_Aac_16x9_1280x720p_24Hz_4.5Mbps",
                        "Extension": "mp4",
                        "NameModifier": "_16x9_1280x720p_24Hz_4.5Mbps"
                    }, {
                        "Preset": "System-Generic_Sd_Mp4_Avc_Aac_4x3_640x480p_24Hz_1.5Mbps",
                        "Extension": "mp4",
                        "NameModifier": "_4x3_640x480p_24Hz_1.5Mbps"
                    }],
                    "OutputGroupSettings": {
                        "Type": "FILE_GROUP_SETTINGS",
                        "FileGroupSettings": {
                            "Destination": output
                        }
                    }
                }]
            }
        };

        const mediaConvertResult = await mediaConvert.createJob(job).promise();
        console.log('mediaConvertResult ' , mediaConvertResult);

    } catch (error) {
        console.error(error);
    }
};

Nuestra función lambda utiliza AWS SDK por lo que necesitamos agregar dicha dependencia dentro de nuestro package.json.

{
  "name": "function",
  "version": "1.0.0",
  "devDependencies": {
    "aws-sdk": "^2.1270.0"
  }
}

Y luego de ello ejecutar npm i dentro de la carpeta donde su ubica este archivo src/Function/ y debemos agregar unas líneas a nuestro template.yaml.

Quedando finalmente así:

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${AWS::StackName}-alfalfita-cloud-bucket-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: alias/aws/s3
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      NotificationConfiguration:
        QueueConfigurations:
          - Event: s3:ObjectCreated:*
            Queue: !GetAtt Queue.Arn
          - Event: s3:ObjectRemoved:*
            Queue: !GetAtt Queue.Arn
    DependsOn:
      - BucketToQueuePermission
  BucketBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref Bucket
      PolicyDocument:
        Id: RequireEncryptionInTransit
        Version: '2012-10-17'
        Statement:
          - Principal: '*'
            Action: '*'
            Effect: Deny
            Resource:
              - !GetAtt Bucket.Arn
              - !Sub ${Bucket.Arn}/*
            Condition:
              Bool:
                aws:SecureTransport: 'false'
  Queue:
    Type: AWS::SQS::Queue
  BucketToQueuePermission:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action: sqs:SendMessage
            Resource: !GetAtt Queue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Sub arn:${AWS::Partition}:s3:::${AWS::StackName}-alfalfita-cloud-bucket-${AWS::AccountId}
      Queues:
        - !Ref Queue
  Function:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub
        - Stack ${AWS::StackName} Function ${ResourceName}
        - ResourceName: Function
      CodeUri: src/Function
      Handler: index.handler
      Role: arn:aws:iam::619747668685:role/aws-sam-lambda-role
      Runtime: nodejs18.x
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Events:
        Queue:
          Type: SQS
          Properties:
            Queue: !GetAtt Queue.Arn
            BatchSize: 1
      Environment:
        Variables:
          MEDIA_ENDPOINT: https://lxlxpswfb.mediaconvert.us-east-1.amazonaws.com
          MEDIA_ROLE: arn:aws:iam::619747668685:role/media-convert-role
          TRANSCODED_VIDEO_BUCKET: serverless-video-transcoded-alfalfita-cloud-06122022
      Policies:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - iam:GetRole
              - iam:PassRole
              - mediaconvert:*
            Resource: arn:aws:iam::619747668685:role/media-convert-role
    Metadata:
      BuildProperties:
        UseNpmCi: true
  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: !Sub /aws/lambda/${Function}
Transform: AWS::Serverless-2016-10-31

Para más información:

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-build.html

6 - Instalar AWS SAM CLI

Para prepararnos para desplegar nuestra aplicación en la nube, necesitamos un servicio compatible con Cloudformation para la implementación, en este caso AWS SAM, la documentación para la instalación la podemos encontrar en:

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html

Una vez instalado AWS SAM CLI podemos usarlo desde la terminal, con los diversos comandos que proporciona

Ejecutamos primero sam-build

Y luego preparamos el despliegue guiado que nos permitirá establecer ciertos valores:

Confirmamos los cambios, y veremos como los recursos se irán creando:

Confirmamos que la pila existe en el panel de CloudFormation

Ahora probamos nuestra función, subiendo un video al bucket S3

Veremos que tendremos un nuevo bucket donde encontraremos diversos formatos de nuestro video original

AWS Elemental MediaConvert: https://docs.aws.amazon.com/mediaconvert/index.html

El repositorio con la solución lo encuentras en:

https://github.com/alfalfita/decoder-video-with-sam-and-lambda

More from this blog

A

Alfalfita ☁️

19 posts

Software developer motivada y apasionada por la tecnología en la nube (AWS)