[Java] Build AWS Lambda with Quarkus

5 minute read

Quarkus(https://quarkus.io/) is SUPERSONIC SUBATOMIC JAVA,

A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.

That’s right. I don’t know what you’re talking about

Since it is possible to convert Java programs natively with the function of GraalVM, it is possible to solve ** slow cold start problem ** which is a characteristic of Java applications running on AWS Lambda when combined with a custom runtime of AWS Lambda. I can expect

I tried following the procedure on the official website, so I will leave the procedure.

QUARKUS-BUILDING A NATIVE EXECUTABLE https://quarkus.io/guides/building-native-image

QUARKUS-AMAZON LAMBDA https://quarkus.io/guides/amazon-lambda

Environment

  • Docker Desktop (Mac) 2.3.0.4
  • VSCode + Visual Studio Code Remote-Containers extension
  • Amazon Linux 2 (on Docker)

Deploying template application

Step 1. Amazon Linux 2 environment setup

Amazon Linux 2 of the Docker image cannot do tar and unzip, so I will put various things. I don’t know if I need all of them, but I put in this much.

yum install -y sudo shadow-utils procps tar.x86_64 gzip xz unzip witch git python3 tree

Step 2. Install GraalVM

Download from the official website and unzip.

curl -s -L -o /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1 .0/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz
tar zxf /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz -C /opt/
ln -s /opt/graalvm-ce-java11-20.1.0 /opt/graalvm

Set JAVA_HOME to GraalVM and use GraalVM to build with Maven.

export GRAALVM_HOME=/opt/graalvm
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH

Finally, install the native-image required for Native build. The command is gu (GraalVM Updater).

gu install native-image

Step 3. Install Maven

Maven used in Quarkus builds requires version 3.6.2 or higher. The version that can be installed with yum was old, so I downloaded it from the official website and installed it.

curl -s -L -o /tmp/apache-maven-3.6.3-bin.tar.gz https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6 .3-bin.tar.gz
tar zxf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/
ln -s /opt/apache-maven-3.6.3 /opt/apache-maven

Check mvn version

bash-4.2# mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/apache-maven
Java version: 11.0.7, vendor: GraalVM Community, runtime: /opt/graalvm-ce-java11-20.1.0
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix"
bash-4.2#

You can see that it is running on the JVM of GraalVM.

Step 4. Maven project creation

Create a project with the mvn command.

mvn archetype:generate \
    -DarchetypeGroupId=io.quarkus \
    -DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
    -DarchetypeVersion=1.6.0.Final

After a while, you will be asked a question and will answer. User input is shown in [].

Define value for property'groupId': [myGroup]
Define value for property'artifactId': [myArtifact]
Define value for property'version' 1.0-SNAPSHOT: :[]
Define value for property'package' myGroup: :[example]
Confirm properties configuration:
groupId: myGroup
artifactId: myArtifact
version: 1.0-SNAPSHOT
package: example
 Y:: [Y]

A directory is created with artifactId(myArtifact) and a project is generated.

The project structure immediately after creation is like this.

bash-4.2# tree myArtifact/
myArtifact/
├── build.gradle
├── gradle.properties
├── payload.json
├── pom.xml
├── settings.gradle
└── src
    ├── main
    │ ├── java
    │ │ └── example
    │ │ ├── InputObject.java
    │ │ ├── OutputObject.java
    │ │ ├── ProcessingService.java
    │ │ ├── StreamLambda.java
    │ │ ├── TestLambda.java
    │ │ └── UnusedLambda.java
    │ └── resources
    │ └── application.properties
    └── test
        ├── java
        │ └── example
        │ └── LambdaHandlerTest.java
        └── resources
            └── application.properties

9 directories, 14 files
bash-4.2#

Step 5. Setting the Handler started by Lambda

Handler called by Lambda is set in quarkus.lambda.handler of resources/application.properties.

resources/application.properties


quarkus.lambda.handler=test

With the above settings, the class named test below is called.

java:main/java/example.TestLambda.java


@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}

After that, code like normal Lambda. In addition to test, the template also has stream etc.

Step 6. Create Deployment Package

Let’s create a deployment package with the template once.

mvn clean package -Pnative

It takes a lot of time. I think it will take more than 10 minutes.

Step 7. Confirm the contents of the deployment package

When the deploy package is created successfully, target/function.zip is generated. When I tried expanding it, the contents were only bootstrap.

bash-4.2# unzip function.zip
Archive: function.zip
  inflating: bootstrap
bash-4.2#

Step 8. Deploy Lambda

A file called manage.sh is also generated in the target directory, and it seems that you can deploy it on AWS from here. I was new to a custom runtime and I tried it from the management console. A file called target/sam.native.yaml is also generated after the deployment package is created, so I used this as a reference for the handler name and environment variables.

Create a new function. Let the runtime be provide your own bootstrap.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen).png

After creating the function, upload the program.Select Upload .zip file from the action of the function code, and select function.zip.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (1).png

The handler will be not.used.in.provided.runtime.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (2).png

Add DISABLE_SIGNAL_HANDLERS to the environment variable and set the value to true.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (4).png

This completes the settings. Let’s test-execute the following JSON as input.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (3).png

{
  "name": "Bill",
  "greeting": "hello"
}

Click here for the operation results. It moved safely.

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (5).png

Add AWS SDK

To use the AWS SDK, you need to add some settings to pom.xml as well.

Step 1. Enable SSL communication

Add the following content to resources/application.properties. (Although it seems that it is enabled by default in the official document)

resources/application.properties


quarkus.ssl.native=true

Step 2. Add dependency

First, we need quarkus-jaxb, so add it.

pom.xml


<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jaxb</artifactId>
</dependency>

Next, add the AWS SDK library.

For native image, however the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).

When you translate However, for native images, due to GraalVM compilation issues (currently), you should prioritize URL-connecting clients over Apache HTTP clients when using synchronous mode. That’s right. Therefore, the description is as follows.

pom.xml


<properties>
      <aws.sdk2.version>2.10.69</aws.sdk2.version>
  </properties>

  <dependencyManagement>
      <dependencies>

          <dependency>
              <groupId>software.amazon.awssdk</groupId>
              <artifactId>bom</artifactId>
              <version>${aws.sdk2.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>

      </dependencies>
  </dependencyManagement>
  <dependencies>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>url-connection-client</artifactId>
      </dependency>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>apache-client</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>s3</artifactId>
          <exclusions>
              <!-- exclude the apache-client and netty client -->
              <exclusion>
                  <groupId>software.amazon.awssdk</groupId>
                  <artifactId>apache-client</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>software.amazon.awssdk</groupId>
                  <artifactId>netty-nio-client</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      <dependency>
          <groupId>org.jboss.logging</groupId>
          <artifactId>commons-logging-jboss-logging</artifactId>
          <version>1.0.0.Final</version>
      </dependency>
  </dependencies>

Step 3. Create Java code

This is an example of accessing S3, but when creating S3Client, specify UrlConnectionHttpClient explicitly in httpClient.

S3Client s3 = S3Client.builder()
  .region(Region.AP_NORTHEAST_1)
 
 .httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
  .build();

Step 4. Settings required for SSL communication

The deployment package must include the following for SSL communication.

  • Custom Bootstrap
  • libsunec.so
  • cacerts

First, create a src/main/zip.native/ directory and create a bootstrap.

zip.native/bootstrap


#!/usr/bin/env bash

./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts

Then copy libsunec.so and cacerts. These two files are included in GraalVM.

cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/
cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/

Step 5. Create Deployment Package

The procedure for creating a deployment package does not change.

mvn clean package -Pnative

Step 6. Confirm the contents of the deployment packageIf you create a deployment package with SSL enabled, the contents of target/function.zip will change.

bash-4.2# unzip function.zip
Archive: function.zip
   inflating: bootstrap
   inflating: cacerts
   inflating: libsunec.so
   inflating: runner
bash-4.2#

You can see that the pre-prepared bootstrapcacertslibsunec.so is included.

At the end

Please see here for the verification result that the cold start is faster than the normal Java runtime.

Quarkus Saves Java Lambda! ? https://qiita.com/moritalous/items/4de31a66edac728ba088