byandrev's blog

13 min readRead in English

AWS Lambda y API Gateway: Crea tu primera API (consola y CLI)

Los comandos de este post son bash. En Windows usa WSL, Git Bash o CloudShell, que ya viene con el CLI configurado. En PowerShell el heredoc, el JSON con comillas simples y zip no funcionan igual.

Antes de empezar

Si usas tu cuenta personal de AWS con un usuario administrador, salta esta sección. Ya tienes todo.

Si estás en una cuenta de empresa, compartida, o en una de la universidad, es donde aparecen los AccessDenied.

Primero, mira con qué usuario estás autenticado.

bash
aws sts get-caller-identity

Este tutorial toca cuatro servicios, y cada uno necesita permisos:

ParaNecesitas
Crear el rol de ejecuciónIAMFullAccess
Crear e invocar la LambdaAWSLambda_FullAccess
Crear la APIAmazonAPIGatewayAdministrator
Leer y borrar logsCloudWatchLogsFullAccess

Son policies gestionadas por AWS. Verifica los nombres exactos en tu consola antes de pedirlas, porque algunas cambiaron de nombre con los años.

IAMFullAccess es mucho poder. Quien la tiene puede crearse los permisos que quiera, o sea que en la práctica es acceso de administrador. Para aprender está bien. Para un usuario de producción, no.

Crea la función Lambda

Debemos ir al servicio AWS Lambda y crear nuestra primera función.

Servicio AWS Lambda
Servicio AWS Lambda

Tenemos 3 opciones:

  1. Author from scratch: Comenzar con un "Hello World".
  2. Use a blueprint: Una plantilla o configuración preestablecida.
  3. Container image: Con una imagen usando el servicio de ECR.

Voy a utilizar la opción de Author from scratch para simplemente hacer un Hello World.

Elegimos un nombre para la función y el runtime, o sea, el lenguaje en el que se va a ejecutar. Hay opciones como Node.js, Python, Java, Ruby, etc.

Creación de AWS Lambda
Creación de AWS Lambda

Ya con esto le podemos dar en Create function. Una vez creado veremos las siguientes pestañas:

Configuración de una AWS Lambda
Configuración de una AWS Lambda

En la pestaña de Code debemos escribir el código que se ejecutará cada vez que se llame a la función. Ten en cuenta que las Lambdas se cobran por dos cosas: el número de invocaciones y los GB-segundo (duración × memoria asignada).

El Always Free de AWS ofrece 1 millón de peticiones y 400,000 GB-segundos de cómputo cada mes. AWS Lambda pricing.

Lo mismo con el AWS CLI

La consola hace una cosa por debajo que el CLI no, y es crear el rol de ejecución. Así que empezamos por ahí.

bash
# Guarda estos valores, los usaremos en todos los comandos
 
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=$(aws configure get region || echo "$AWS_REGION")

Revisa que ACCOUNT_ID y $REGION no estén vacíos. echo "$ACCOUNT_ID / $REGION". Ten en cuenta que todos estos comandos van en la misma terminal. Si la cierras, las variables se pierden.

bash
aws iam create-role \
  --role-name lambda-hello-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": { "Service": "lambda.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }]
  }'
 
aws iam attach-role-policy \
  --role-name lambda-hello-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Si te sale un AccessDenied al crear el rol, no es que el rol no exista: es que tu usuario no tiene permiso para crear roles. El mensaje completo te dice cuál falta (is not authorized to perform: iam:CreateRole). Pasa mucho en cuentas de empresa o compartidas. Necesitas permisos de IAM, o que alguien con acceso de administrador te cree el rol.

Esa policy es la que permite escribir logs en CloudWatch. Sin ella la función corre, pero no verás nada.

Crea un archivo index.mjs con este contenido:

js
export const handler = async (event) => {
  return {
    statusCode: 200,
    body: "Hello from Lambda!",
  };
};

Y empaquétalo con zip:

bash
zip function.zip index.mjs
bash
aws lambda create-function \
  --function-name hello-lambda \
  --runtime nodejs22.x \
  --handler index.handler \
  --role arn:aws:iam::$ACCOUNT_ID:role/lambda-hello-role \
  --zip-file fileb://function.zip

Si el comando falla con un error de rol inválido, espera unos segundos y repítelo. IAM puede tardar en propagar el rol que creamos.

Y para probarla debemos ejecutar el siguiente comando:

bash
aws lambda invoke --function-name hello-lambda response.json
cat response.json

Los runtimes disponibles cambian y los viejos se deprecan. Revisa cuál está vigente antes de copiar nodejs22.x, puedes verlos en Lambda runtimes.

Para subir cambios de código después:

bash
zip function.zip index.mjs
aws lambda update-function-code \
  --function-name hello-lambda \
  --zip-file fileb://function.zip

Logs de las Lambdas

Para ver cómo está funcionando tu Lambda, entra a la pestaña Monitor. Si quieres ver los logs completos, ve a CloudWatch, donde podrán ver cada evento que ocurra como lo pueden ver en este video:

Ver logs de una Lambda en CloudWatch

Lo mismo con el AWS CLI

Este comando te muestra los logs en vivo sin abrir la consola:

bash
aws logs tail /aws/lambda/hello-lambda --follow

Para ver solo lo reciente:

bash
aws logs tail /aws/lambda/hello-lambda --since 10m

API Gateway

Ahora vamos al servicio API Gateway y damos en Create an API.

Servicio API Gateway de AWS
Servicio API Gateway de AWS

Existen varios tipos que podemos usar:

  1. HTTP API
  2. WebSocket API
  3. REST API
  4. REST API Private

Usa HTTP API, es la v2, más simple y cuesta $1 por millón de requests contra $3.50 del REST API. Cambia a REST API solo si necesitas API keys, throttling por cliente, WAF o caching en el gateway. Verifica precios en los docs antes de publicar, además el precio depende de la región y por el tramo de volumen. Te recomiendo que leas Choose between REST APIs and HTTP APIs y Amazon API Gateway pricing.

Creación de la API Gateway

  1. Le damos un nombre y el tipo de IP address type (IPv4 o Dualstack).

Creación de la API Gateway
Creación de la API Gateway

  1. Definimos un stage, por ejemplo v1 o prod.

Definición de stages de la API Gateway
Definición de stages de la API Gateway

Un stage es una versión desplegada y con nombre de tu API. Cada una tiene su propia URL (.../v1, .../prod), su propia config (variables, logs, throttling) y apunta a un deployment distinto.

Lo mismo con el AWS CLI

Ojo con el subcomando: HTTP API es apigatewayv2, no apigateway. Si usas el segundo estás creando una REST API.

bash
API_ID=$(aws apigatewayv2 create-api \
  --name hello-api \
  --protocol-type HTTP \
  --query ApiId --output text)
 
echo $API_ID

El stage no se crea solo por CLI. Hay que pedirlo, y con --auto-deploy para no tener que desplegar a mano cada cambio:

bash
aws apigatewayv2 create-stage \
  --api-id $API_ID \
  --stage-name v1 \
  --auto-deploy

Conectar nuestra Lambda

Ahora en la sección de Routes del API Gateway creamos una ruta, por ejemplo GET /hello.

Creación de ruta en el API Gateway
Creación de ruta en el API Gateway

Una vez creada la ruta debemos conectarla con nuestra Lambda a través de las Integrations. La integración es el recurso de backend al que esa ruta llama cuando recibe una solicitud.

Ruta en un API Gateway
Ruta en un API Gateway

Existen varias opciones de integraciones, en nuestro caso debemos seleccionar la Lambda Function.

Tipos de integraciones en una API Gateway
Tipos de integraciones en una API Gateway

Y ya solo queda seleccionar nuestra Lambda y darle en crear.

Creación de integración en una API Gateway
Creación de integración en una API Gateway

Y listo. Ahora si vamos a nuestra ruta /hello ya nos saldrá el "Hello from Lambda!" que habíamos definido en nuestra Lambda.

La URL del API Gateway la podemos ver en el menú izquierdo donde dice API, o en la parte de Stages seleccionando un stage.

Lo mismo con el AWS CLI

Aquí son tres comandos, y el tercero es el que a todo el mundo se le olvida.

Primero la integración:

bash
INTEGRATION_ID=$(aws apigatewayv2 create-integration \
  --api-id $API_ID \
  --integration-type AWS_PROXY \
  --integration-uri arn:aws:lambda:$REGION:$ACCOUNT_ID:function:hello-lambda \
  --payload-format-version 2.0 \
  --query IntegrationId --output text)

--payload-format-version 2.0 no es opcional en la práctica. Si lo omites, el CLI usa 1.0 y el event que recibe tu Lambda tiene otra forma que la que ves en la consola.

Luego la ruta, apuntando a esa integración:

bash
aws apigatewayv2 create-route \
  --api-id $API_ID \
  --route-key "GET /hello" \
  --target integrations/$INTEGRATION_ID

Y el permiso. La consola lo agrega sola cuando eliges la Lambda en el dropdown; por CLI no. Si te lo saltas, la ruta responde 500 y los logs de la Lambda están vacíos, porque la Lambda nunca se llegó a ejecutar:

bash
aws lambda add-permission \
  --function-name hello-lambda \
  --statement-id apigw-invoke-hello \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT_ID:$API_ID/*/GET/hello"

Para obtener la URL y probar:

bash
ENDPOINT=$(aws apigatewayv2 get-api --api-id $API_ID \
  --query ApiEndpoint --output text)
 
curl $ENDPOINT/v1/hello

El /v1 es el nombre del stage. Si tu stage se llama $default, la URL va sin prefijo.

CORS

El curl funciona. Pero un fetch() desde el navegador, no.

Esto es porque el API Gateway no manda las cabeceras de CORS por defecto, así que el navegador bloquea la respuesta. El error que ves en la consola del navegador no menciona API Gateway, y ahí es donde perdemos tiempo buscando el error.

En la consola está en el menú izquierdo, en la sección CORS de tu API.

CORS en API Gateway
CORS en API Gateway

Lo mismo con el AWS CLI

bash
aws apigatewayv2 update-api --api-id $API_ID \
  --cors-configuration AllowOrigins="*",AllowMethods="GET"

AllowOrigins="*" sirve para probar. En algo real pon tu dominio.

Limpiar todo

Acabas de dejar una URL pública, sin autenticación, que ejecuta tu código. Cualquiera que la tenga puede invocarla. Este post no cubre autenticación, así que lo mejor es borrarlo.

Parado no te cuesta nada, pero borrar lo que ya no usas es una costumbre que te recomiendo.

AWS Alarms Are Stronger Than Coffee. Tomado de programmerhumor.io
AWS Alarms Are Stronger Than Coffee. Tomado de programmerhumor.io

El rol no se borra mientras tenga la policy adjunta.

bash
aws apigatewayv2 delete-api --api-id $API_ID
 
aws lambda delete-function --function-name hello-lambda
 
aws iam detach-role-policy \
  --role-name lambda-hello-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
 
aws iam delete-role --role-name lambda-hello-role

Falta uno. delete-function no borra los logs, y el log group se queda ahí:

bash
aws logs delete-log-group --log-group-name /aws/lambda/hello-lambda

Frequently asked questions

¿Por qué mi Lambda tarda tanto la primera vez y luego va rápida?

Eso es un cold start. En la primera llamada, Lambda tiene que descargar tu código, arrancar el runtime y ejecutar todo lo que está fuera del handler. Después mantiene ese entorno vivo y lo reutiliza, así que las siguientes llamadas se saltan todo eso.

Lo puedes ver en los logs. La línea REPORT muestra Init Duration solo cuando hubo cold start.

¿Cómo instalo dependencias? En la consola no hay npm install

Correcto, no lo hay. En cuanto necesitas un paquete, subes un zip.

Tienes tres opciones:

  • Zip. Instalas en local, comprimes tu código junto con node_modules y lo subes. Empieza por aquí.
  • Layers. Un zip aparte que le adjuntas a la función. Sirve cuando varias funciones comparten las mismas dependencias. Para una sola función no vale la pena.
  • Imagen de container. Una imagen de Docker en ECR. Para dependencias pesadas, como librerías de machine learning.
Mi Lambda hace timeout. ¿Cuál es el límite?

Por defecto son 3 segundos. Ese es el que agarra a todo el mundo. El máximo son 15 minutos.

aws lambda update-function-configuration \
  --function-name hello-lambda \
  --timeout 30

Detrás de un HTTP API el techo real es más bajo. API Gateway corta la conexión a los 30 segundos y devuelve un 504, sin importar lo que diga el timeout de tu Lambda. Un REST API sí puede subir de ahí, pero solo los regionales y privados, y hay que pedirlo por Service Quotas. Los HTTP API no pueden.

Si necesitas más tiempo, no pelees con el límite. Responde 202 de inmediato y haz el trabajo en segundo plano.

¿Cuánta memoria le pongo? ¿Más memoria es más caro?

La memoria también define la CPU. Van juntas y no se configuran por separado. Alrededor de los 1.769 MB tienes una vCPU completa. Por debajo, una fracción.

Así que más memoria no siempre es más caro. Pagas GB-segundos, que es memoria por duración. Si al doblar la memoria la función termina en la mitad del tiempo, la factura es la misma y la respuesta llega antes.

Configuración de la memoria de una función de Lambda

Comments