Hola Mundo con AWS Lambda y Docker

Llevo utilizando AWS Lambda desde hace tiempo para procesos internos. Apagar bases de datos de pruebas fuera de horas de trabajo, un par de bots para Slack…

Una de las formas de lanzar una función Lamda es crear un zip con todas sus dependencias y subirlo a S3. Para algunas aplicaciones en Node.js algo más avanzadas he usado el Serverless Framework.

Uno de mis compañeros está trabajando en un proyecto para el que podríamos usar Lambdas perfectamente. Charlando con él, me comentó que había visto que en la documentación de AWS decía que ahora es posible lanzar Lambas como imágenes de contenedor. Me interesó mucho e hice un pequeño experimento del estilo Hola Mundo. Los resultados están en este post.

Escribiendo tu imagen de Docker

AWS proporciona imagenes base para todos los runtimes soportados por Lambda (Python, Node.js, Java, .NET, Go y Ruby). Esto hace que añadir nuestro código y dependencias sea muy fácil.

También proporcionan imagenes basadas en Amazon Linux donde podemos construir nuestro propio runtime personalizado. Si queremos, también podemos hacerlo con imágenes basadas en Alpine o Debian. Para ello necesitamos implementar la Lambda Runtime API. Sin embargo, este artículo no va de eso, lo único que quería era probar una simple aplicación en Python.

El código

La idea es muy simple. Mandar una petición POST con tu nombre a la aplicación. Esta elegirá un idioma al azar y te saludará. Lo que quería hacer es demostrar lo simple que es añadir nuestro código y culquier otro fichero o dependencias que necesite para funcionarl. Igual que haríamos normalmente con Docker.

El código en Python de mi aplicación es el siguiente.

lambda_function.py

import random
import yaml


def lambda_handler(event, context):

    with open('hello.yml', 'r') as file:
        translations = yaml.load(file, Loader=yaml.FullLoader)

    hello = translations[random.randint(0, len(translations) - 1)]

    message = f"{hello.get('translation')} {event.get('name', 'Foo Bar')}! " \
              f"Now you know how to say 'Hello' in {hello.get('language')}"

    print(message)

    return {
        'statusCode': 200,
        'body': message,
    }

Y el archivo que contiene las diferentes traducciones de «Hola»

hello.yml

- language: English
  translation: Hello
- language: Finnish
  translation: Hei
- language: Galician
  translation: Ola
- language: Portuguese
  translation: Olá
- language: Spanish
  translation: Hola

Como podéis ver, estoy usando YAML así que necesito una librería externa para ello. Mi requriments.txt es muy simple.

PyYAML==5.4.1

Estructura del proyecto

Antes de empezar a escribir nuestro Dockerfile, esta es la estructura de mi proyecto Hello World.

lambda-docker-example
├── Dockerfile
├── README.md
└── sample
    ├── hello.yml
    ├── lambda_function.py
    └── requirements.txt

Dockerfile

Empecemos con el Dockerfile. Cuando escribí este post, AWS proporcionaba las siguientes imágenes como base:

TagsRuntimeSistema Operativo
3, 3.8Python 3.8Amazon Linux 2
3.7Python 3.7Amazon Linux 2018.03
3.6Python 3.6Amazon Linux 2018.03
2, 2.7Python 2.7Amazon Linux 2018.03

Puedes conseguirlas tanto de Docker Hub como de ECR. Yo usé ECR y mi Dockerfile es el siguiente.

FROM public.ecr.aws/lambda/python:3.8

COPY sample/* /var/task/

RUN pip install -r /var/task/requirements.txt


CMD ["lambda_function.lambda_handler"]
  1. Mi imagen se basa en la imagen AWS Python 3.8.
  2. Copio mi código en Python, el fichero YAML con las traducciones y el fichero de dependencias a /var/task/, el directorio ejecución por defecto de Lambda.
  3. Installo las dependencias con pip.
  4. Le digo a LAmbda cual es la función handler. Cuando invocamos la función Lambda, ejecuta la función handler.

Crear la imagen y probarla localmente

Para crear la imagen vamos a darle un nombre muy simple. Recordad que tendremos que subirla a ECR más adelante, así que tendremos que renombrarla correctamente, pero por ahora hagamos esto.

Ejecutamos este comando en el directorio donde está nuestro Dockerfile.

docker build -t lambda-test .

Y ahora vamos a lanzar el contenedor. Necesitamos poder acceder a uno de los puertos para poder hacer cURL a nuestra aplicación. Por defecto, las Lambdas escuchan en el puerto 8080 y yo lo voy a mapear al puerto 9000 en mi máquina.

 docker run --rm=true -p 9000:8080 lambda-test

Ahora puedes ver los logs de la función Lambda. Abre otro terminal y ejectuta este comando.

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"name": "David"}'

y debería ver algo parecido a esto como respuesta (en el idioma que salga aleatoriamente):

{
  "statusCode": 200,
  "body": "Hola David! Now you know how to say 'Hello' in Spanish"
}

y mirando los logs:

START RequestId: 5aa06e1e-a8a6-4bec-9abe-ef44d63e6d8a Version: $LATEST
Hola David! Now you know how to say 'Hello' in Spanish
END RequestId: 5aa06e1e-a8a6-4bec-9abe-ef44d63e6d8a
REPORT RequestId: 5aa06e1e-a8a6-4bec-9abe-ef44d63e6d8a  Init Duration: 0.24 ms  Duration: 65.57 ms      Billed Duration: 100 ms Memory Size: 3008 MB    Max Memory Used: 3008 MB      

Como puedes ver, son como los logs que puedes ver en AWS cuando se ejecuta una función Lambda.

Repositorio ECR

Para poder lanzar la función Lambda que acabamos de crear necesitamos subir nuestra imagen a Amazon ECR. Es importante recordar que la función Lambda y el repositorio deben de estar en la misma cuenta y región.

Configurando el repositorio

En mi caso fui a ECR y creé un nuevo repositorio privado devops/lambda-test.

Etiquetar/taguear y subir la imagen

Vamos a renombrar y taguear nuestra imagen correctamente para que valga con el repositorio que acabamos de crear.

docker tag lambda-test:latest tu-cuenta.dkr.ecr.tu-region.amazonaws.com/devops/lambda-test:latest

Antes de poder subir la imagen al repositorio tienes que loguearte. En este paso asumo que tienes permisos para hacerlo.

aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin tu-cuenta.dkr.ecr.tu-region.amazonaws.com 

Y la subimos con este comando

docker push tu-cuenta.dkr.ecr.tu-region.amazonaws.com/devops/lambda-test:latest

Ejecutar la aplicación en AWS

Crear la función Lambda

Ahora ya podemos crear nuestra función Lambda. Para ello vamos a la sección correspondiente en la consola de AWS.

Nos vamos a Crear función y seleccionamos Imagen del contenedor.

Ponle el nombre que quieras (por ejemplo, docker-hello-world)

Y ahora selecciona la imagen que quieres usar. Puedes utilizar la URI directamente o navegar por ECR para econtrarla.

En este punto también podríamos sobreescribir el entrypoint, cmd o workdir que vienen en la imagen. No es necesario para nuestro ejemplo.

Tampoco es necesario cambiar los permisos. Dejando la opción por defecto AWS creará un nuevo rol como permisos de ejecución Lambda básicos. Entre ellos está la capacacidad de escribir logs en CloudWatch.

Le damos a Crear función abajo a la derecha. Después de esperar un rato nuestra función estará lista. Y veremos un pop-up parecido a este.

Probando en AWS

Vamos a probar nuestra Lambda. Yo he creado un test event con el nombre HelloDave. Contiene un documento JSON con un nombre, exactamente lo que la aplicación espera recibir.

Le damos a guardar y después a Invocar. La función se empezará a ejecutar.

Si todo ha ido bien, deberías ver un mesaje informándote de nque la ejecución ha finalizado con éxito. Al acceder a esa sección podrás ver la respuesta y los logs. Hay también un link a CloudWatch para que puedas ver los logs allí también.

Conclusión

Después de un primer vistazo me gusta esta opción. Por el moment me gusta mucho más que hacer un zip y subirlo a AWS.

Este sistema nos permite crear nuestras propias imagenes en Docker, adaptadas a lo que necesitamos, y ya que es Docker deberíamos poder añadirlo a nuestros procesos de integración continua sin muchos problemas.

También me gusta la posibilidad de ejecutarlo en mi máquina local fácilmente. Python tiene un paquete fantástico para ejecutar AWS Lambdas localmente (échale un ojo a python-lambda-local), pero prefiero poder probar la función en el contenedor ya que ahí es exactamente donde se ejecutará en AWS.

¡Hasta otra vez!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *