AWS LambdaをRustで動かしてみる
とりあえずDevelopersIOさんの記事をほぼ写経するところから初めてみる。
なんか知らんけど、rustupがバージョンアップできなかった。(m1チップの問題っぽい?)
error: failed to install component: 'rust-std-wasm32-unknown-unknown', detected conflict: 'lib/rustlib/manifest-rust-std-wasm32-unknown-unknown'
info: checking for self-updates
stable-aarch64-apple-darwin update failed - rustc 1.56.1 (59eed8a2a 2021-11-01)
info: cleaning up downloads & tmp directories
ので仕方ないから一回アンインストールして
公式から入れ直した
今のバージョン
rustc 1.57.0 (f1edd0429 2021-11-29)
rustup 1.24.3 (ce5817a94 2021-05-31)
cargo 1.57.0 (b2e52d7ca 2021-10-21)
ちなみに本当はVSCodeのRemoteContainerで環境作りたいんだけど、以前にやはりm1チップがらみで断念した気がする。今はできるようになってるのかな。。。
とりあえずcargo new
する
cargo new rust-lambda-cdk
写経しようと思ったけど流石にクレートたちが古い気がする。特にLambdaのランタイム。
よって、これを入れることを試みてみる。
とりあえず、
[package]
name = "rust-lambda-cdk"
version = "0.1.0"
edition = "2021"
readme = "README.md"
license = "MIT"
[dependencies]
lambda_runtime = "0.4.1"
とりあえずRust部分の写経をしてみる。
DevelopersIOとaws-lambda-rust-runtimeのREADMEのあいのこ写経になってきた。
Cargo.toml
[package]
name = "rust-lambda-cdk"
version = "0.1.0"
edition = "2021"
readme = "README.md"
license = "MIT"
[lib]
name = "lib"
path = "src/lib.rs"
[[bin]]
name = "bootstrap"
path = "src/bin/bootstrap.rs"
[dependencies]
lambda_runtime = "0.4.1"
serde_json = "1.0.74"
tokio = "1.15.0"
src/lib.rs
use lambda_runtime::{Context, Error};
use serde_json::{json, Value};
pub async fn hello_world(event: Value, _: Context) -> Result<Value, Error> {
let first_name = event["firstName"].as_str().unwrap_or("world");
Ok(json!({"message": format!("Hello, {}!", first_name)}))
}
src/bin/bootstrap.rs
use lambda_runtime::{handler_fn, Error};
use ::lib::hello_world;
#[tokio::main]
async fn main() -> Result<(), Error>{
println!("execute bootstrap#main");
let runtime_handler = handler_fn(hello_world);
lambda_runtime::run(runtime_handler).await?;
Ok(())
}
改めてLambdaのカスタムランタイムの仕様を調べてみる。
なるほど、bootstrap
という名前の実行ファイルを叩いたときに初期化処理とイベントループが回ればいいのか。
おそらくlambda_runtime::run
は後者のイベントループを回してくれていると。
とりあえずmacからamazon linuxにクロスコンパイルできるように設定。これCIするときはどうするんだろ。。まあ、後で考えるか。
クロスコンパイラ?のインストール
brew install filosottile/musl-cross/musl-cross
意外と時間かかるので待機。
これ、クロスコンパイルしなくていいっていう意味ではAWS Cloud9とかGitHub CodeSpacesに開発環境作った方が捗るのかも知れない。
brew install
でエラー。内容見るとまたもやM1チップがらみっぽい。
ググったところ以下を発見。さすがクラメソさん。
上でエラー解決。さすが。
とりあえず素でビルドしてみる。
targetの追加
rustup target add x86_64-unknown-linux-musl
ビルド
cargo build --release --target x86_64-unknown-linux-musl
./target/x86_64-unknown-linux-musl/release
以下にbootstrap
を含めたいろいろなファイルができた。これをlambdaのカスタムランタイムに突っ込めば良いわけか。
ということでaws cdk
をセットアップする。
DevelopersIOではnpm init
でやっているが私は最近の推しツールであるprojen
を使う。
projen new awscdk-app-ts
このままだと、元々cargo new
で作られていた.gitignore
が上書きされてtarget/
以下がgitの管理対象になってしまうので、
./.projenrc.js
const { awscdk } = require('projen');
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'rust-lambda-cdk',
gitignore: [
'target/'
]
// deps: [], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
// devDeps: [], /* Build dependencies for this module. */
// packageName: undefined, /* The "name" in package.json. */
});
project.synth();
.projenrc.js
を更新して、
npx projen
DevelopersIOの記事を参考に、先ほど手でやったRustのビルドをnpm script
でやるように編集。
./.projenrc.js
const { awscdk } = require('projen');
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'rust-lambda-cdk',
gitignore: [
'target/'
],
scripts: {
"build:rust": "npm run build:rust:clean && rustup target add x86_64-unknown-linux-musl && cargo build --release --target x86_64-unknown-linux-musl && mkdir -p ./target/cdk/release && cp ./target/x86_64-unknown-linux-musl/release/bootstrap ./target/cdk/release/bootstrap",
"build:rust:clean": "rm -r ./target/cdk/release || echo '[build:clean] No existing release found.'"
}
// deps: [], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
// devDeps: [], /* Build dependencies for this module. */
// packageName: undefined, /* The "name" in package.json. */
});
project.synth();
npx projen
npm run build:rust
target/cdk/release
にbootstrap
がコピーされたのでOKっぽい。
CDKの空定義をする。projen new
したときに既にsrc
ディレクトリがあったからなのか、projen
がmain.ts
を作ってくれないので自作する。
./src/main.ts
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class RustLambdaCdkStack extends Stack {
constructor(scope: Construct, id:string, props: StackProps = {}) {
super(scope, id, props);
}
}
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
const app = new App();
new RustLambdaCdkStack(app, 'rust-lamnda-cdk', { env: devEnv });
app.synth();
./test/main.test.ts
test('No test yet', () => {
expect(true).toBe(true);
});
ついでに、npm run build
したときに、先にRustをコンパイルするよう.projenrc.js
を設定する。
./.projenrc.js
const { awscdk } = require('projen');
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'rust-lambda-cdk',
gitignore: [
'target/',
],
scripts: {
'build:rust': 'npm run build:rust:clean && rustup target add x86_64-unknown-linux-musl && cargo build --release --target x86_64-unknown-linux-musl && mkdir -p ./target/cdk/release && cp ./target/x86_64-unknown-linux-musl/release/bootstrap ./target/cdk/release/bootstrap',
'build:rust:clean': "rm -r ./target/cdk/release || echo '[build:clean] No existing release found.'",
},
// deps: [], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
// devDeps: [], /* Build dependencies for this module. */
// packageName: undefined, /* The "name" in package.json. */
});
project.preCompileTask.exec('npx projen build:rust');
project.synth();
カスタムランタイムのLambdaをCDKで定義。
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Code, Function, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export class RustLambdaCdkStack extends Stack {
constructor(scope: Construct, id:string, props: StackProps = {}) {
super(scope, id, props);
new Function(this, 'rust-helloworld', {
runtime: Runtime.PROVIDED_AL2,
handler: 'rust-lambda-cdk',
code: Code.fromAsset(`${__dirname}/../target/cdk/release`),
tracing: Tracing.ACTIVE,
});
}
}
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
const app = new App();
new RustLambdaCdkStack(app, 'rust-lamnda-cdk', { env: devEnv });
app.synth();
ちなみに上を書くときに、handler
に何を入れるだろうと思ったが。。。以下を読む限りでは今回に関しては何入れても良いっぽい。
本来は、bootstrap
の中で環境変数_HANDLER
の中身を確認して、それに応じて実行する(Rustの)関数を切り分けたりするべきなのかな。
ということで、deploy
する。
npm run deploy
特に問題なくというか、普通にdeploy
できた。
マネージメントコンソールからテスト実行。速い・・・スクショは数回目の実行結果なのでホットスタートだが、それにしても速い。。記憶ベースだが、コールドスタート時もトータル10msくらいだったと思う。こんなHello Worldコードでもはっきり分かるくらいインタプリタ系の言語とは格が違う。