Spring Native + AWS Lambda: Part 2 - The Build
Part 2 is of this series is all about compiling our Function to a native executable and bundling it with a bootstrap
file for AWS Lambda.
Table of Contents:
- Part 1 - The Code
- Part 2 - The Build
- Part 3 - The Deploy
Getting Started
For compiling and packaging we will use the following plugins:
native-maven-plugin
for compiling the native imagemaven-assembly-plugin
for bundling the image with a bootstrap script
Referring back to the scenario in Part 1, we want to deploy our function to AWS Lambda, which uses Amazon Linux 2.
Building a native image requires installing build tools, GraalVM, and matching libraries used in Amazon Linux 2. We can do this either locally, or within a Docker container. Spring initializr configures paketobuildpacks
to create an image of our application using Docker. This approach is ideal if you are deploying a REST endpoint to Kubernetes or AWS Fargate. For our scenario, want to upload the code to AWS Lambda, which is why we will use our own build container.
If you want to install locally, instructions can be found in the Spring Native documentation.
Using Amazon Linux 2 as a base image, we can install GraalVM and native-image build tools to compile our application to be compatible with AWS. I have uploaded such an image to Dockerhub. We will use this image, as well as docker-compose to build our native-images.
Building The Native Image
Let's create a docker-compose.yml
file to get started.
The image assumes you are skipping tests and the profile name in pom.xml
is native
. You can see configuration options on DockerHub.
The builder image uses Amazon Linux 2 as a base and has everything necessary to compile our code. Edit command if you want to enable tests or have a different profile name. Before we can build, we need to mount our source code into the container as a volume.
One last bit of code before building. When compiling within the container, the target/ directory is owned by the user inside the container. This script will build and change permissions for us.
With our docker-compose.yml and build.sh files ready, we can compile. Run the build script and sit back. My builds tend to take between 1 and 2 minutes. If all goes well, you will be presented with a build success message.
A common error that can occur is Error: Image build request failed with exit status 137
, which means the container ran out of memory. Make sure you have at least 6GB+ for Docker to use for the build process.
Update: As of GraalVM 22.2, memory usage has been drastically reduced, allowing you to build with much less memory. As a result, it may take longer to compile. Highly recommended to still have 6GB+.
Congratulations! You have created your first Spring Native executable! Inside your target directory, you should see the build artifacts.
target/
├── cloud-function-dynamodb-lambda
├── cloud-function-dynamodb-lambda-exec.jar
├── cloud-function-dynamodb-lambda.jar
└── cloud-function-dynamodb-lambda-native.zip
cloud-function-dynamodb-lambda
is our native image and can be run using ./target/cloud-function-dynamodb-lambda
, but it will crash with java.lang.NoClassDefFoundError: org.springframework.cloud.function.web.source.DestinationResolver
. This is intended as the application is looking for AWS specific infrastructure that we do not have. Finally, cloud-function-dynamodb-lambda-native.zip
is our deployment package and will be used in Part 3 to deploy our function to AWS.
Recap of Part 2
native-maven-plugin
for compiling the native imagemaven-assembly-plugin
for bundling the image with a bootstrap script- Docker container as a build environment against Amazon Linux 2
In the final part, we will deploy our function as an AWS Lambda Function.
Full source code can be found on my Github
Click here for Part 3 - The Deploy