LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

Kotlin/Native on AWS Lambda その1 - Amazon Linux 2 上でビルド・実行

要約

  1. Kotlin/Native で書いたコードを Amazon Linux 2 上でビルド・実行する
  2. Kotlin/Native プロジェクトを作成
  3. ローカルでビルド・実行
  4. Docker で Amazon Linux 2 向けのバイナリを作成する環境を構築
  5. Amazon Linux 2 (Docker) 上でビルド・実行

ソースコード

はじめに

こんにちは。 lasta です。

本記事は Kotlin Advent Calendar 2020 8日目の記事です。
穴が空いていたので、後追いですが執筆しました。
昨日は wrongwrong さんの 【Kotlin】KFunctionを高速に呼び出す(後編) でした。

Kotlin 1.4 にて、 Kotlin/Native 含め Kotlin 及びその周辺に大幅なアップデートがありました。
詳細は公式ページ What's New in Kotlin 1.4.0 をご確認ください。

また、 kotlinx.serialization 1.0 がリリースされた (GA した) ことにより、 Kotlin/Native をプロダクション利用しやすくなりました。
これにより、 Kotlin で全プラットフォーム向けの実装ができるようになりましたね。

一方で Kotlin/Native はまだまだ発展途上にあります。
JetBrains 公式のサーバレスフレームワークとして Kotless がありますが、 本記事執筆時点 (2020/12/12) では Kotlin-JVM しか対応していません。
GraalVM 向けはベータリリース段階であり、 Multiplatform 向け (JVM/JS/Native) は開発中です。

そのため、 AWS Lambda の Custom Runtime 上で Kotlin/Native を動かすことをゴールとして進めていきます。
本記事は3部編成の1部目になります。

本記事のゴール

Kotlin/Native のコードを Amazon Linux 2 向けにネイティブビルドし、動作することを確認する

環境

Kotlin は主に JetBrains 社が開発しているため、同じく JetBrains 社が開発している IntelliJ IDEA を用いるととてもスムーズにローカルの環境構築を行う事ができます。

周辺環境

  • MacBook Pro 2019
    • macOS Big Sur 11.0.1
  • :wrench: IntelliJ IDEA Ultimate 2020.3
  • :wrench: docker desktop 3.0.1
  • Kotlin 1.4.20
    • IntelliJ IDEA および Gradle が自動的に環境構築してくれるため、手動でのインストールは不要

:wrench: は事前インストールが必要になります。

プロジェクト作成

IntelliJ IDEA を用いて作成します。
Gradle を利用するため、 gradle init で作成しても問題ありません。

スクリーンショットは IntelliJ IDEA Ultimate 2020.3 を用いて作成しております。

  1. IntelliJ IDEA を起動
  2. "File" → "New" → "Project"
  3. "New Project" new_project1.png
    1. "Kotlin" を選択
    2. "Name" を自由に指定
    3. "Location" を自由に指定
    4. "Multiplatform" の "Native Application" を指定
    5. "Build System" として "Gradle Kotlin" を指定
    6. "Project SDK" として Java 8 以降の JDK を指定
    7. "Group ID", "Artifact ID", "Version" を自由に指定
    8. "Next"
  4. "New Project" new_project2.png
    1. "Template" の "None" を指定
    2. "Finish"

いろいろなものが自動的に作成されます。

.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    ├── nativeMain
    │   ├── kotlin
    │   │   └── main.kt
    │   └── resources
    └── nativeTest
        ├── kotlin
        └── resources

サンプルコードの実行

まずは自動的に作成されるサンプルコードが動作することを確認します。

src/nativeMain/kotlin/main.kt を開き、 main 関数の左にある実行ボタンを押します。
初回ビルド時は native 向け依存ライブラリ (LLVM等) をダウンロードするため、少し時間がかかります。

hello_kotlinnative.png

マルチプラットフォームビルド

ローカルマシンのプラットフォームでビルドするためにすべきことを確認するために、まずは build.gradle.kts を読み解いていきます。

基本設定

build.gradle.kts
plugins {
    kotlin("multiplatform") version "1.4.20"
}

group = "me.lasta"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

Gralde プラグイン、アーティファクトのグループ名、バージョン、利用する外部ライブラリのリポジトリを指定しています。
ここまでは Kotlin/Native に限った話ではないため、割愛します。

ビルドターゲット の自動選択

build.gradle.kts
kotlin {
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }
}

kotlin 関数ブロックの前半では、まずビルドするプラットフォームを自動取得しています。
ホスト OS が Mac OS X であれば macosX64 用のバイナリが、 Linux であれば linuxX64 用のバイナリが生成されます。

Mac OS X 上で macosX64 用のバイナリと linuxX64 用のバイナリの両方を作成することも可能ですが、その場合はピュアな Kotlin およびマルチプラットフォーム対応している Kotlin ライブラリしか利用することができません。

次回以降でプラットフォーム依存のライブラリを利用する予定があるため、複数ターゲットビルドの解説は割愛します。

ソースセット

build.gradle.kts
kotlin {
    sourceSets {
        val nativeMain by getting
        val nativeTest by getting
    }
}

src 配下のどのディレクトリが main および test コードであるかを指定します。
Kotlin/Native 自体は複数プラットフォーム向けの実装を1プロジェクトで管理することができますが、今回は Amazon Linux 2 上で動作すれば十分であるため、デフォルトのままで問題ありません。

エントリポイント

build.gradle.kts
kotlin {
    nativeTarget.apply {
        binaries {
            executable {
                entryPoint = "main"
            }
        }
    }
}

entryPoint には、エントリポイントとなる main 関数のパッケージと関数名を指定します。

パッケージを作成した場合は、下記のようになります。

src/nativeMain/kotlin/me/lasta/entrypoint/main.kt
package me.lasta.entrypoint

fun main() {
    println("Hello, Kotlin/Native! in me.lasta.entrypoint")
}
build.gradle.kts
kotlin {
    nativeTarget.apply {
        binaries {
            executable {
                // パッケージ + 関数名
                entryPoint = "me.lasta.entrypoint.main"
            }
        }
    }
}

なお、複数のバイナリを一度にビルドすることもできます。

このとき、生成されるバイナリを一意に定める必要があるため、 executable に引数に任意の値 (ここではパッケージ名) を指定する必要があります。

build.gradle.kts
kotlin {
    nativeTarget.apply {
        binaries {
            executable("root") {
                entryPoint = "main"
            }
            executable("me.lasta.entrypoint") {
                // パッケージ + 関数名
                entryPoint = "me.lasta.entrypoint.main"
            }
        }
    }
}

この状態で ./gradlew build を実行すると、下記ようにバイナリが生成されます。

$ find build -name '*.kexe' | grep -v 'dSYM'
build/bin/native/rootDebugExecutable/root.kexe
build/bin/native/me.lasta.entrypointDebugExecutable/me.lasta.entrypoint.kexe
build/bin/native/rootReleaseExecutable/root.kexe
build/bin/native/me.lasta.entrypointReleaseExecutable/me.lasta.entrypoint.kexe

すべて実行可能ファイルです。

for kexe in $(find build -name '*.kexe' | grep -v 'dSYM'); do echo "Run: ${kexe}"; "./${kexe}"; done
Run: build/bin/native/rootDebugExecutable/root.kexe
Hello, Kotlin/Native!
Run: build/bin/native/me.lasta.entrypointDebugExecutable/me.lasta.entrypoint.kexe
Hello, Kotlin/Native! in me.lasta.entrypoint
Run: build/bin/native/rootReleaseExecutable/root.kexe
Hello, Kotlin/Native!
Run: build/bin/native/me.lasta.entrypointReleaseExecutable/me.lasta.entrypoint.kexe
Hello, Kotlin/Native! in me.lasta.entrypoint

Amazon Linux 2 上で Hello, Kotlin/Native!

Amazon Linux 2 上で動作させる準備を進めていきます。

ビルド環境の準備

Amazon Linux 2 上で Gradle build するための Docker image を作成します。

Dockerfile
FROM amazonlinux:2
RUN yum -y install tar gcc gcc-c++ make ncurses-compat-libs
RUN amazon-linux-extras enable corretto8 # ※
RUN yum clean metadata
# for gradle
RUN yum -y install java-1.8.0-amazon-corretto-devel # ※
RUN yum -y install install which zip unzip
RUN curl -s http://get.sdkman.io | bash && \
    bash ${HOME}/.sdkman/bin/sdkman-init.sh && \
    source ${HOME}/.bashrc && \
    sdk install gradle

※ IntelliJ IDEA でプロジェクトを作成する際に指定した JDK バージョンと Amazon Corretto のバージョンを合わせてください

上記の Dockerfile を作成したら、下記コマンドで Docker image を作成します。

docker build -t gradle-on-amazonlinux2:1.0 .

下記の通り無事イメージが作成されれば OK です。

$ docker image ls
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
gradle-on-amazonlinux2   1.0       3fe2426fd527   4 minutes ago   1.2GB
amazonlinux              2         ba2cc467a2bc   4 months ago    163MB

Gradle build 時に 137 エラー (OOME) が発生した場合

Docker for Mac はイメージが動作するためのメモリ領域をデフォルトで 2GB に制限していますが、 Kotlin/Native のコンパイルはそれよりも多くのメモリを消費することがあります。
もし Gradle build 時に 137 エラーを吐いた場合は、 Out of memory error (OOME) が発生している可能性があります。
下図のように設定変更することで、より多くのメモリを確保可能になります。

allocate-more-memory.png

Amazon Linux 2 上でビルド

# イメージの起動 (初回のみ)
docker run --memory=3g -v "$(pwd)":/root/work -itd gradle-on-amazonlinux2:1.0
# ビルド
docker exec -it $(docker ps | grep 'gradle-on-amazonlinux' | awk '{print $1}') /root/work/gradlew -p /root/work/ clean build

docker run でビルドまで行うことも可能ですが、下記コマンドではビルドのたびに LLVM をはじめとした Gradle プラグインおよび依存ライブラリの取得を行うため、非常に時間がかかるのでおすすめしません。

毎回時間がかかるので非推奨
docker run --memory=3g -v "$(pwd)":/root/work -t gradle-on-amazonlinux2:1.0 /root/work/gradlew -p /root/work/ clean build

BUILD SUCCESSFUL と出力されれば成功です。

Amazon Linux 2 上で動作確認

$ docker exec -it $(docker ps | grep 'gradle-on-amazonlinux' | awk '{print $1}') /root/work/build/bin/native/me.lasta.entrypointReleaseExecutable/me.lasta.entrypoint.kexe
Hello, Kotlin/Native! in me.lasta.entrypoint
$ docker exec -it $(docker ps | grep 'gradle-on-amazonlinux' | awk '{print $1}') /root/work/build/bin/native/rootReleaseExecutable/root.kexe
Hello, Kotlin/Native!

無事、ローカル実行時と同じ結果が出力されました。

おわりに

Kotlin/Native で実装したコードをローカルマシン上と Amazon Linux 2 上で実行できました。

次回は本シリーズの本題である、 Amazon Lambda (SAM local) 上で Kotlin/Native で書いたコードを実行します。

明日は fisherman08 さんの KotlinのSealed Classは良いよ、という話 です。

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
What you can do with signing up
0