AWS Lambda + カスタムランタイムでbash実行

## 概要

2020年12月にLambdaのコンテナ実行に対応（[リンク](https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-container-image-support/)）しているが、任意のプログラムを実行するために Lambda ランタイム APIに準拠して実装する必要がある。
（カスタムランタイムは2020年8月にAmazon Linux2に対応（[リンク](https://aws.amazon.com/jp/about-aws/whats-new/2020/08/aws-lambda-supports-custom-runtimes-amazon-linux-2/)））

このページでは、[公式チュートリアル](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html)にあるbashをコンテナ経由で実行する。

既存のzipファイル化する方法とさほど変わらないがECRにdockerイメージを置いとくだけなのでデプロイが若干楽になる気がする。

以下の作業を行う。

1. カスタムランタイムのためファイル用意
2. Dockerfile作成
3. ECRにdockerイメージをpush
4. pushしたdockerイメージを使ったLambda関数を作成
5. イメージの更新

CDKも対応済みだったので試してみた。

## 手動による作業
### カスタムランタイムのためファイル用意

適当なディレクトリに `bootstrap` と `function.sh` を作成。
両方とも[公式チュートリアル](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-walkthrough.html)と同じコードを使用。このコードでは受け渡したEventをそのまま返している。

コメントにあるようにLambdaからのイベントデータは `function.sh` では、ShellScriptの引数として渡されるので、もしbash以外の環境で実行するならコマンドライン引数やファイルに一時保存するなどして引き渡せそう。

また、結果はRESPONSEに代入しcurlでAWS Lambdaに渡している。

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

set -euo pipefail

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event. The HTTP request will block until one is received
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")

  # Extract request ID by scraping response headers received above
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Run the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

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

```bash:function.sh
function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Echoing request: '$EVENT_DATA'"

  echo $RESPONSE
}
```


### Dockerfile作成
[DockerHubのリポジトリ](https://hub.docker.com/r/amazon/aws-lambda-provided) のUsageを参考に作成。以下ではDockfileで実行権限をつけているが、最初から実行権限をつけたファイルをCOPYすればいいと思う。

```dockerfile:Dockerfile

FROM public.ecr.aws/lambda/provided:al2

COPY bootstrap ${LAMBDA_RUNTIME_DIR}
COPY function.sh ${LAMBDA_TASK_ROOT}

RUN chmod +x ${LAMBDA_RUNTIME_DIR}/bootstrap \
&& chmod +x ${LAMBDA_TASK_ROOT}/*.sh

CMD [ "function.handler" ]
```

### ECRにdockerイメージをpush

ECRレポジトリを作り、「プッシュコマンドの表示」の通りビルドしてプッシュする。

![ecr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/d9c1cd20-fac6-a593-9d5b-1c4efefa66af.png)

### pushしたdockerイメージを使ったLambda関数を作成


コンテナイメージURIを指定して、Lambdaを作成する。
![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/20ea6788-7d99-f79e-6177-92cb69055dfa.png)

テストデータを作成する。
![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/ef19c0b2-7e2f-ddd4-a7b0-bb2d00685c15.png)

テストデータを実行する。Eventで渡したデータがそのまま返ってきていることを確認する。
![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/e29afcb0-3709-a96d-8d63-edfbfba354dd.png)

### イメージの更新

Lambdaのページで「新しいイメージをデプロイ」をクリックすれば最新のイメージをpullして反映してくれた。

![Untitled (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/581336b0-b455-3dd4-026e-f65e82b78b1f.png)

## CDKによる作業
ドキュメントを見る限りすでに使えそうだったので試してみる。

- [aws-lambda module · AWS CDK](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html#docker-images)

先程の`Dockerfile`、`bootstrap`、`function.sh` を cdkプロジェクト下の `images` ディレクトリに移した。
stackを以下のように定義した（Pythonを使用）。

```python:aws_lambda_container_test_stack
from aws_cdk import (
    core,
    aws_lambda,
)

class AwsLambdaContainerTestStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Lambdaを作成
        aws_lambda.DockerImageFunction(
            self,
            "AssetFunction",
            code=aws_lambda.DockerImageCode.from_image_asset("./images")
        )
```

勝手にビルドしてECRリポジトリを作ってプッシュしてLambda関数を作ってくれた。
ECRを別に作成して指定することも可能のようだった（ドキュメント参照）。