AWS Builder Center
De localhost a la nube: construye tu primera API Serverless con FastAPI, Docker y AWS Lambda

De localhost a la nube: construye tu primera API Serverless con FastAPI, Docker y AWS Lambda

El objetivo del manual FastAPI + Docker + AWS Lambda es dar a conocer un conjunto de herramientas que permiten a cualquier persona con conocimientos básicos de desarrollo publicar una API en Internet de manera rápida, económica y escalable. Además, el uso de Docker facilita la creación de soluciones agnósticas de la nube, al empaquetar la aplicación junto con sus dependencias en un entorno reproducible. Tiempo estimado: 100 minutos Nivel: Básico–intermedio

Introducción: ¡En mi computadora sí funciona!

Imagina que acabas de desarrollar una API para uno de tus clientes.
Después de varias horas programando, finalmente consigues que todo funcione. Ejecutas tu aplicación, realizas algunas pruebas y obtienes exactamente los resultados que esperabas.
Tu API está disponible en:
localhost:8000
Presentas el resultado a tu cliente, quien parece bastante satisfecho.
Pero entonces llega una pregunta:
—¡Excelente! ¿Cuál es la dirección para que nuestros usuarios puedan comenzar a utilizarla?
Y es en ese momento cuando descubres que desarrollar una aplicación es solamente una parte del trabajo.
Ahora necesitas responder otras preguntas:
  • ¿Dónde vamos a publicar nuestra API?
  • ¿Necesitamos contratar un servidor?
  • ¿Cómo instalamos las dependencias?
  • ¿Qué ocurre si tenemos miles de solicitudes?
  • ¿Quién va a administrar la infraestructura?
Tradicionalmente, una de las soluciones habría sido contratar un servidor, instalar Python, configurar nuestra aplicación y mantener todo funcionando.
Pero ¿qué tal si pudiéramos ejecutar nuestra aplicación en la nube sin tener que administrar directamente un servidor?
Bienvenido al mundo del serverless computing.
En este workshop vamos a construir una API desde cero, empaquetarla utilizando Docker y desplegarla en AWS Lambda.
Al finalizar tendremos una URL pública capaz de recibir solicitudes desde Internet.
Y entenderemos cómo llegamos hasta ahí.

1. ¿Qué vamos a construir?

Nuestro objetivo es desarrollar una sencilla API REST que permita recibir solicitudes HTTP y devolver respuestas JSON.
Por ejemplo, al realizar la siguiente solicitud:
1
GET /hello/Carlos
Obtendremos:
1
2
3
{
"message": "Hello, Carlos!"
}
Además, agregaremos un endpoint POST que recibirá información en formato JSON.
Pero lo interesante no será solamente desarrollar esos endpoints, sino construir la infraestructura necesaria para publicarlos.
Nuestra arquitectura final será:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
USUARIO
|
v
INTERNET
|
v
API GATEWAY
|
v
AWS LAMBDA
|
v
MANGUM
|
v
FASTAPI

Imagen de contenedor:
FastAPI + Mangum + Python
|
v
AMAZON ECR
|
v
AWS LAMBDA
Cada tecnología resolverá un problema específico.

2. ¿Qué herramientas necesitamos?

Python

Nuestro lenguaje de programación principal.

FastAPI

Un framework moderno de Python que permite construir APIs con validación de datos y documentación interactiva automática.

Docker

Nos permitirá empaquetar nuestra aplicación y sus dependencias para ejecutarla dentro de un entorno reproducible.

Mangum

Es un adaptador entre aplicaciones ASGI, como FastAPI, y eventos de AWS Lambda.

Amazon Elastic Container Registry (ECR)

Almacenará la imagen Docker que utilizaremos para desplegar nuestra aplicación.

AWS Lambda

Ejecutará nuestra aplicación bajo un modelo serverless.

Amazon API Gateway

Expondrá nuestra función Lambda mediante una URL HTTP pública.

AWS CLI

Permitirá administrar los recursos de AWS utilizando comandos desde la terminal.

3. Preparación del entorno

Antes de comenzar necesitamos:
  • Python 3.12
  • Docker con el complemento Buildx
  • Una cuenta de AWS
  • AWS CLI versión 2
  • Un editor como Visual Studio Code
  • Conocimientos básicos de Python y terminal
Para este tutorial utilizaremos comandos compatibles con Bash, disponible en Linux, macOS o WSL en Windows.
Primero verificamos que tenemos las herramientas necesarias.
1
2
3
4
python3 --version
docker --version
docker buildx version
aws --version
También debemos configurar el acceso a AWS CLI. Es recomendable utilizar credenciales temporales mediante IAM Identity Center o un perfil autorizado, y nunca utilizar las credenciales de la cuenta raíz.
Verificamos nuestra sesión:
1
aws sts get-caller-identity
Si está configurada correctamente, obtendremos información sobre nuestra identidad de AWS.
Importante: necesitaremos permisos para crear recursos en IAM, ECR, Lambda, API Gateway y CloudWatch Logs.

Parte 1. Construyendo nuestra API con FastAPI

4. Crear el proyecto

Comencemos creando una carpeta para nuestro proyecto.
1
2
3
mkdir fastapi-lambda-workshop

cd fastapi-lambda-workshop
Creamos un entorno virtual:
1
python3 -m venv .venv
Lo activamos:
1
source .venv/bin/activate
En Windows PowerShell podemos utilizar:
1
.venv\Scripts\Activate.ps1
Ahora creamos nuestra carpeta de aplicación:
1
2
3
4
mkdir app
touch app/__init__.py
touch app/main.py
touch requirements.txt
Nuestra estructura inicial será:
1
2
3
4
5
6
7
fastapi-lambda-workshop/
│
├── app/
│ ├── __init__.py
│ └── main.py
│
└── requirements.txt

5. Instalar nuestras dependencias

En el archivo requirements.txt agregamos:
1
2
3
fastapi>=0.115,<1.0
uvicorn>=0.30,<1.0
mangum>=0.19,<1.0
Instalamos las dependencias:
1
pip install -r requirements.txt
¿Qué hace cada una?
FastAPI permite construir nuestra API. Uvicorn es el servidor ASGI que utilizaremos para ejecutarla localmente. Mangum permitirá adaptar posteriormente nuestra aplicación a Lambda.

6. Crear nuestra primera API

Abrimos app/main.py y agregamos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from fastapi import FastAPI
from mangum import Mangum
from pydantic import BaseModel, Field

app = FastAPI(
title="API del workshop FastAPI + AWS Lambda",
description="Ejemplo práctico: de localhost a la nube.",
version="1.0.0"
)

class GreetingRequest(BaseModel):
name: str = Field(min_length=1, max_length=80)

@app.get("/")
def root():
return {
"message": "Hello from FastAPI!"
}

@app.get("/health")
def health():
return {
"status": "ok"
}

@app.get("/hello/{name}")
def hello(name: str):
return {
"message": f"Hello, {name}!"
}

@app.post("/greetings", status_code=201)
def create_greeting(payload: GreetingRequest):
return {
"message": f"Hola, {payload.name}!"
}

handler = Mangum(app, lifespan="off")

¿Qué acabamos de construir?

Nuestra aplicación tiene cuatro endpoints:
MétodoEndpointFunción
GET/Mensaje de bienvenida
GET/healthEstado de la API
GET/hello/{name}Saludo personalizado
POST/greetingsRecibe datos JSON y genera un saludo
También hemos utilizado Pydantic para definir y validar los datos recibidos.
Por ejemplo:
1
2
class GreetingRequest(BaseModel):
name: str =
Field(min_length=1, max_length=80)
Esto significa que nuestro endpoint POST espera un objeto con un campo name que contenga entre 1 y 80 caracteres.
La última instrucción:
1
handler = Mangum(app, lifespan="off")
será especialmente importante cuando llevemos nuestra aplicación a AWS Lambda.
Por ahora podemos dejarla ahí.

7. Ejecutar FastAPI localmente

Ejecutamos:
1
uvicorn app.main:app --reload
Si todo está configurado correctamente, nuestra aplicación estará disponible en:
127.0.0.1:8000
Ahora abrimos:
127.0.0.1:8000/docs
<text><escape> </escape></text>
Aquí descubrimos una de las características más interesantes de FastAPI: la documentación interactiva de nuestra API se genera automáticamente.
Desde esta interfaz podemos explorar y probar los endpoints sin construir una aplicación frontend.

8. Probar los endpoints

Desde una segunda terminal ejecutamos:
1
curl http://127.0.0.1:8000/
Resultado:
1
2
3
{
"message": "Hello from FastAPI!"
}
Ahora:
1
curl http://127.0.0.1:8000/hello/Carlos
Resultado:
1
2
3
{
"message": "Hello, Carlos!"
}
Probemos también nuestro endpoint POST:
1
2
3
4
curl -X POST \
http://127.0.0.1:8000/greetings \
-H "Content-Type: application/json" \
-d '{"name":"Ana"}'
Resultado esperado:
1
2
3
{
"message": "Hola, Ana!"
}
¡Excelente!
Nuestra API está funcionando.
Pero seguimos teniendo el mismo problema del principio.
Solo podemos acceder a ella desde nuestra computadora.
Es momento de dar el siguiente paso.

Parte 2. Dockerizando nuestra aplicación

9. ¿Por qué necesitamos Docker?

Imagina que queremos ejecutar nuestra aplicación en una computadora diferente.
Necesitaríamos instalar Python, las librerías correspondientes y preparar el entorno.
Además, ¿qué sucede si esa computadora tiene una versión distinta de Python?
Podríamos encontrarnos con el famoso problema:
“Pero en mi computadora sí funciona...”
Docker nos ayuda a reducir estos problemas al empaquetar la aplicación y sus dependencias en una imagen.
Esa imagen podrá ejecutarse en cualquier entorno compatible con el contenedor.

10. Crear nuestro primer Dockerfile

En la raíz del proyecto creamos:
Dockerfile.local
Agregamos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app

EXPOSE 8000

CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]
Nota: la instrucción CMD anterior puede escribirse en una sola línea si lo prefiere tu editor de Dockerfiles:
1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

¿Qué significa cada instrucción?

FROM: define la imagen base que utilizaremos.
WORKDIR: establece nuestro directorio de trabajo.
COPY: copia archivos de nuestro proyecto hacia la imagen.
RUN: ejecuta comandos durante la construcción de la imagen.
EXPOSE: documenta el puerto utilizado por nuestra aplicación.
CMD: indica qué comando se ejecutará al iniciar el contenedor.
También crearemos .dockerignore:
1
2
3
4
5
6
7
8
.git
.venv
venv
__pycache__
*.pyc
*.pyo
.env
.pytest_cache
Esto evita incluir archivos innecesarios o sensibles dentro del contexto de construcción.

11. Construir nuestra imagen

Ejecutamos:
1
2
3
docker build \
-f Dockerfile.local \
-t fastapi-workshop:local .
Docker leerá nuestro Dockerfile, descargará la imagen base cuando sea necesario, instalará las dependencias y preparará nuestra aplicación.
Al finalizar tendremos una imagen llamada:
fastapi-workshop:local
Podemos verificarla:
1
docker images

12. Ejecutar nuestra API dentro de Docker

Primero detenemos Uvicorn si continúa ejecutándose, para liberar el puerto 8000.
Después:
1
2
3
docker run --rm \
-p 8000:8000 \
fastapi-workshop:local
El parámetro -p 8000:8000 conecta el puerto 8000 de nuestra computadora con el puerto 8000 del contenedor.
Ahora abrimos nuevamente:
localhost:8000/docs
¡Nuestra API continúa funcionando!
Pero esta vez está ejecutándose dentro de un contenedor Docker.
Hemos conseguido que nuestra aplicación tenga un entorno de ejecución reproducible.
Sin embargo, Docker por sí solo no publica nuestra aplicación en Internet.
Necesitamos desplegarla en algún lugar.

Parte 3. Preparar FastAPI para AWS Lambda

13. ¿Qué es AWS Lambda?

AWS Lambda es un servicio de cómputo serverless que permite ejecutar código en respuesta a eventos.
A diferencia de un servidor tradicional, no necesitamos administrar directamente una máquina virtual para mantener nuestra función funcionando.
En nuestro caso, las solicitudes llegarán mediante API Gateway y se convertirán en eventos que serán enviados a Lambda.
Pero tenemos un pequeño desafío.
FastAPI es una aplicación ASGI, mientras que Lambda ejecuta funciones a través de un mecanismo de invocación basado en eventos.
¿Cómo conectamos estos dos modelos?

14. La función de Mangum

Aquí entra Mangum.
Mangum funciona como un adaptador que interpreta los eventos de Lambda y permite que nuestra aplicación FastAPI los procese como solicitudes ASGI.
El flujo será:
1
2
3
4
5
6
7
8
9
10
11
12
13
API Gateway
|
v
AWS Lambda
|
v
Mangum
|
v
FastAPI
|
v
Respuesta JSON
Ya agregamos Mangum en nuestro código:
1
handler = Mangum(app, lifespan="off")
Este objeto será el punto de entrada de nuestra función Lambda.
La opción lifespan="off" desactiva los eventos de inicio y cierre de ASGI, algo adecuado para nuestro ejemplo, que no necesita inicialización especial.

15. Crear el Dockerfile para Lambda

Ahora necesitamos construir una imagen compatible con el entorno de AWS Lambda.
No utilizaremos exactamente la misma imagen Docker con la que ejecutamos Uvicorn.
AWS proporciona imágenes base específicas para Lambda que incluyen componentes necesarios para comunicarse con su runtime.
Crearemos un archivo:
Dockerfile.lambda
Con el siguiente contenido:
1
2
3
4
5
6
7
8
9
10
11
FROM public.ecr.aws/lambda/python:3.12

COPY requirements.txt ${LAMBDA_TASK_ROOT}/

RUN pip install --no-cache-dir \
-r ${LAMBDA_TASK_ROOT}/requirements.txt \
--target ${LAMBDA_TASK_ROOT}


COPY app ${LAMBDA_TASK_ROOT}/app

CMD ["app.main.handler"]

¿Qué cambió?

Ahora utilizamos:
1
FROM public.ecr.aws/lambda/python:3.12
Esta imagen está preparada para el runtime de Python en AWS Lambda.
También cambiamos nuestro comando final.
Antes ejecutábamos Uvicorn:
1
uvicorn app.main:app
Ahora indicamos:
1
CMD ["app.main.handler"]
Esto significa que Lambda buscará el objeto handler dentro del módulo app.main.
Recuerda que este objeto lo creamos utilizando Mangum.

Parte 4. Construir y probar nuestra imagen de Lambda

16. Construir la imagen

Ejecutamos:
1
2
3
4
5
6
docker buildx build \
--platform linux/amd64 \
--provenance=false \
--load \
-f Dockerfile.lambda \
-t fastapi-workshop:lambda .
Aquí estamos utilizando algunos parámetros importantes:
--platform linux/amd64: construye para la arquitectura x86-64 que utilizaremos en Lambda.
--provenance=false: evita generar metadatos de procedencia que pueden producir formatos de imagen incompatibles con Lambda.
--load: carga la imagen construida en nuestro Docker local.

17. Probar Lambda localmente

Una ventaja de las imágenes base de AWS Lambda es que incluyen un emulador de su interfaz de ejecución.
Esto nos permite realizar pruebas antes de desplegar nuestra aplicación en AWS.
Ejecutamos:
1
2
3
4
docker run --rm \
--platform linux/amd64 \
-p 9000:8080 \
fastapi-workshop:lambda
Ahora nuestra imagen está escuchando invocaciones simuladas de Lambda a través del puerto 9000.
Pero hay una diferencia importante:
No podemos realizar una solicitud GET directamente al puerto 9000 como si fuera Uvicorn.
Tenemos que enviar un evento con una estructura similar a la utilizada por API Gateway.

18. Crear un evento de prueba

En la raíz del proyecto creamos:
event.json
Agregamos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"version": "2.0",
"routeKey": "$default",
"rawPath": "/hello/Carlos",
"rawQueryString": "",
"headers": {
"accept": "*/*",
"host": "localhost"
},
"requestContext": {
"accountId": "123456789012",
"apiId": "local-demo",
"domainName": "localhost",
"http": {
"method": "GET",
"path": "/hello/Carlos",
"protocol": "HTTP/1.1",
"sourceIp": "127.0.0.1",
"userAgent": "curl"
},
"requestId": "local-request",
"routeKey": "$default",
"stage": "$default",
"timeEpoch": 0
},
"isBase64Encoded": false
}
Este archivo simula el evento que recibiría nuestra función desde una API HTTP de API Gateway.

19. Invocar nuestra función localmente

Con el contenedor en ejecución, abrimos otra terminal y ejecutamos desde la carpeta del proyecto:
1
2
3
4
curl -X POST \
http://localhost:9000/2015-03-31/functions/function/invocations \
-H "Content-Type: application/json" \
--data-binary @event.json
Deberíamos obtener una respuesta de Lambda similar a:
1
2
3
4
5
6
7
8
{
"statusCode": 200,
"headers": {
"content-type": "application/json"
},
"body": "{\"message\":\"Hello, Carlos!\"}",
"isBase64Encoded": false
}
El contenido exacto de los encabezados puede variar.
Lo importante es observar que la función procesa nuestro evento y devuelve una respuesta HTTP válida.
¡Nuestra aplicación ya está preparada para ejecutarse en Lambda!
Solo falta publicarla.

Parte 5. Desplegar nuestra aplicación en AWS

20. Configurar nuestras variables

Para simplificar los siguientes comandos utilizaremos algunas variables de entorno en Bash.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export REGION=us-east-1

export PROJECT=fastapi-workshop

export ACCOUNT_ID=$(
aws sts get-caller-identity \
--query Account \
--output text
)

export REPOSITORY_URI="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${PROJECT}"

export IMAGE_URI="${REPOSITORY_URI}:latest"

export ROLE_NAME="${PROJECT}-role"

export FUNCTION_ARN="arn:aws:lambda:${REGION}:${ACCOUNT_ID}:function:${PROJECT}"
Utilizaremos la región us-east-1 como ejemplo.
Puedes modificarla por otra región de AWS compatible con los servicios utilizados.
El repositorio ECR y la función Lambda deberán encontrarse en la misma región.

21. Crear un repositorio en Amazon ECR

Antes de crear nuestra función necesitamos almacenar la imagen Docker en Amazon Elastic Container Registry.
Ejecutamos:
1
2
3
aws ecr create-repository \
--repository-name "$PROJECT" \
--region "$REGION"
Esto creará un repositorio privado llamado:
fastapi-workshop

22. Autenticar Docker con Amazon ECR

Ejecutamos:
1
2
3
4
5
aws ecr get-login-password \
--region "$REGION" \
| docker login \
--username AWS \
--password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"
Con esto Docker podrá enviar imágenes a nuestro registro de ECR.

23. Etiquetar y subir nuestra imagen

Primero asignamos a nuestra imagen local la dirección del repositorio:
1
2
3
docker tag \
fastapi-workshop:lambda \
"$IMAGE_URI"
Después la publicamos:
1
docker push "$IMAGE_URI"
Al finalizar, nuestra imagen estará disponible en Amazon ECR.
Podemos comprobarlo utilizando:
1
2
3
aws ecr describe-images \
--repository-name "$PROJECT" \
--region "$REGION"
Ahora tenemos nuestra aplicación empaquetada y almacenada en AWS.
Pero todavía no se está ejecutando.

24. Crear un rol IAM para nuestra función

AWS Lambda necesita un rol de ejecución con permisos para realizar determinadas operaciones.
En nuestro ejemplo, solamente necesita permisos básicos para escribir registros en CloudWatch Logs.
Crearemos:
iam-trust-policy.json
Con este contenido:
1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Este documento indica que el servicio Lambda puede asumir el rol.
Ahora creamos el rol:
1
2
3
aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document file://iam-trust-policy.json
Asignamos la política administrada que permite escribir registros:
1
2
3
aws iam attach-role-policy \
--role-name "$ROLE_NAME" \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Para este ejercicio no es necesario otorgar a nuestra función permisos generales de administración de AWS.

25. Crear nuestra función Lambda

Finalmente vamos a crear la función utilizando nuestra imagen Docker.
1
2
3
4
5
6
7
8
9
aws lambda create-function \
--function-name "$PROJECT" \
--package-type Image \
--code "ImageUri=${IMAGE_URI}" \
--role "arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}" \
--architectures x86_64 \
--memory-size 256 \
--timeout 15 \
--region "$REGION"
Aquí estamos indicando:
  • El nombre de nuestra función.
  • Que utilizaremos una imagen de contenedor.
  • La ubicación de esa imagen en ECR.
  • El rol de ejecución.
  • La arquitectura del procesador.
  • La memoria y el tiempo máximo de ejecución.
AWS comenzará a preparar nuestra función.
Esperamos hasta que esté activa:
1
2
3
aws lambda wait function-active-v2 \
--function-name "$PROJECT" \
--region "$REGION"
Nota: la creación de roles IAM tiene propagación eventual. Si recibes un error indicando que Lambda no puede asumir el rol recién creado, comprueba la configuración del rol y vuelve a intentar la creación cuando el cambio se haya propagado.
¡Nuestra aplicación ya está desplegada en Lambda!
Pero necesitamos resolver el último problema.
¿Cómo recibirá solicitudes desde Internet?

Parte 6. Publicar nuestra API con API Gateway

26. ¿Para qué necesitamos API Gateway?

Nuestra función Lambda puede ejecutarse cuando recibe un evento.
Sin embargo, queremos ofrecer una API HTTP pública para que cualquier cliente autorizado pueda realizar solicitudes utilizando una URL.
API Gateway cumplirá esa función.
El recorrido será:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Cliente HTTP
|
v
API Gateway
|
v
AWS Lambda
|
v
Mangum
|
v
FastAPI
|
v
Respuesta HTTP
API Gateway recibirá una solicitud HTTP y la enviará a nuestra función Lambda.
Mangum convertirá el evento en una solicitud compatible con FastAPI y convertirá la respuesta para que API Gateway pueda devolverla al cliente.

27. Crear una API HTTP

Utilizaremos la opción de creación rápida de API Gateway.
1
2
3
4
5
6
7
8
9
10
11
API_ID=$(
aws apigatewayv2 create-api \
--name "${PROJECT}-http" \
--protocol-type HTTP \
--target "$FUNCTION_ARN" \
--region "$REGION" \
--query ApiId \
--output text
)

echo "$API_ID"
Este comando crea una API HTTP con integración Lambda, una ruta predeterminada $default y un stage que publica automáticamente los cambios.
La ruta $default permitirá dirigir a Lambda las solicitudes de nuestros diferentes endpoints sin configurar cada ruta por separado.

28. Autorizar a API Gateway para invocar Lambda

Aunque nuestra API ya existe, necesitamos otorgar permiso para que API Gateway invoque nuestra función.
Ejecutamos:
1
2
3
4
5
6
7
aws lambda add-permission \
--function-name "$PROJECT" \
--statement-id "allow-${PROJECT}-api-gateway" \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:${REGION}:${ACCOUNT_ID}:${API_ID}/*/*" \
--region "$REGION"
Este permiso está limitado al identificador de nuestra API Gateway.
No estamos concediendo permisos indiscriminados a cualquier API Gateway.

29. Obtener nuestra URL pública

Consultamos la dirección de la API:
1
2
3
4
5
6
7
8
9
API_URL=$(
aws apigatewayv2 get-api \
--api-id "$API_ID" \
--region "$REGION" \
--query ApiEndpoint \
--output text
)

echo "$API_URL"
Obtendremos una dirección similar a:
1
https://abc123xyz.execute-api.us-east-1.amazonaws.com
¡Lo conseguimos!
Ya tenemos una URL pública.

30. Realizar nuestra primera solicitud desde Internet

Vamos a probar el endpoint de bienvenida.
1
curl "${API_URL}/"
Resultado esperado:
1
2
3
{
"message": "Hello from FastAPI!"
}
Ahora nuestro endpoint personalizado:
1
curl "${API_URL}/hello/Carlos"
Resultado:
1
2
3
{
"message": "Hello, Carlos!"
}
Probemos también nuestro POST:
1
2
3
4
curl -X POST \
"${API_URL}/greetings" \
-H "Content-Type: application/json" \
-d '{"name":"Ana"}'
Resultado:
1
2
3
{
"message": "Hola, Ana!"
}
Finalmente podemos abrir la documentación interactiva:
1
h t t p s ://abc123xyz.execute-api.us-east-1.amazonaws.com/docs
Sustituye la dirección de ejemplo por el valor real de API_URL.
¡Nuestra API ahora funciona desde Internet utilizando una arquitectura serverless!

Parte 7. Actualizar nuestra aplicación

31. ¿Qué ocurre cuando modificamos nuestro código?

Imaginemos que nuestro cliente nos pide realizar una modificación.
Actualizamos nuestra aplicación, pero ahora necesitamos desplegar la nueva versión.
Como estamos utilizando imágenes de contenedor, el procedimiento es sencillo.
Primero construimos una nueva imagen:
1
2
3
4
5
6
docker buildx build \
--platform linux/amd64 \
--provenance=false \
--load \
-f Dockerfile.lambda \
-t fastapi-workshop:lambda .
La etiquetamos:
1
2
3
docker tag \
fastapi-workshop:lambda \
"$IMAGE_URI"
La publicamos:
1
docker push "$IMAGE_URI"
Y actualizamos Lambda:
1
2
3
4
aws lambda update-function-code \
--function-name "$PROJECT" \
--image-uri "$IMAGE_URI" \
--region "$REGION"
Esperamos a que la actualización termine:
1
2
3
aws lambda wait function-updated-v2 \
--function-name "$PROJECT" \
--region "$REGION"
Es importante comprender que AWS Lambda no actualiza automáticamente el código cuando subimos una imagen nueva con la misma etiqueta.
Debemos solicitar explícitamente la actualización de la función.
En proyectos reales también conviene utilizar etiquetas de versión únicas o identificadores de imagen inmutables para controlar los despliegues.

Parte 8. Monitoreo y solución de problemas

32. Consultar los registros de ejecución

En aplicaciones reales no siempre todo funciona a la primera.
Podemos encontrarnos con errores en nuestro código, dependencias incorrectas o problemas de permisos.
AWS Lambda integra su registro de ejecución con CloudWatch Logs.
Podemos revisar los registros desde la consola o utilizando AWS CLI:
1
2
3
4
5
aws logs tail \
"/aws/lambda/${PROJECT}" \
--since 10m \
--follow \
--region "$REGION"
Algunos problemas comunes son:
ErrorPosible causa
502 Bad GatewayError o respuesta inválida en la integración
403 ForbiddenPermisos o autorización incorrectos
404 Not FoundRuta inexistente o mal configurada
Runtime.ImportModuleErrorProblema de dependencias o importación
Task timed outLa función superó su tiempo máximo
Los registros de Lambda y las métricas de API Gateway nos ayudarán a identificar la causa.

Parte 9. Presupuesto y buenas prácticas

33. ¿Cuánto cuesta ejecutar nuestra API?

Una de las ventajas de esta arquitectura es que no tenemos que contratar y mantener un servidor tradicional encendido permanentemente.
Sin embargo, serverless no significa que todo sea gratuito.
Los principales componentes que pueden generar costos son:
Servicio¿Qué puede generar costos?
FastAPIFramework gratuito
Docker EngineUso gratuito en entornos elegibles; otras herramientas o licencias pueden tener condiciones
Amazon ECRAlmacenamiento de imágenes y transferencia
AWS LambdaInvocaciones y recursos consumidos
API GatewaySolicitudes y transferencia
CloudWatchAlmacenamiento e ingesta de registros
AWS ofrece cuotas gratuitas y, según la fecha de creación y el tipo de cuenta, posibles créditos promocionales.
Para realizar este workshop con pocas solicitudes, el costo puede ser muy bajo, pero no debemos asumir que será necesariamente cero.
Antes de comenzar, recomendamos crear un presupuesto con alertas mediante AWS Budgets.

34. Consideraciones de seguridad

Nuestra API fue publicada sin autenticación para facilitar el aprendizaje.
Esto es adecuado únicamente para una demostración controlada.
Para una aplicación de producción debemos considerar:
  • Autenticación y autorización.
  • Restricciones de acceso y límites de solicitudes.
  • Principio de mínimo privilegio en IAM.
  • Manejo seguro de secretos y variables de entorno.
  • Validación de entradas.
  • Actualización de dependencias e imágenes.
  • Monitoreo, alertas y control de costos.
También debemos evaluar los tiempos de arranque de Lambda, especialmente cuando una función no ha sido utilizada recientemente.
Una arquitectura serverless es una herramienta poderosa, pero requiere tomar buenas decisiones de diseño.

Parte 10. Limpiar los recursos de AWS

35. Eliminar los recursos del workshop

Una buena práctica al terminar nuestros ejercicios es eliminar los recursos que ya no necesitamos.
Atención: los siguientes comandos eliminan recursos de AWS de manera permanente. Ejecútalos únicamente si estás seguro de que corresponden a tu workshop y no necesitas conservarlos.
Primero eliminamos API Gateway:
1
2
3
aws apigatewayv2 delete-api \
--api-id "$API_ID" \
--region "$REGION"
Eliminamos nuestra función Lambda:
1
2
3
aws lambda delete-function \
--function-name "$PROJECT" \
--region "$REGION"
Eliminamos el repositorio ECR y sus imágenes:
1
2
3
4
aws ecr delete-repository \
--repository-name "$PROJECT" \
--force \
--region "$REGION"
Desvinculamos la política del rol:
1
2
3
aws iam detach-role-policy \
--role-name "$ROLE_NAME" \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Finalmente eliminamos el rol:
1
2
aws iam delete-role \
--role-name "$ROLE_NAME"
Los grupos de CloudWatch Logs pueden permanecer después de eliminar una función.
Si ya no necesitas los registros del ejercicio, también puedes eliminar el grupo correspondiente:
1
2
3
aws logs delete-log-group \
--log-group-name "/aws/lambda/${PROJECT}" \
--region "$REGION"
Esto elimina permanentemente los registros de ese grupo.

Conclusión: de una idea a una API disponible en Internet

Regresemos al inicio de nuestra historia.
Teníamos una aplicación funcionando en nuestra computadora.
Nuestro cliente nos había preguntado:
“¿Cuál es la dirección para que nuestros usuarios puedan comenzar a utilizarla?”
En ese momento solamente teníamos:
localhost:8000
Hoy, después de recorrer todo el proceso, tenemos una API desplegada utilizando servicios administrados de AWS.
Aprendimos a desarrollar endpoints con FastAPI, empaquetar nuestra aplicación con Docker, adaptarla utilizando Mangum, almacenar imágenes en Amazon ECR y ejecutar nuestra API en AWS Lambda.
Finalmente utilizamos API Gateway para hacerla accesible desde Internet.
Pero el verdadero aprendizaje fue comprender cómo se conecta todo el proceso de desarrollo y despliegue.
Ahora que conocemos este camino, podemos utilizarlo como base para proyectos más interesantes:
  • Construir APIs para aplicaciones móviles.
  • Crear microservicios empresariales.
  • Desarrollar servicios para agentes de inteligencia artificial.
  • Exponer modelos de machine learning.
  • Integrar sistemas y automatizar procesos.
  • Construir aplicaciones SaaS y arquitecturas basadas en eventos.
Porque desarrollar una aplicación no termina cuando conseguimos que funcione en nuestra computadora.
En muchos casos, ahí comienza el siguiente reto.
De localhost a la nube: un endpoint a la vez.

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article