Packaging AWS Java lambdas in Docker
AWS Lambda is a great service, but you need to be careful when packaging Java programs for AWS Lambda. It is really easy to make a silly mistake and spend a day trying to understand what’s going on, why classes can’t be found, and looking for mediation.
In this article I will guide you through packaging of Docker-based Java function, but many ideas are also applicable to ZIP functions too.
First, let’s pickup a simple Java function for the purpose of the exercise. Go ahead and clone java-basic project. It has both maven and gradle build files, thus you can use any of the build tool. Take 5 minutes to familiarise yourself with the handlers’ code, but do not go deep with the bash scripts in the root, as we won’t use them for OCI deployment. All we need is the Java code.
Second, we will make up a Dockerfile. If you are using gradle, use this one:
FROM public.ecr.aws/lambda/java:11
COPY build/classes/java/main /var/task
COPY build/dependency/* /var/task/lib/
CMD [ "example.Handler::handleRequest" ]
Let’s walk through it:
- FROM defines a base image. It is a good idea to derive from the official AWS image baked for Java 11
- COPY class files from build directory into Lambda’s task root
- COPY runtime dependencies from build directory to the image’s lib directory. We will define a gradle task to collect the runtime dependencies a little bit later.
- CMD defines Lambda’s entry point. It must match Java package name and handler name. Please note the semicolons between them. This notion is different from standard Java notion, where fully qualified name of a method utilises number sign -
com.example.ClassName#methodName
In order to collect runtime dependencies in gradle you can use the following task.
task copyRuntimeDependencies(type: Copy) {
from configurations.runtimeClasspath
into 'build/dependency'
}
build.dependsOn copyRuntimeDependencies
After everything is copied, the resulting directory structure will look like:
/var/task/
├── example
│ ├── Handler.class
│ ├── HandlerDivide.class
│ └── <other class files here>
│
└── lib
├── aws-lambda-java-core-1.2.1.jar
└── gson-2.8.6.jar
Now we should be ready to build the project and package it into a docker image:
$ gradle build && docker build -t java-basic-oci .
To test it locally, let’s run a container:
$ docker run -p 9000:8080 java-basic-oci
and in the other terminal tab:
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
Examine function output in both terminals.
Homework exercise #1: run container for another function from the project
Homework exercise #2: run an OCI function in the cloud
Common mistakes you should avoid:
- copying dependency jar files into
/var/task
directory. They are just invisible for lambda. Always use/var/task/lib
for dependencies - copying main code jar files into
/var/task
directory. This directory must contain*.class
files only, or directories with class files (packages), but not jars. - naming jar files with extension different from
jar
, for examplefunction.JAR
orfunction.Jar
. Such files are also invisible for lambda. Correct:function.jar
As a bonus, below you can find Dockerfile for Maven layout and a plugin setup to copy runtime dependencies:
FROM public.ecr.aws/lambda/java:11
COPY target/classes /var/task
COPY target/dependency/* /var/task/lib/
CMD [ "example.Handler::handleRequest" ]
include maven-dependency-plugin
to the project to copy runtime dependencies to target/dependency
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
use the following command to compile the project and collect the dependencies:
$ mvn compile dependency:copy-dependencies -DincludeScope=runtime
That’s it! Happy coding!
I used materials from https://gallery.ecr.aws/lambda/java for this article.