Iqbal´s DLQ main Help

GraalVM with AWS Lambda - Starter

The goal is to showcase that the Java programming language can compete as well in the serverless space.

We will be building a native executable with the help of GraalVM's Native Image technology, and deploy it on AWS Lambda.

Custom Lambda runtimes

Lambdas support managed runtimes for most prominent programming languages, including Java.

We won´t be using the AWS runtime for Java in this tutorial because the whole point is to not run on the JVM!

AWS Lambda allow you to run your own custom runtime directly on the Amazon Linux 2 (AL2).

This will allow us to run a native binary, provided it is for AL2 and x86_64/arm64 architecture:

All we need to provide is a zip archive containing a bootstrap file and the function being invoked:

. ├── bootstrap ├── function.sh

Example Bootstrap

bootstrap

#!/bin/sh set -euo pipefail # Initialization - load function handler source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" # Processing while true do HEADERS="$(mktemp)" # Get an event. The HTTP request will block until one is received EVENT_DATA=$(curl -sS -LD "$HEADERS" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") # Extract request ID by scraping response headers received above REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Run the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response curl "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" done

Example Function

function.sh

function handler () { EVENT_DATA=$1 echo "$EVENT_DATA" 1>&2; RESPONSE="Echoing request: '$EVENT_DATA'" echo $RESPONSE }

Package and grant permissions

Give the right privileges and zip the files together:

chmod 755 function.sh bootstrap zip lambda.zip bootstrap function.sh

Configure the Lambda as follows :

img.png

Setup the right entry point after creation:

img.png

finally, upload your zip file, and voila :

img.png

Congrats, you are running a custom runtime!

Compile GraalVM Native Image

Prepare build environment

Graalvm does not support cross-platform compilation at this time.

This means that we need to build our native executable on the same platform the lambda function is going to be running on, ie AL2 amd_64.

I will be using a custom al2 docker image that includes graalvm for jdk17, and both gradle and mvn build tools.

Dockerfile

FROM amazonlinux:2 RUN yum -y update \ && yum install -y tar unzip gzip bzip2-devel ed gcc gcc-c++ gcc-gfortran \ less libcurl-devel openssl openssl-devel readline-devel xz-devel \ zlib-devel glibc-static libcxx libcxx-devel llvm-toolset-7 zlib-static \ && rm -rf /var/cache/yum ENV GRAAL_VERSION 22.3.0 ENV GRAAL_FOLDERNAME graalvm-ce-java17-${GRAAL_VERSION} ENV GRAAL_FILENAME graalvm-ce-java17-linux-amd64-${GRAAL_VERSION}.tar.gz RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} | tar -xvz RUN mv $GRAAL_FOLDERNAME /usr/lib/graalvm RUN rm -rf $GRAAL_FOLDERNAME # Graal maven plugin requires Maven 3.3.x ENV MVN_VERSION 3.8.8 ENV MVN_FOLDERNAME apache-maven-${MVN_VERSION} ENV MVN_FILENAME apache-maven-${MVN_VERSION}-bin.tar.gz RUN curl -4 -L https://mirrors.ukfast.co.uk/sites/ftp.apache.org/maven/maven-3/${MVN_VERSION}/binaries/${MVN_FILENAME} | tar -xvz RUN mv $MVN_FOLDERNAME /usr/lib/maven RUN rm -rf $MVN_FOLDERNAME # Gradle ENV GRADLE_VERSION 7.4.1 ENV GRADLE_FOLDERNAME gradle-${GRADLE_VERSION} ENV GRADLE_FILENAME gradle-${GRADLE_VERSION}-bin.zip RUN curl -LO https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip RUN unzip gradle-${GRADLE_VERSION}-bin.zip RUN mv $GRADLE_FOLDERNAME /usr/lib/gradle RUN rm -rf $GRADLE_FOLDERNAME VOLUME /project WORKDIR /project RUN /usr/lib/graalvm/bin/gu install native-image RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn RUN ln -s /usr/lib/gradle/bin/gradle /usr/bin/gradle ENV JAVA_HOME /usr/lib/graalvm ENTRYPOINT ["sh"]

Build an image with docker that will serve as our build environment:

docker build -t al2graalvm:1.0 .

Run the build on AL2

Use the AL2 GraalVM Docker image we just built to compile a native image binary:

With Gradle

gradle nativeCompile

Gradle will output the native image executable to

/build/native/nativeCompile/

Package native image

cp ./build/native/nativeCompile/my-binary my-binary chmod 755 function.sh bootstrap my-binary zip lambda.zip bootstrap function.sh my-binary

Adapt function.sh to call binary

function handler () { EVENT_DATA=$1 echo "$EVENT_DATA" 1>&2; RESPONSE="Echoing request: '$EVENT_DATA'" ./my-binary echo $RESPONSE }

Re-upload the newly created Zip, and test your function :

img.png

That´s Intellij´s java hello world :)

Summary

This is a quick dive to setup a skeleton for your GraalVM on AWS Lambda deployment.

I will cover in the next article a real world example with pipeline setup on bitbucket

Last modified: 12 March 2024