Dependencymanagement für AWS Lambda

Jeder Entwickler*in nutzt Bibliotheken, welche Funktionalität bieten und gleichzeitig Entwicklungsaufwand minimieren. Bei der “herkömmlichen” Entwicklung von Software ist das Einbinden zusätzlicher Softwarepakete meist kein Probem (Sicherheitsbedingte Netzwerkeinschränkungen außer acht gelassen). Im Fall von Softwareentwicklung für den Serverless Compute Service Lambda von AWS können hier jedoch Probleme auftreten. Dieser Beitrag beleuchtet das Problem und diskutiert mögliche Lösungswege am Beispiel einer Lambda-Funktion mit Python 3.6 Runtime.

Das Problem

AWS Lambda unterstützt mehrere Sprachen durch die Verwendung von Laufzeiten. Man wählt eine Laufzeit, wenn eine Funktion angelegt wird und kann diese bei Bedarf ändern. Die zugrunde liegende Ausführungsumgebung bietet zusätzliche Bibliotheken und Umgebungsvariablen, auf die der Funktionscode zugreifen kann.

Wählt man bspw. Python3.6 als Runtime, wird das AMI “Amazon Linux“ mit Linux Kernel “4.14.171-105.231.amzn1.x86_64” verwendet (siehe auch https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). Sollte man im Code jedoch Bibliotheken benötigen, die nicht im AMI mitgeliefert werden, müssen diese der Lambda-Funktion zur Verfügung gestellt werden, da sich die Bibliotheken nicht wie gewohnt importieren lassen.

Falls die bekannte pandas-Bibliothek genutzt werden soll, wird die Lambda-Funktion schon während des Imports von pandas auf Probleme stoßen und folgende Fehlermeldung ausgeben:

Unable to import module 'lambda_function': No module named 'pandas'

Die Lösung

Glücklicherweise hat AWS dieses Problem bedacht und mehrere mögliche Lösungswege bereitgestellt. Bevor mögliche Lösungswege diskutiert werden ein wichtiger Hinweis, der für alle Lösungen gilt: Viele Bibliotheken sind für spezifische Versionen der Runtime veröffentlicht. Daher ist es wichtig, die benötigten Bibliotheken hinsichtlich der Runtimeversion zu checken (Bspw über https://pypi.org/). Am Beispiel von pandas ergibt sich folgendes Bild (Quelle: https://pypi.org/project/pandas/#files, 01.07.2020):

Hier sieht man, dass die Pythonversion für pandas zur Zeit bei Python 3.6 liegt, weshalb die Runtime der Lambda-Funktion entsprechend eingestellt werden sollte:

Bild 3 – Runtime setting für die Lambda-Funktion

Wie also stellt man Lambda nun die benötigten Bibliotheken zu Verfügung? Hier gibt es mehrere Lösungswege:

  1. Manuelles installieren von .whl- oder tar.gz-files
  2. Installieren mit “target”-Option von pip
  3. Nutzung von AWS Lambda Layers

Um das richtige Paket auszuwählen, muss man wissen, dass Amazon AMIs einen *.x86_64”-Kernel verwenden. Daher sind Pakete auszuwählen, wekche für diesen Kernel ausgelegt sind:

*-manylinux1_x86_64.whl

Manuelles installieren von .whl- oder tar.gz-files

Hierbei werden die entsprechenden Bibliotheken explizit zum Verzeichnis hinzugefügt, in welchem der Funktionscode liegt. Das entsprechende Paket (in diesem Beispiel pandas) wird als .whl oder .tar.gz Datei heruntergeladen und in das Verzeichnis der importierenden Lambda-Funktion verschoben. Für Pandas muss folgendes Paket ausgewählt (vgl. Bild 2),

pandas-1.0.5-cp36-cp36m-manylinux1_x86_64.whl

in den entsprechenden Ordner verschoben und mit

unzip pandas-1.0.5-cp36-cp36m-manylinux1_x86_64.whl

entpackt werden (bspw mit der Cmder-Konsole, die Linuxbefehle unter Windows zur Verfügung stellt: https://cmder.net/). Im Fall von Pandas müssen zwei weitere Pakete installiert werden:

  • numpy
  • pytz

Im Anschluss sollte man nicht benötigte Artefakte entfernen:

rm -r *.whl *.dist-info __pycache__

Das Endergebnis sollte wie folgt aussehen:

Bild 4 – Finales Verzeichnis

Abschließend wählt man alle Dateien, einschließlich Funktionscode, aus und fügt diese zu einer .zip-Datei hinzu. Diese kann nun in Lambda anstelle des Funktionscode genutzt werden:

Bild 5 – Codeupload in der AWS Management Konsole

Cloud 9 (Entwicklungseditor von Lambda) kann Code nur bis zu einer Größe von 3MB darstellen, weshalb die Bearbeitungsmöglichkeit im Browser meist entfällt.

Installieren mit “target”-Option von pip

Anstatt alle Pakete manuell zu installieren, kann man einen Paketmanager wie “pip” nutzen. Mithilfe des “target”-flags kann bestimmt werden, in welchen Ordner das Package installiert werden soll:

pip install --target ./<<dir>> pandas

Zusätzlich kann das “–no-deps“ flag verwendet werden. Hierdurch wird nur die Bibliothek und nicht dessen Dependencies heruntergeladen. Somit bläht sich die Größe des Deployment packages nicht unnötig auf.

Anschließen erstellt man wie bei Lösungsweg 1 ein .zip-file und lädt dies bei AWS Lambda hoch. Siehe auch: https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-dependencies

Diese Möglichkeit spart im Vergleich zu 1 Zeit, hat jedoch zur Folge, dass das Deployment Package unnötig aufgebläht werden kann.

Lambda Layers

Die “AWS-nativste” Lösung ist die Nutzung von sog. Lambda Layers. Diese muss man einmalig konfigurieren und kann sich im Anschluss auf die Entwicklung des Codes konzentrieren und umgeht das wiederholte hochladen von zip-Dateien, falls Codeänderungen einen neuen Upload nötig machen.

Zur Vorbereitung müssen die entsprechenden Bibliotheken mit einem der beiden vorgestellten Vorgehen heruntergeladen werden und Runtime-spezifisch strukturiert und in einer .zip-Datei gepackt werden. Für Python ergibt sich folgende Struktur:

lambda_layer_package.zip
├───python
  ├───numpy
  │   ├───[subfolders...]
  ├───pandas
  │   ├───[subfolders...]
  └───[additional package folders...]

Andere Programmiersprachen benötigen ähnliche Ordnerstrukturen, siehe dazu auch https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-path.

Um einen Layer zu nutzen, erstellt man diesen im Hauptmenü von Lamba, indem man die Option “Layers” (siehe Bild) auswählt und dann über den Button “Create Layer” einen Layer anlegt.

Bild 6 – Layermenü in der AWS Management Konsole

Im Create-Dialog wählt man Name, Dependency-Package und eine Runtime entsprechend der Lambda-Funktion aus. Im Anschluss fügt man im Reiter “Design” der Lambda-Funktion den entsprechenden Layer hinzu:

Bild 7 – Hinzufügen des Layers zur Lambda-Funktion

Die Verwendung von Layern bietet Entwicklern einige Vorteile. In erster Linie können sie sich auf die Logik der Anwendung konzentrieren, anstatt sich um das zusätzliche Bibliotheken zu kümmern. Sobald ein Layer erstellt und konfiguriert ist, wird die Bereitstellung von neuem Code bequem, da die erneute Bereitstellung von Bibliotheken nun überflüssig ist.
Ein weiterer Vorteil ist die Tatsache, dass Schichten in anderen Funktionen wiederverwendet und sogar versioniert werden können, was Zeit und damit Kosten spart.

Falls Lambda Layers verwendet werden sollen, darf die Gesamtgröße des Deployment-Pakets 250MB (entpackt) nicht überschreiten!

Mehr Informationen zu Lambda Layers: https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html