AWS Lambda で C のバイナリを動かす

ありふれたネタですが、 Lambda のカスタムランタイムで C を動かしてみました。

# ディレクトリ構成
```
.
├── bin
│   ├── bootstrap
│   └── lambda_function
├── docker-compose.yml
├── Dockerfile
└── src
    ├── lambda_function.c
    └── Makefile
```
一連のコードは https://github.com/tsubasaogawa/lambda-with-c にあげています。

# 手順
## 概要
1. C のソースを書く
2. Lambda と同じ実行環境でソースをコンパイルする
3. bootstrap を修正する
4. Lambda にアップロードする

## 詳細

### C のソースを書く

普段どおりソースを書きます。今回は「標準入力された内容を出力する」だけの簡単なものを作ってみました。

```c:lambda_function.c
#include <stdio.h>

int main(void) {
  char buf[512];

  while(1) {
    /* Obtain from Stdin https://qiita.com/mpyw/items/aff12a6ff2c7726ed1d8 */
    if(scanf("%511[^\n]%*[^\n]", buf) != 1) {
      break;
    }
    /* Ignore linefeed */
    scanf("%*c");

    printf("%s\n", buf);
  }

  return 0;
}
```

```Makefile:Makefile
PROGRAM = lambda_function
CC = gcc
CFLAGS = -Wall
OBJS = $(PROGRAM).o

$(PROGRAM): $(OBJS)
	$(CC) $(CFLAGS) -o $(PROGRAM) $(OBJS)

$(OBJS): $(PROGRAM).c
	$(CC) -c $(PROGRAM).c

.PHONY: clean
clean:
	rm -f $(PROGRAM) $(OBJS)

.PHONY: clean_obj
clean_obj:
	rm -f $(OBJS)
```

### Lambda と同じ実行環境でソースをコンパイルする

Lambda で動かすので、Lambda と同じ環境でコンパイルする必要があります。
[Lambda 実行環境と利用できるライブラリ](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/current-supported-versions.html) によると Amazon Linux を使っている (2019/02 時点) ことがわかるため、Amazon Linux のコンテナ上でコンパイルすることにします。

```Dockerfile:Dockerfile
FROM amazonlinux:2
MAINTAINER tsubasaogawa

WORKDIR /usr/local/src/lambda-with-c

RUN set -x && yum install -y gcc make

# ADD ./bootstrap .
COPY ./src/lambda_function.c .
COPY ./src/Makefile .

RUN make && make clean_obj
```

なんとなく Amazon Linux 2 にしてしまいました。docker-compose は以下です。

```docker-compose.yml
version: '3'
services:
  lambda-with-c-compiler:
    build: .
    volumes:
      - ./bin:/var/tmp
    command: /bin/bash -c 'cp -r /usr/local/src/lambda-with-c/lambda_function /var/tmp'
```
コンテナビルド時に `make` で作られたバイナリを、マウントしたディレクトリにコピーしています。また、コンテナを実行するとホストの `bin/` 配下にバイナリが作られます。

```bash
$ docker-compose build
$ docker-compose up -d
$ ls -l bin/lambda_function
-rwxr-xr-x 1 root root 8232 Feb 11 12:49 bin/lambda_function
```

### bootstrap を修正する

カスタムランタイムを使用すると、 `bootstrap` というファイルが実行されます。bootstrap 経由でバイナリを実行するような形になります。
[チュートリアル](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html) では、シェルスクリプトで bootstrap が書かれています。これを一部 (というかほとんど) 流用します。

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

set -euo pipefail

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event
  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)

  # Execute the handler function from the script
  EXEC="$LAMBDA_TASK_ROOT/$(echo "$_HANDLER" | cut -d. -f1)"
  RESPONSE=$(echo "$EVENT_DATA" | $EXEC)

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done
```

### Lambda にアップロードする

成果物を zip にして Lambda へアップロードします。Role は予め適当なものを用意してください。

```bash:Lambdaへアップロード
$ cd bin
$ zip ../lambda_function.zip ./*
$ cd ..
$ ls -l lambda_function.zip 
-rw-rw-r-- 1 vagrant vagrant 3032 Feb 11 12:55 lambda_function.zip

$ aws lambda create-function --function-name lambda-with-c \
    --zip-file fileb://lambda_function.zip --handler lambda_function.handler \
    --runtime provided --role arn:aws:iam::***:role/role_name
```

試しに実行してみます。

```bash:実行
$ aws lambda invoke --function-name lambda-with-c \
    --invocation-type RequestResponse \
    --payload '{ "test1": "value1" }' \
    /tmp/lambda-with-c.log
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
```

```bash:実行結果
$ cat /tmp/lambda-with-c.log 
{
    "test1": "value1"
}
```
よさそうです。引数が得られたので、あとは自由にパースして使えそうです。

# おわりに
- Lambda のカスタムランタイムを使って、 C で作られたバイナリを動かしてみた
- Amazon Linux 上でコンパイルを行うと安心
- カスタムランタイム使用時は bootstrap というファイルがまず実行される
- bootstrap 経由でバイナリを実行する
- なお、C++ では便利なライブラリが公開されているのでこれを使うと幸せになれそう
  - https://github.com/awslabs/aws-lambda-cpp
- 参考記事
  - https://dev.classmethod.jp/cloud/aws/lambda-custom-runtimes-c/