Manejo de dependencias python en AWS lambda con layers

Imaginemos lo siguiente: estas armando un proyecto desde cero y te estas apoyando 100% en las herramientas de amazon: para exponer tus servicios a internet usas Api Gateway y para autorizar a los usuarios estas usando un custom authorizer, (en lambda, serverless FTW), etc. Bueno eso es lo que yo estaba probando, una POC, para entender cómo se mueven las cosas y divertirme un rato.

Los detalles del código no son muy importante, arme una serie de clases tratando de respetar SOLID y de dejar algo bien testeable, hasta hice algo de CI con los pipelines de Bibucket. Fuera del código escrito por mi toda la solucion depende de PyJWT, una biblioteca que nos permite interactuar con JWT.

Ejemplo de proyecto para función lambda.

En mi local y en los pipelines es muy sencillo, pip install -r requirements.txt(En un virtualenv porfavor, python con virtualenv siempre, a menos que usen docker) y se instala la única dependencia que defini allí (PyJWT). Con eso ya puedo probar y hacer todo lo que necesito. Pero en lambda no se puede correr pip install. Entonces la dependencia no existe y los imports fallan.

Para solucionarlo explore algunas opciones. Varios intentos después estas son las 2 que me funcionaron.

Instalar las dependencias en el root del proyecto y subir un zip

La primer opción que tenemos para que lambda encuentre las dependencias de python es subirlas explicitamente en nuestro package de deployment. Es sucio, pero funciona. Asi, por ejemplo podemos, estando en el root de nuestra función lambda:

pip install -r requirements.txt -t .

Con esto nos quedarán las dependencias instaladas “al lado” de nuestro código. Armamos un zip con todo, lo subimos y sale andando, se encuentran las dependencias y todos felices. Pero es una solución sucia y poco escalable.

El primer problema que tiene esto es que es muy probable que nuestro paquete pese demasiado como para ver el código en el editor online que tiene lambda. El segundo es que estaríamos mezclando el código que mantenemos con las dependencias y generando una estructura de carpetas propensa a errores.

Instalar las dependencias python en un layer/capa de AWS lambda

Lambda nos permite mantener layers. Un layer es una porcion de codigo que mantenemos por fuera de nuestra función lambda. Ese layer puede ser reutilizado por diferentes lambdas. Alli podemos subir lo que queramos, codigo propio, configuraciones, dependencias.

Acceso a los layers en AWS Lambda

Un punto importante es que nosotros creamos un layer y cuando lo subimos tenemos que indicar la compatibilidad con las funciones lambda. En mi caso, por ejemplo, tuve que indicar que mi layer es compatible con Python 3.7, que es el lenguaje base que utilice para desarrollar la función lambda.

Tuve que intentar varias veces hasta que logre que funcione c0mo espero. Lo primero que hice fue agarrar la carpeta completa de la dependencia, zipearla y crear ese layer. Luego en la función lambda lo agregue… obviamente no funciono.

Agregar un layer a una función lambda

Luego de un par de pruebas detecte que era por la estructura de carpetas. Si queremos que python encuentre las dependencia instaladas en un layer de aws lambda tenemos que respetar la estructura que sigue pip al instalarlas en un virtualenv. Esto es específico para cada versión de python, igualmente al crear el layer nos pide que indiquemos con que versiones somos compatibles, asi que no hay problema.

Entonces… para que nuestra función lambda encuentre las dependencias python que tenemos en un layer necesitamos que estas dependencias esten en una carpeta /python/lib/PythonVersion/site-packages/ en mi caso fue python/lib/python3.7/site-packages/ quedando algo tan sencillo como correr:

pip install -r requirements.txt -t python/lib/python3.7/site-packages/

zip -r dependencies.zip python

La primer linea instala todas las dependencias en la estructura de carpetas definida: python/lib/python3.7/site-packages/, empezando desde el directorio actual. La segunda línea lo mete todo en un zip.

Luego hay que agarrar el zip y subirlo al layer.

Al final… en nuestra función lambda apuntamos a la versión específica del layer y magicamente las dependencias funcionan. Esto nos deja un paquete deployable mucho más pequeño y legible ya que solo estamos subiendo nuestra lógica y no todo lo extra que nos permite ejecutarla.

La imágenes del artículo las saque de aca: https://aws.amazon.com/es/blogs/aws/new-for-aws-lambda-use-any-programming-language-and-share-common-components/ ademas es un buen punto de partida para profundizar sobre lo layers de aws lambda.