通常Javaで記述したAPI Gateway+AWS LambdaのLambdaプログラムをNative化してみたので、そこで追加・修正した内容を書いておく
Dockerfile作成
以下の通りプロジェクトの直下にDockerfileを作成する
FROM ghcr.io/graalvm/graalvm-community:25 AS builder
RUN microdnf install -y --nodocs \
zip \
findutils \
gcc-14.3.1-2.1.el10 \
glibc-devel-2.39-58.0.1.el10 \
zlib-ng-compat-devel-2.2.3-2.el10 \
&& microdnf clean all
WORKDIR /app
COPY . .
RUN chmod +x src/main/resources/bootstrap
RUN ./gradlew --no-daemon buildZip
Native化のbatファイルを用意
いちいちコマンドをたたくのが面倒くさいのでNative化するコマンドをbatファイルにまとめた
docker build -t lambda-graalvm-gradle .
docker create --name extract lambda-graalvm-gradle
docker cp extract:/app/build/distributions/{LambdaにデプロイするZIPファイル名} .
docker rm extract
bootstrapファイルの作成
Native化したLambdaはbootstrapファイルを起動しようとするので用意する
#!/bin/sh
set -eu
cd $LAMBDA_TASK_ROOT
./{出力されるバイナリ名}
{出力されるバイナリ名}は、build.gradleにて指定
build.gradleの修正
Native化するためのpluginやtaskの記述を追加する
plugins {
・・・
id 'org.graalvm.buildtools.native' version '1.1.9' ←追加
}
dependencies {
・・・
implementation 'com.amazonaws:aws-lambda-java-serialization:1.4.1' ←追加
implementation 'org.springframework.boot:spring-boot-starter-json' ←追加
}
graalvmNative {
binaries {
main {
// 出力されるバイナリ名を設定
imageName = '出力されるバイナリ名'
buildArgs.add('--no-fallback')
buildArgs.add('--enable-native-access=ALL-UNNAMED')
buildArgs.add('-H:+AllowIncompleteClasspath')
// Native化するためにビルド時に初期化が必要なものがある
// それをここに追記していく
// 指定不足だとNative化(gradlew buildZip)するとエラーが出るので
// エラーがなくなるまで追加していくこと
buildArgs.add('--initialize-at-build-time=software.amazon.awssdk.core.SdkField') // 例
}
}
}
tasks.register('buildZip', Zip) {
// ネイティブコンパイルタスクの完了を待つ
dependsOn 'nativeCompile'
// 重複が発生した場合は上書きを許容(文字列指定が確実です)
duplicatesStrategy = 'include'
// 1. ネイティブコンパイルされたバイナリのみを含める(1箇所に集約)
from(nativeCompile.outputDirectory.get()) {
include '出力されるバイナリ名'
}
// bootstrapファイルに実行権限を与える
from('src/main/resources') {
include 'bootstrap'
eachFile { fileCopyDetails ->
fileCopyDetails.permissions { permissions ->
permissions.user { read = true; write = true; execute = true } // 7
permissions.group { read = true; write = false; execute = true } // 5
permissions.other { read = true; write = false; execute = true } // 5
}
}
}
// 出力するZIPファイル名と出力先
archiveFileName = 'LambdaにデプロイするZIPファイル名'
destinationDirectory = file("build/distributions")
}
LambdaRuntimeHints.javaの追加
GraalVMがNative化時に削除してしまったJavaの「リフレクション情報」を、明示的に登録して保護するために用意する
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
public class LambdaRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// 登録対象のクラス配列
Class<?>[] types = {
{Lambdaのハンドラクラス}.class,
{Lambdaのハンドラに渡すリクエストクラス}.class,
{Lambdaのハンドラから戻されるレスポンスクラス}.class, com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent.class,
com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent.ProxyRequestContext.class,
com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent.RequestIdentity.class,
com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent.class
};
for (Class<?> type : types) {
// リフレクションの全権限(コンストラクタ、フィールド、メソッド)を付与
hints.reflection().registerType(type, MemberCategory.values());
}
// 共有ライブラリ (.so) の登録パターン
hints.resources().registerPattern("libaws-lambda-jni.*\\.so");
hints.resources().registerPattern("jni/libaws-lambda-jni.*\\.so");
}
}
FunctionConfi.javaの追加
AWS LambdaとSpring Bootを仲介するためのルーティング設定を行う
@Configuration
public class FunctionConfig {
@Bean
public Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> getOnOffHandler() {
GetOnOffHandler originalHandler = new GetOnOffHandler();
return request -> originalHandler.handleRequest(request, null);
}
}
GetOnOffHandlerはLambdaのハンドラクラス。getOnOffHandlerは任意(Lambdaの環境設定で使用)
メインクラスの修正
Spring BootのメインクラスにLambdaRuntimeHintsを指定する
@SpringBootApplication
@ImportRuntimeHints(LambdaRuntimeHints.class)
public class メインクラス {
private static ApplicationContext context;
/**
* メイン処理
*
* @param args 引数
*/
public static void main(String[] args) {
SpringApplication.run(メインクラス.class, args);
}
public static ApplicationContext getContext() {
return context;
}
}
AWS Lambdaの設定
ランタイム設定
・ ランタイム設定のハンドラには"org.springframework.cloud.function.adapter.aws.FunctionInvoker"を設定する
・ ランタイムには"Amazon Linux 2023"を指定する
環境設定
以下の環境設定を行う
JAVA_TOOL_OPTIONS : --enable-native-access=ALL-UNNAMED
SPRING_CLOUD_FUNCTION_DEFINITION : getOnOffHandler(FunctionConfi.javaで定義したメソッド名)
spring_cloud_function_expected_output_type : com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
実行
上記の修正を行った後にbatファイルを起動してZIPファイルを作成する。