Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 219 additions & 3 deletions aws-lambda-java-runtime-interface-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

We have open-sourced a set of software packages, Runtime Interface Clients (RIC), that implement the Lambda
[Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html), allowing you to seamlessly extend your preferred
base images to be Lambda compatible.
base images to be Lambda compatible or to implement your own custom Lambda runtime.
The Lambda Runtime Interface Client is a lightweight interface that allows your runtime to receive requests from and send requests to the Lambda service.

You can include this package in your preferred base image to make that base image Lambda compatible.
## Usage 1: Deploy your Lambda as a Container Image without native compilation

## Usage
You can include this package in your preferred base image to make that base image Lambda compatible.

### Creating a Docker Image for Lambda with the Runtime Interface Client

Expand Down Expand Up @@ -151,6 +151,222 @@ DOCKERHUB_PASSWORD=<dockerhub password>
```
Recommended way is to set the Docker Hub credentials in CodeBuild job by retrieving them from AWS Secrets Manager.

## Usage 2: Deploy your Lambda with Native Compilation

You can include this package as a dependency to build your Custom Runtime or to run your Lambda as a Container without any Java Runtime.

The idea here is to compile natively your java bytecode with [GraalVM](https://www.graalvm.org) [native-image](https://www.graalvm.org/reference-manual/native-image/) to a linux executable file.
In order to ease this native compilation of the Java Runtime Interface Client, the Java RIC jar artifact is now including special configuration files for GraalVM native-image.

Note: In this example the docker image provided may be used
* For cross compiling only to linux x86-64 and extract the Custom Runtime zip to deploy
* For deploying your Lambda as a Container Image
* For local testing of your Container Image or your native executable.

### Example: hello-lambda-native project

Let's take the same Lambda as before:

src/main/java/example/App.java
```java
package example;

public class App {
public static String sayHello() {
return "Hello λ!";
}
}
```

This is the needed configuration for native-image for finding the Lambda code:

src/main/resources/META-INF/native-image/reflect-config.json
```json
[
{
"name":"example.App",
"allPublicMethods":true
}
]
```

When building a Custom Runtime you need a zip archive containing at root a file named bootstrap that will be the entry point for running your Lambda. In a Container, the same bootstrap file is used as ENTRYPOINT. And finally, it contains also the logic to identify the case of local testing:

bootstrap
```bash
#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/bin/aws-lambda-rie ./func $1
else
set -euo pipefail
exec ./func $_HANDLER
fi
```

The Dockerfile is slightly more complex to support the three use cases described above:

Dockerfile
```dockerfile
FROM amazonlinux:2 as base

FROM base as build
ENV LANG=en_US.UTF-8
RUN yum update -y && yum install -y gcc gcc-c++ zlib-devel zip tar gzip && yum clean all
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java11-linux-amd64-20.3.0.tar.gz -o /tmp/graalvm.tar.gz \
&& tar -zxf /tmp/graalvm.tar.gz -C /tmp \
&& mv /tmp/graalvm-ce-java11-20.3.0 /usr/lib/graalvm \
&& rm -rf /tmp/*
RUN /usr/lib/graalvm/bin/gu install native-image
ENV PATH=/usr/lib/graalvm/bin:${PATH}
ENV JAVA_HOME=/usr/lib/graalvm
RUN yum install -y maven

# compile the function
WORKDIR /home/app
ADD . .
RUN mvn package

COPY bootstrap /home/app/
# (Optional) Add Lambda Runtime Interface Emulator and use a script in the ENTRYPOINT for simpler local runs
ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /home/app/aws-lambda-rie
RUN native-image -jar target/hello-lambda-native-1.0-SNAPSHOT.jar -H:Name=func --no-fallback
RUN yum install -y zip && yum clean all
RUN chmod 755 aws-lambda-rie
RUN chmod 755 bootstrap
RUN chmod 755 func
RUN zip -j function.zip bootstrap func

FROM base
WORKDIR /function
COPY --from=build /home/app/func /function/func
COPY --from=build /home/app/bootstrap /function/bootstrap
COPY --from=build /home/app/function.zip /function/function.zip
COPY --from=build /home/app/aws-lambda-rie /usr/bin/aws-lambda-rie
ENTRYPOINT [ "/function/bootstrap" ]
CMD [ "example.App::sayHello" ]
```

pom.xml
```xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello-lambda-native</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<jdk.version>11</jdk.version>
<release.version>11</release.version>
<exec.mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</exec.mainClass>
</properties>

<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>${jdk.version}</source>
<target>${release.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/libs
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>${exec.mainClass}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

</plugins>

</build>

</project>
```

### Custom Runtime

just build the image with Docker:

```bash
docker build -t hello-lambda-native:latest .
```

Then extract the Custom Runtime deployable zip bundle with this command:

```bash
docker cp $(docker create hello-lambda-native:latest):/function/function.zip .
```

### Container with native compilation

just build the image with Docker:

```bash
docker build -t hello-lambda-native:latest .
```

This image can be deployed directly.

### Local Testing

just build the image with Docker:

```bash
docker build -t hello-lambda-native:latest .
```

Then you can run the image locally:

```bash
docker run -p 9000:8080 hello-lambda-native:latest
```

And test it from another Terminal/Console with:

```bash
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
```

## Security

If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }]
},
{
"name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest",
"fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name":"content"}],
"allPublicMethods":true
}
Comment on lines +7 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've discovered that this JNI configuration triggers the following error during native image generating with a Micronaut application:

[application:25]     universe:   2,074.36 ms,  4.87 GB
Fatal error:java.lang.NoClassDefFoundError
       at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
       at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
       at java.base/java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:603)
       at java.base/java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1006)
       at com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:488)
        at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:370)
        at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:529)
       at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:119)
        at com.oracle.svm.hosted.NativeImageGeneratorRunner$JDK9Plus.main(NativeImageGeneratorRunner.java:561)
Caused by: java.lang.NoClassDefFoundError: io/netty/internal/tcnative/SSLPrivateKeyMethod
        at jdk.internal.vm.ci/jdk.vm.ci.hotspot.CompilerToVM.getDeclaredMethods(Native Method)
       at jdk.internal.vm.ci/jdk.vm.ci.hotspot.HotSpotResolvedObjectTypeImpl.getDeclaredMethods(HotSpotResolvedObjectTypeImpl.java:987)
       at com.oracle.svm.jni.access.JNIAccessibleMethod.anyMatchIgnoreReturnType(JNIAccessibleMethod.java:139)
       at com.oracle.svm.jni.access.JNIAccessibleMember.findHidingSubclasses(JNIAccessibleMember.java:103)
       at com.oracle.svm.jni.access.JNIAccessibleMember.findHidingSubclasses(JNIAccessibleMember.java:110)
       at com.oracle.svm.jni.access.JNIAccessibleMember.setHidingSubclasses(JNIAccessibleMember.java:83)
        at com.oracle.svm.jni.access.JNIAccessibleMethod.finishBeforeCompilation(JNIAccessibleMethod.java:135)
       at com.oracle.svm.jni.access.JNIAccessFeature.beforeCompilation(JNIAccessFeature.java:350)
        at com.oracle.svm.hosted.NativeImageGenerator.lambda$doRun$2(NativeImageGenerator.java:619)
       at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:70)
       at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:619)
       at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:476)
       at java.base/java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1407)
       at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
       at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
       at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
       at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
       at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: java.lang.ClassNotFoundException: io.netty.internal.tcnative.SSLPrivateKeyMethod
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
        ... 18 more
Error: Image build request failed with exit status 1

More info: micronaut-projects/micronaut-aws#1024 (comment)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I was wrong about this. The thing that triggers everything is this resource configuration (already included in this PR):

{
  "resources":{
  "includes":[
    {"pattern":"\\Qaws-lambda-runtime-interface-client.glibc.so\\E"}, 
    {"pattern":"\\Qaws-lambda-runtime-interface-client.musl.so\\E"}
  ]},
  "bundles":[]
}

More info: micronaut-projects/micronaut-aws#1024 (comment)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these native libraries are necessary for running the RIC...
I cannot understand why there would be this strange side effect...

Except that without this configuration nothing is working nor failing ??!?

]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \
-H:ReflectionConfigurationResources=${.}/reflect-config.json \
-H:JNIConfigurationResources=${.}/jni-config.json \
-H:ResourceConfigurationResources=${.}/resource-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]"
},
{
"name":"com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"com.amazonaws.services.lambda.runtime.LambdaRuntime",
"fields":[{"name":"logger"}]
},
{
"name":"java.lang.Void",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"java.util.Collections$UnmodifiableMap",
"fields":[{"name":"m"}]
},
{
"name":"jdk.internal.module.IllegalAccessLogger",
"fields":[{"name":"logger"}]
},
{
"name":"sun.misc.Unsafe",
"fields":[{"name":"theUnsafe"}]
},
{
"name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest",
"fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name":"content"}],
"allPublicMethods":true
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"resources":{
"includes":[
{"pattern":"\\Qaws-lambda-runtime-interface-client.glibc.so\\E"},
{"pattern":"\\Qaws-lambda-runtime-interface-client.musl.so\\E"}
]},
"bundles":[]
}