Tutorial: cómo usar un desencadenador de Amazon S3 para crear imágenes en miniatura - AWS Lambda

Tutorial: cómo usar un desencadenador de Amazon S3 para crear imágenes en miniatura

En este tutorial, creará una función de Lambda y configurará un desencadenador para Amazon Simple Storage Service (Amazon S3). Amazon S3 invoca la función de CreateThumbnail para cada archivo de imagen que se carga en un bucket de S3. La función lee el objeto de imagen del bucket de S3 de origen y crea una imagen en miniatura para guardarla en un bucket de S3 de destino.

nota

Este tutorial requiere un nivel moderado de conocimiento del dominio AWS y Lambda. Le recomendamos que primero intente Tutorial: Usar un trigger Amazon S3 para invocar una función Lambda.

En este tutorial, usa el AWS Command Line Interface (AWS CLI) para crear los siguientes recursos de AWS:

Recursos de Lambda

  • Una función de Lambda. Puede elegir Node.js, Python o Java para el código de función.

  • Un paquete de implementación de archivos .zip para la función.

  • Política de acceso que concede permiso de Amazon S3 para invocar la función.

Recursos de AWS Identity and Access Management (IAM)

  • Un rol de ejecución con una política de permisos asociada para conceder permisos que necesita la función.

Recursos de Amazon S3

  • Un bucket de origen de S3 con una configuración de notificación que invoca la función.

  • Un bucket de destino de S3 donde la función guarda las imágenes modificadas.

Requisitos previos

  • Una cuenta de AWS

    Para usar Lambda y otros AWS servicios, necesita una cuenta AWS. Si no dispone de una cuenta, visite aws.amazon.com y, a continuación, elija Create an AWS Account (Crear una cuenta de AWS). Para obtener instrucciones, consulte ¿Cómo puedo crear y activar una AWS cuenta nueva?

  • Línea de comandos

    Para completar los siguientes pasos, necesita un terminal de línea de comandos o shell para ejecutar comandos. Los comandos y la salida esperada se muestran en bloques separados:

    aws --version

    Debería ver los siguientes datos de salida:

    aws-cli/2.0.57 Python/3.7.4 Darwin/19.6.0 exe/x86_64

    Para los comandos largos, se utiliza un carácter de escape (\) para dividir un comando en varias líneas.

    En Linux y macOS, use su administrador de shell y paquetes preferido. En Windows 10, puede instalar Windows Subsystem para Linux para obtener una versión de Ubuntu y Bash integrada con Windows.

  • AWS CLI

    En este tutorial, utilizará comandos de AWS CLI para crear e invocar la función de Lambda. Instale la AWS CLI y configúrela con sus credenciales de AWS.

  • Herramientas de idioma

    Instale las herramientas de soporte de idiomas y un administrador de paquetes para el idioma que desea utilizar: Node.js, Python o Java. Para ver las herramientas sugeridas, consulte Herramientas para crear código.

Paso 1. Crear buckets de S3 y cargar un objeto de muestra

Siga estos pasos para crear buckets de S3 y cargar un objeto.

  1. Abra la consola de Amazon S3.

  2. Cree dos buckets de S3. Se debe nombrar el bucket de destino como source-resized, donde origen es el nombre del bucket de origen. Por ejemplo, un b de origen denominado mybucket y un bucket de destino denominado mybucket-resized.

  3. En el bucket de origen, cargue un objeto .jpg, por ejemplo, HappyFace.jpg.

    Debe crear este objeto de muestra antes de probar la función Lambda. Cuando se prueba la función manualmente con el comando Lambda invoke, se pasan los datos del evento de muestra a la función que especifica el nombre del bucket de origen y HappyFace.jpg como el objeto recién creado.

Paso 2. Cree la política de IAM.

Cree una política de IAM que defina los permisos para la función Lambda. La función debe tener permisos para:

  • Obtener el objeto del bucket S3 de origen.

  • Coloque el objeto redimensionado en el bucket S3 objetivo.

  • Escriba logs en Amazon CloudWatch Logs.

Para crear una política de IAM

  1. Abra la página Policies (Políticas) en la consola de IAM.

  2. Elija Create Policy.

  3. Elija la pestaña JSON y luego pegue la siguiente política. Asegúrese de reemplazar mybucket por el nombre del bucket de origen que creó anteriormente.

    { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:PutLogEvents", "logs:CreateLogGroup", "logs:CreateLogStream" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject" ], "Resource": "arn:aws:s3:::mybucket/*" }, { "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": "arn:aws:s3:::mybucket-resized/*" } ] }
  4. Elija Next: Tags (Siguiente: Etiquetas).

  5. Elija Next: Review.

  6. En Review policy (Revisar política), para Name (Nombre), escriba AWSLambdaS3Policy.

  7. Elija Create Policy.

Paso 3. Creación del rol de ejecución

Cree el rol de ejecución que concederá a su función de Lambda permiso para obtener acceso a los recursos de AWS.

Para crear un rol de ejecución

  1. Abra la página de roles en la consola de IAM.

  2. Elija Create role (Crear rol).

  3. Cree un rol con las propiedades siguientes:

    • Trusted entity (Entidad de confianza)Lambda.

    • Política de permisosAWSLambdas3Policy

    • Nombre del rollambda-s3-role

Paso 4. Crear el código de función

En los ejemplos de código siguientes, el evento Amazon S3 contiene el nombre del bucket de S3 de origen y el nombre de la clave de objeto. Si el objeto es un archivo de imagen.jpg o .png, lee la imagen del bucket de origen, genera una imagen en miniatura y, a continuación, guarda la miniatura en el bucket de S3 de destino.

Tenga en cuenta lo siguiente:

  • El código asume que el bucket de destino existe y que su nombre es una concatenación del nombre del bucket de origen y -resized.

  • Para cada archivo de miniatura creado, el código de la función de Lambda deriva el nombre de la clave de objeto como una concatenación de resized- y el nombre de la clave de objeto de origen. Por ejemplo, si la clave del objeto de origen es sample.jpg, el código crea una objeto de miniatura cuya clave es resized-sample.jpg.

Node.js

Copie el siguiente código de ejemplo en un archivo denominado index.js.

ejemplo index.js

// dependencies const AWS = require('aws-sdk'); const util = require('util'); const sharp = require('sharp'); // get reference to S3 client const s3 = new AWS.S3(); exports.handler = async (event, context, callback) => { // Read options from the event parameter. console.log("Reading options from event:\n", util.inspect(event, {depth: 5})); const srcBucket = event.Records[0].s3.bucket.name; // Object key may have spaces or unicode non-ASCII characters. const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " ")); const dstBucket = srcBucket + "-resized"; const dstKey = "resized-" + srcKey; // Infer the image type from the file suffix. const typeMatch = srcKey.match(/\.([^.]*)$/); if (!typeMatch) { console.log("Could not determine the image type."); return; } // Check that the image type is supported const imageType = typeMatch[1].toLowerCase(); if (imageType != "jpg" && imageType != "png") { console.log(`Unsupported image type: ${imageType}`); return; } // Download the image from the S3 source bucket. try { const params = { Bucket: srcBucket, Key: srcKey }; var origimage = await s3.getObject(params).promise(); } catch (error) { console.log(error); return; } // set thumbnail width. Resize will set the height automatically to maintain aspect ratio. const width = 200; // Use the sharp module to resize the image and save in a buffer. try { var buffer = await sharp(origimage.Body).resize(width).toBuffer(); } catch (error) { console.log(error); return; } // Upload the thumbnail image to the destination bucket try { const destparams = { Bucket: dstBucket, Key: dstKey, Body: buffer, ContentType: "image" }; const putResult = await s3.putObject(destparams).promise(); } catch (error) { console.log(error); return; } console.log('Successfully resized ' + srcBucket + '/' + srcKey + ' and uploaded to ' + dstBucket + '/' + dstKey); };
Python

Copie el siguiente código de ejemplo en un archivo denominado lambda_function.py.

ejemplo lambda_function.py

import boto3 import os import sys import uuid from urllib.parse import unquote_plus from PIL import Image import PIL.Image s3_client = boto3.client('s3') def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(tuple(x / 2 for x in image.size)) image.save(resized_path) def lambda_handler(event, context): for record in event['Records']: bucket = record['s3']['bucket']['name'] key = unquote_plus(record['s3']['object']['key']) tmpkey = key.replace('/', '') download_path = '/tmp/{}{}'.format(uuid.uuid4(), tmpkey) upload_path = '/tmp/resized-{}'.format(tmpkey) s3_client.download_file(bucket, key, download_path) resize_image(download_path, upload_path) s3_client.upload_file(upload_path, '{}-resized'.format(bucket), key)
Java

El código Java implementa la interfaz RequestHandler proporcionada en la biblioteca de aws-lambda-java-core. Cuando se crea una función de Lambda, se especifica la clase como controlador (en este ejemplo de código, example.handler). Para obtener más información acerca de cómo utilizar interfaces para proporcionar un controlador, consulte Interfaces de controlador.

El controlador utiliza S3Event como tipo de entrada, que proporciona métodos convenientes para que su código de función lea información del evento Amazon S3 entrante. Amazon S3 invoca su función de Lambda de forma asíncrona. Como está implementando una interfaz que requiere que especifique un tipo de retorno, el controlador utiliza String como tipo de retorno.

Copie el siguiente código de ejemplo en un archivo denominado Handler.java.

ejemplo Handler.java

package example; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.S3Event; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.event.S3EventNotification.S3EventNotificationRecord; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.AmazonS3ClientBuilder; public class Handler implements RequestHandler<S3Event, String> { private static final float MAX_WIDTH = 100; private static final float MAX_HEIGHT = 100; private final String JPG_TYPE = (String) "jpg"; private final String JPG_MIME = (String) "image/jpeg"; private final String PNG_TYPE = (String) "png"; private final String PNG_MIME = (String) "image/png"; public String handleRequest(S3Event s3event, Context context) { try { S3EventNotificationRecord record = s3event.getRecords().get(0); String srcBucket = record.getS3().getBucket().getName(); // Object key may have spaces or unicode non-ASCII characters. String srcKey = record.getS3().getObject().getUrlDecodedKey(); String dstBucket = srcBucket + "-resized"; String dstKey = "resized-" + srcKey; // Sanity check: validate that source and destination are different // buckets. if (srcBucket.equals(dstBucket)) { System.out .println("Destination bucket must not match source bucket."); return ""; } // Infer the image type. Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(srcKey); if (!matcher.matches()) { System.out.println("Unable to infer image type for key " + srcKey); return ""; } String imageType = matcher.group(1); if (!(JPG_TYPE.equals(imageType)) && !(PNG_TYPE.equals(imageType))) { System.out.println("Skipping non-image " + srcKey); return ""; } // Download the image from S3 into a stream AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient(); S3Object s3Object = s3Client.getObject(new GetObjectRequest( srcBucket, srcKey)); InputStream objectData = s3Object.getObjectContent(); // Read the source image BufferedImage srcImage = ImageIO.read(objectData); int srcHeight = srcImage.getHeight(); int srcWidth = srcImage.getWidth(); // Infer the scaling factor to avoid stretching the image // unnaturally float scalingFactor = Math.min(MAX_WIDTH / srcWidth, MAX_HEIGHT / srcHeight); int width = (int) (scalingFactor * srcWidth); int height = (int) (scalingFactor * srcHeight); BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = resizedImage.createGraphics(); // Fill with white before applying semi-transparent (alpha) images g.setPaint(Color.white); g.fillRect(0, 0, width, height); // Simple bilinear resize g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(srcImage, 0, 0, width, height, null); g.dispose(); // Re-encode image to target format ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageIO.write(resizedImage, imageType, os); InputStream is = new ByteArrayInputStream(os.toByteArray()); // Set Content-Length and Content-Type ObjectMetadata meta = new ObjectMetadata(); meta.setContentLength(os.size()); if (JPG_TYPE.equals(imageType)) { meta.setContentType(JPG_MIME); } if (PNG_TYPE.equals(imageType)) { meta.setContentType(PNG_MIME); } // Uploading to S3 destination bucket System.out.println("Writing to: " + dstBucket + "/" + dstKey); try { s3Client.putObject(dstBucket, dstKey, is, meta); } catch(AmazonServiceException e) { System.err.println(e.getErrorMessage()); System.exit(1); } System.out.println("Successfully resized " + srcBucket + "/" + srcKey + " and uploaded to " + dstBucket + "/" + dstKey); return "Ok"; } catch (IOException e) { throw new RuntimeException(e); } } }

Paso 5. Creación del paquete de implementación

El paquete de implementación es un archivo .zip que contiene el código de la función de Lambda y sus dependencias.

Node.js

La función de ejemplo debe incluir el módulo sharp en el paquete de implementación.

Para crear un paquete de implementación

  1. Abra un terminal o shell de línea de comandos en un entorno Linux. Asegúrese de que la versión Node.js del entorno local coincide con la versión Node.js de la función.

  2. Guarde el código de función como index.js en un directorio llamado lambda-s3.

  3. Instale la biblioteca sharp con npm. Para Linux, utilice el siguiente comando:

    npm install sharp

    Después de este paso, tiene la siguiente estructura de directorios:

    lambda-s3 |- index.js |- /node_modules/sharp └ /node_modules/...
  4. Cree un paquete de implementación con el código de la función y sus dependencias. Establezca la opción -r (recursiva) para que el comando zip comprima las subcarpetas.

    zip -r function.zip .
Python

Dependencias.

Para crear un paquete de implementación

  • Recomendamos utilizar el comando sam build de la CLI de AWS SAM con la opción --use-container para crear paquetes de implementación que contengan bibliotecas escritas en C o C ++, como la biblioteca Pillow (PIL).

Java

Dependencias.

  • aws-lambda-java-core

  • aws-lambda-java-events

  • aws-java-sdk

Para crear un paquete de implementación

Paso 6. Creación de la función de Lambda

Para crear la función

  • Cree una función de Lambda con el comando create-function.

    Node.js
    aws lambda create-function --function-name CreateThumbnail \ --zip-file fileb://function.zip --handler index.handler --runtime nodejs12.x \ --timeout 10 --memory-size 1024 \ --role arn:aws:iam::123456789012:role/lambda-s3-role
    nota

    Si utiliza la versión 2 de la AWS CLI, agregue los siguientes parámetros al comando:

    --cli-binary-format raw-in-base64-out

    El comando create-function especifica el controlador de función como index.handler. Este nombre refleja el nombre de la función como handler, y el archivo donde se almacena el código del controlador como index.js. Para obtener más información, consulte Controlador de la función AWS Lambda en Node.js. El comando especifica un tiempo de ejecución de nodejs12.x. Para obtener más información, consulte Tiempos de ejecución de Lambda..

    Python
    aws lambda create-function --function-name CreateThumbnail \ --zip-file fileb://function.zip --handler lambda_function.lambda_handler --runtime python3.8 \ --timeout 10 --memory-size 1024 \ --role arn:aws:iam::123456789012:role/lambda-s3-role
    nota

    Si utiliza la versión 2 de la AWS CLI, agregue los siguientes parámetros al comando:

    --cli-binary-format raw-in-base64-out

    El comando create-function especifica el controlador de función como lambda_function.lambda_handler. Este nombre refleja el nombre de la función como lambda_handler, y el archivo donde se almacena el código del controlador como lambda_function.py. Para obtener más información, consulte Controlador de funciones de Lambda en Python. El comando especifica un tiempo de ejecución de python3.8. Para obtener más información, consulte Tiempos de ejecución de Lambda..

    Java
    aws lambda create-function --function-name CreateThumbnail \ --zip-file fileb://function.zip --handler example.handler --runtime java11 \ --timeout 10 --memory-size 1024 \ --role arn:aws:iam::123456789012:role/lambda-s3-role
    nota

    Si utiliza la versión 2 de la AWS CLI, agregue los siguientes parámetros al comando:

    --cli-binary-format raw-in-base64-out

    El comando create-function especifica el controlador de función como example.handler. La función puede utilizar el formato de controlador abreviado de package.Class porque la función implementa una interfaz de controlador. Para obtener más información, consulte Controlador de funciones de AWS Lambda Java. El comando especifica un tiempo de ejecución de java11. Para obtener más información, consulte Tiempos de ejecución de Lambda..

Para el parámetro de rol, reemplace 123456789012 por la ID de la cuenta AWS. El comando del ejemplo anterior especifica un tiempo de espera de 10 segundos como configuración de la función. En función del tamaño de los objetos que cargue, es posible que necesite para aumentar el tiempo de espera utilizando el siguiente comando de AWS CLI:

aws lambda update-function-configuration --function-name CreateThumbnail --timeout 30

Paso 7. Pruebe la función de Lambda

Invoque la función de Lambda de forma manual utilizando los datos del evento de muestra de Amazon S3.

Para probar la función de Lambda

  1. Guarde los siguientes datos del evento de muestra de Amazon S3 en un archivo denominado inputFile.txt. Asegúrese de reemplazar sourcebucket y HappyFace.jpg con el nombre del bucket S3 de origen y una clave de objeto.jpg, respectivamente.

    { "Records":[ { "eventVersion":"2.0", "eventSource":"aws:s3", "awsRegion":"us-west-2", "eventTime":"1970-01-01T00:00:00.000Z", "eventName":"ObjectCreated:Put", "userIdentity":{ "principalId":"AIDAJDPLRKLG7UEXAMPLE" }, "requestParameters":{ "sourceIPAddress":"127.0.0.1" }, "responseElements":{ "x-amz-request-id":"C3D13FE58DE4C810", "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" }, "s3":{ "s3SchemaVersion":"1.0", "configurationId":"testConfigRule", "bucket":{ "name":"sourcebucket", "ownerIdentity":{ "principalId":"A3NL1KOZZKExample" }, "arn":"arn:aws:s3:::sourcebucket" }, "object":{ "key":"HappyFace.jpg", "size":1024, "eTag":"d41d8cd98f00b204e9800998ecf8427e", "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko" } } } ] }
  2. Invoque la función con el siguiente comando de invoke. Tenga en cuenta que el comando solicita la ejecución asíncrona (--invocation-type Event). Opcionalmente, puede invocar la función sincrónicamente especificando RequestResponse como valor del parámetro invocation-type.

    aws lambda invoke --function-name CreateThumbnail --invocation-type Event \ --payload file://inputFile.txt outputfile.txt
    nota

    Si utiliza la versión 2 de la AWS CLI, agregue los siguientes parámetros al comando:

    --cli-binary-format raw-in-base64-out
  3. Verifique que se crea la miniatura en el bucket de S3 de destino.

Paso 8. Configure Amazon S3 para publicar eventos

Complete la configuración para que Amazon S3 pueda publicar eventos creados por objetos en Lambda e invocar la función de Lambda. En este paso, hará lo siguiente:

  • Agregue permisos a la política de acceso de la función para permitir que Amazon S3 invoque la función.

  • Agregue una configuración de notificaciones al bucket de S3 de origen. En la configuración de notificaciones, debe proporcionar lo siguiente:

    • El tipo de evento para el que desea que Amazon S3 publique eventos. Para este tutorial, debe especificar el tipo de evento de s3:ObjectCreated:* para que Amazon S3 publique eventos cuando se creen objetos.

    • La función a invocar.

Para añadir permisos a la política de función.

  1. Ejecute el siguiente comando de add-permission para conceder a la entidad principal del servicio Amazon S3 (s3.amazonaws.com) permisos para realizar la acción lambda:InvokeFunction. Tenga en cuenta únicamente que se concede permiso a Amazon S3 para invocar la función si se cumplen las siguientes condiciones:

    • Se detecta un evento de creación de objeto en un bucket de S3 específico.

    • El bucket de S3 es propiedad de su cuenta de AWS. Si elimina un bucket, es posible que otra cuenta de AWS cree un bucket con el mismo nombre de recurso de Amazon (ARN).

    aws lambda add-permission --function-name CreateThumbnail --principal s3.amazonaws.com \ --statement-id s3invoke --action "lambda:InvokeFunction" \ --source-arn arn:aws:s3:::sourcebucket \ --source-account account-id
  2. Verifique la política de acceso de la función ejecutando el comando get-policy.

    aws lambda get-policy --function-name CreateThumbnail

Para que Amazon S3 publique eventos creados por objetos en Lambda, agregue una configuración de notificación en el bucket S3 de origen.

importante

Este procedimiento configura el bucket de S3 para invocar su función cada vez que se crea un objeto en el bucket. Asegúrese de configurar esta opción solo en el bucket de origen. No configure su función para que cree objetos en el bucket de origen, o su función podría permitir que se invoque continuamente en un bucle.

Para configurar notificaciones

  1. Abra la consola de Amazon S3.

  2. Elija el nombre del bucket de S3 de origen.

  3. Elija la pestaña Properties (Propiedades).

  4. En Event notifications (Notificaciones de eventos), elija Create event notification (Crear notificación de evento) para configurar una notificación con los siguientes ajustes:

    • Nombre del eventolambda-trigger

    • Tipos de eventoAll object create events

    • DestinoLambda function

    • Función de LambdaCreateThumbnail

Para obtener más información sobre la configuración de eventos, vea Habilitar y configurar notificaciones de eventos mediante la consola de Amazon S3 en Guía del usuario de la consola de Amazon Simple Storage Service.

Paso 9. Pruebe con el disparador de S3

Pruebe la configuración de la siguiente manera:

  1. Cargue objetos .jpg o .png en el bucket de S3 de origen mediante la consola de Amazon S3.

  2. Compruebe para cada objeto de imagen que se crea una miniatura en el bucket de S3 de destino mediante la función de CreateThumbnail Lambda.

  3. Vea los logs en la consola de CloudWatch.

Paso 10. Limpiar los recursos

Ahora puede eliminar los recursos que creó para este tutorial, a menos que desee conservarlos. Si elimina los recursos de AWS que ya no utiliza, evitará gastos innecesarios en su cuenta de AWS.

Para eliminar la función de Lambda

  1. Abra la Página de Funciones de la consola de Lambda.

  2. Seleccione la función que ha creado.

  3. Elija Actions (Acciones), Delete (Eliminar).

  4. Elija Eliminar.

Para eliminar la directiva que creó

  1. Abra la página Directivas de la consola IAM.

  2. Seleccione la política que creó (AWSLambdas3Policy).

  3. Elija Policy actions (Acciones de política), Delete (Eliminar).

  4. Elija Eliminar.

Para eliminar el rol de ejecución

  1. Abra la Página de Roles de la consola de IAM.

  2. Seleccione el rol de ejecución que ha creado.

  3. Elija Delete role (Eliminar rol).

  4. Elija Sí, eliminar.

Para eliminar el bucket de S3

  1. Abra la consola de Amazon S3.

  2. Seleccione el bucket que ha creado.

  3. Elija Eliminar.

  4. Escriba el nombre del bucket en el cuadro de texto.

  5. Elija Confirm.