This post was originally published on my personal blog
At re:Invent 2020 Amazon announced support for deploying and running containers directly to Lambda. Here is a link to the blog post for the announcement https://aws.amazon.com/blogs/aws/new-for-aws-lambda-container-image-support/.
Recently, I've been messing around with Quarkus which is a relatively new Java framework for building Apps and already has a pretty impressive eco-system.
This demo shows how you can package a Quarkus Lambda App using the new Lambda containers support. I basically used the Quarkus - Amazon Lambda guide as the basis for this demo.
The code for this demo is available on GitHub https://github.com/base2Services/quarkus-lambda-container-demo
To complete this guide, you need:
So lets get started.
First, we are going to create a basic Quarkus Lambda app using the Maven archetype.
These steps are taken from the Quarkus Lambda guide.
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
-DarchetypeVersion=1.10.2.Final
This will create a standard Quarkus Lambda app in a directory based on the Maven artifactId you entered. I will use quarkus-lambda-demo
for the rest of the guide.
This generates a simple Quarkus Lambda application with a Test Lambda handler.
package com.base2services;
import javax.inject.Inject;
import javax.inject.Named;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
@Inject
ProcessingService service;
@Override
public OutputObject handleRequest(InputObject input, Context context) {
return service.process(input).setRequestId(context.getAwsRequestId());
}
}
Add the following dependency to the Maven pom dependencies.
....
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
<version>1.0.0</version>
</dependency>
....
In the quarkus-lambda-demo run Maven.
$ mvn clean install
...
INFO] --- maven-install-plugin:2.4:install (default-install) @ quarkus-lambda-demo ---
[INFO] Installing /Users/aaronwalker/Workspaces/aaronwalker/quarkus-lambda-container-demo/quarkus-lambda-demo/target/quarkus-lambda-demo-1.0-SNAPSHOT.jar to /Users/aaronwalker/.m2/repository/com/base2services/quarkus-lambda-demo/1.0-SNAPSHOT/quarkus-lambda-demo-1.0-SNAPSHOT.jar
[INFO] Installing /Users/aaronwalker/Workspaces/aaronwalker/quarkus-lambda-container-demo/quarkus-lambda-demo/pom.xml to /Users/aaronwalker/.m2/repository/com/base2services/quarkus-lambda-demo/1.0-SNAPSHOT/quarkus-lambda-demo-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.732 s
[INFO] Finished at: 2020-12-03T15:10:39+01:00
This will create the required artifacts in the target directory.
The quarkus-lambda-demo project has by default configured the test handler in the quarkus-lambda-demo/src/main/resources/application.properties/
and we will use this handler for the demo.
quarkus.lambda.handler=test
Create a Dockerfile in the quarkus-lambda-container-demo using the public.ecr.aws/lambda/java:8.al2
Lambda java8 Amazon Linux 2 runtime container as the base image.
# (1)
FROM public.ecr.aws/lambda/java:8.al2
ARG APP_NAME=quarkus-lambda-demo
ARG APP_VERSION=1.0-SNAPSHOT
# (2) Copies artifacts into /function directory
ADD ${APP_NAME}/target/${APP_NAME}-${APP_VERSION}-runner.jar /var/task/lib/${APP_NAME}.jar
ADD ${APP_NAME}/target/lib/ /var/task/lib/
# (3) Setting the command to the Quarkus lambda handler
CMD ["io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest"]
1) Details about the Lambda container images can be found at https://docs.aws.amazon.com/lambda/latest/dg/images-create.html
2) Copies the runner and its dependencies into the default WORKDIR which is /var/task
3) Overrides the CMD using the default Quarkus Lambda handler
$ docker build -t quarkus/lambda-demo .
....
Successfully built 09666b8a56b0
Successfully tagged quarkus/lambda-demo:latest
You can now use this image to test the Lambda execution locally using:
$ docker run --rm -it -p 9000:8080 quarkus/lambda-demo:latest
....
INFO[0000] exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
....
This starts the AWS Lambda runtime emulator and a web server listening locally on port 9000. You can invoke the test Lambda handler using curl.
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"greeting":"herzlich willkommen", "name":"aaron"}'
....
{"result":"herzlich willkommen aaron","requestId":"d8a48f84-a166-429e-a8ec-d8bea2e7087c"}
In order to be able to create a Lambda function from our container image we need to push it to a registry. I will use ECR in the guide.
Assumes you have valid AWS credentials configured
$ aws ecr create-repository --repository-name quarkus/lambda-demo --region eu-central-1
....
{
"repository": {
"repositoryArn": "arn:aws:ecr:eu-central-1:<aws-accountid>:repository/quarkus/lambda-demo",
"registryId": "<aws-accountid>",
"repositoryName": "quarkus/lambda-demo",
"repositoryUri": "<aws-accountid>.dkr.ecr.eu-central-1.amazonaws.com/quarkus/lambda-demo",
"createdAt": "2020-12-03T16:10:37+01:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
$ aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin <aws-accountid>.dkr.ecr.eu-central-1.amazonaws.com
$ docker tag quarkus/lambda-demo <aws-accountid>.dkr.ecr.eu-central-1.amazonaws.com/quarkus/lambda-demo
$ docker push <aws-accountid>.dkr.ecr.eu-central-1.amazonaws.com/quarkus/lambda-demo
....
The push refers to repository [<aws-accountid>.dkr.ecr.eu-central-1.amazonaws.com/quarkus/lambda-demo]
Create a SAM template to deploy the function.
sam.container.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS Serverless Quarkus - quarkus-lambda-demo-1.0-SNAPSHOT
Parameters:
ImageUri:
Type: String
Resources:
QuarkusLambdaDemo:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
ImageUri: !Ref ImageUri
MemorySize: 256
Timeout: 15
Policies: AWSLambdaBasicExecutionRole
Outputs:
Function:
Value: !Ref QuarkusLambdaDemo
Now deploy the template.
AWS_REGION=eu-central-1
AWS_ACCOUNT_ID=xxxxxx
$ aws cloudformation deploy \
--stack-name quarkus-lambda-demo \
--template-file sam.container.yaml \
--parameter-overrides ImageUri=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/quarkus/lambda-demo:latest \
--capabilities CAPABILITY_IAM \
--region ${AWS_REGION}
Now invoke the function.
$ aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name QuarkusLambdaDemo \
--payload '{"greeting":"herzlich willkommen", "name":"aaron"}' \
--region ${AWS_REGION}
out.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
$ cat out.json
{"result":"herzlich willkommen aaron","requestId":"02d6569d-0452-4ed7-bdbe-7e860a8592d8"}
Without much modification it was possible to create a simple Quarkus Lambda App and package it as a container image. Quarkus gives you the ability to run the app locally. However, by running it in the container, it gives you the same environment that the app will run in when deployed to Lambda. Another big advantage of using a container is that you aren't restricted by the Lambda zip file size limit.
Follow me on Twitter for regular updates and my random thoughts on various topics. If you have questions or remarks, or would just like to get in touch, you can also find me on LinkedIn.