サーバーレスアセンブリ🌴

# みんな大好きLambda
![AWS-Lambda_light-bg@4x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83183/797bf7ca-822c-0006-dcff-72eae43dd3f1.png)

今年の re:Invent では RDS Proxy という個人的神アップデートも発表されて、
ますますサーバーレスしやすくなってきましたね！
（Provisioned Concurrency もな！）

そんな Lambda ですが、現在サポートされているランタイムは以下です。

- Node.js
- Python
- Ruby
- Java
- Go
- .NET

もちろんこれ以外の言語でも使いたい人はたくさんいますよね。

そんなときはカスタムランタイムです。

# [カスタム AWS Lambda ランタイム](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html)

去年（2018）の re:Invent で発表された機能ですね。
[New for AWS Lambda – Use Any Programming Language and Share Common Components](https://aws.amazon.com/jp/blogs/aws/new-for-aws-lambda-use-any-programming-language-and-share-common-components/)

`bootstrap` という実行可能ファイルにパッケージできれば、
どんな言語でも Lambda 上で動かすことができます。

見た感じそんなに難しくなさそうだったので自分でも何か作ってみることにしました。

# カスタムランタイムの例
- [カスタムランタイムを使ってLambdaでAWSCLIを動かす](http://blog.serverworks.co.jp/tech/2019/04/25/lambda-awscli/)
- [AWS Lambda 新機能 Custom Runtime を作ってみた](https://www.m3tech.blog/entry/aws-lambda-custom-runtime)
- [AWS LambdaのCustom RuntimeでサーバーレスDartしてみた](https://qiita.com/qazx7412/items/8bda5d97614d272ed829)
- [AWS LambdaのCustom RuntimeでRustを実行してみた #reinvent](https://dev.classmethod.jp/cloud/aws/custom-runtime-rust/)

有名どころの言語はあらかた先人たちが試していて、
車輪の再発明するのもあれだな...と思っていたところ、

ふと、

「あれ、アセンブリない......」

ということに気づきました。

かわいそう。
作ってあげないと。

# アセンブる
まずソースコードから書きます。
シンプルに Hello world を。

```nasm:app.asm
section .data

message: db "早めのパブロン", 10
length: equ $ - message

section .text

global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, length
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall
```

同じものを Ruby で書くとこうです。

```rb:app.rb
puts "早めのパブロン"
```

🤷‍♂️

ざっくり解説すると、

まず1バイトの容量を確保して変数を初期化。
`10` は ASCII コードの改行を表す。

```nasm
message: db "早めのパブロン", 10
```

出力するサイズも明示する必要がある。
文字列長を計算しているのがここ。
$がその行の先頭アドレスを表しているので、
そこから文字列の先頭アドレスを引けば自動的に文字列長が求められるのだとか。


```nasm
length: equ $ - message
```

`rax` レジスタに格納している `1` は `write` システムコールの番号を表している。
システムコールの一覧は `ausyscall --dump` で見れる。
コマンドが入ってなかったら `audit` とか `auditd` とかインストールする。

```nasm
mov rax, 1
```

`rdi` レジスタの `1` は出力先で `stdout` を表す。

```nasm
mov rdi, 1
```

文字列と文字列長を指定して `syscall` で呼び出す。

```nasm
mov rsi, message
mov rdx, length
```

これは `exit 0` と等価。

```nasm
mov rax, 60
xor rdi, rdi
syscall
```




一つだけ気になったこととして、 32ビットのレジスタである `eax` にすると動きませんでした。
正確にいうと、 amazonlinux:2018.03 ベースの Docker コンテナ上では動くのに、
Lambda に載せるとなぜか SIGSEGV を投げてくる。
これは... :thinking: 

ソースコードができたのでこれをコンパイルして実行可能ファイルにします。
最終 Lambda に載せるので Amazon Linux ベースの Dockerを実行環境として使います。

```dockerfile:Dockerfile
FROM amazonlinux:2018.03

RUN yum update -y && yum install -y wget tar gzip gcc make perl

RUN cd /tmp \
    && wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz \
    && tar xvfz nasm-2.14.02.tar.gz \
    && cd nasm-2.14.02 \
    && ./configure --prefix=/usr/local/nasm/2_14_02 \
    && make \
    && make install \
    && ln -s /usr/local/nasm/2_14_02/bin/nasm /usr/local/bin/

ENV APP_HOME /usr/src/assemmbly_on_aws_lambda
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME

RUN nasm -f elf64 $APP_HOME/src/app.asm -o $APP_HOME/src/app.o
RUN ld $APP_HOME/src/app.o -o $APP_HOME/bin/app
```

アセンブラには NASM を使います。
yum でインストールできる。

`nasm` でオブジェクトファイルを作り、 `ld` でリンクして実行可能形式に変換します。

これで実行。

```
./bin/app
早めのパブロン
```

簡単ですね！！

# Bootstrap
Lambdaでカスタムランタイムを動かすには、 `bootstrap` というファイルを作成する必要があります。
難しいことは考えずに[チュートリアル](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html)のをコピペしてきてちょろっと修正します。

```sh:bootstrap
#!/bin/sh

set -euo pipefail

while true
do
  HEADERS="$(mktemp)"
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  EXEC="$LAMBDA_TASK_ROOT/$(echo "$_HANDLER" | cut -d . -f 1)"
  RESPONSE=$(echo "$EVENT_DATA" | $EXEC)
  
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done
```

`EVENT_DATA` が 引数に渡される `event` を取得してきている部分です。
カスタムランタイム用に、APIのエンドポイントが用意されています。

取得したイベントデータは、実行するプログラムに渡すのと、
最後に結果を返すエンドポイントのパスにリクエストIDを含める必要があるのでそれに使います。

`$LAMBDA_TASK_ROOT` は関数にアップロードしたファイルが置かれる場所です。
見たら `/var/task` でした。

`$_HANDLER` はハンドラのファイル名+関数名です。
デフォルトで lambda_function.lambda_handler になってるやつですね。

今回は実行可能ファイルを直接呼び出すため、 `bin/app` と設定しています。

他にもエラーハンドリングを実装しないといけないんですが、最低限これで動くので省略します。

最後に、ファイルに実行権限をつけます。
Lambda がこいつを呼び出せるように `chmod 755 bootstrap` します。

# 関数の作成
普段は CloudFormation 原理主義者ですが、
zipアップロードが面倒なのでマネジメントコンソールから作ります。

ランタイムには、「ユーザー独自のブートストラップを提供する」を選択します。
英語だと `provided` って言うらしいですね。

<img width="1310" alt=" 2019-12-21 23.52.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83183/aa73f6b2-92d6-5779-b4d7-0d6079e5eae7.png">

先ほどの `bootstrap` と実行可能ファイルを zip でポンして、
ハンドラに `bin/app` を指定します。

<img width="1263" alt=" 2019-12-21 23.56.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83183/76919b60-4546-5ba2-9b7d-763fd748ace4.png">

テストして動作確認。

<img width="1288" alt=" 2019-12-21 23.56.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83183/f5885c3d-1fad-28f3-3714-b4a3001db1e9.png">


# 終わり
ということで、 Lambda でアセンブリ動くよ。
サーバーレスアセンブリ:metal_tone2:
[Blue-Pix/nasm_on_aws_lambda](https://github.com/Blue-Pix/nasm_on_aws_lambda)