0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

通常のJavaで動いているAWS LambdaをGraalvmを用いてネイティブ化してみた

0
Posted at

通常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ファイルを作成する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?