AWS LambdaのDocker Container ImageをPerlでやっていく

結構引っかかったのでメモ。

# Lambda用のPerl Container
コンテナ自体はshogoさんが動くものを置いていただいているのでそれをベースイメージとすればよい。感謝感謝。
自分で作る手順も書いてあるのでこんどやる。

https://shogo82148.github.io/blog/2021/01/02/perl-runtime-supports-docker-format/

手順としては、まず 上記のコンテナをFROMとして `Dockerfile` をつくりECRにビルドしてpush、そしてビルドしたECRのイメージをLambdaで使うという設定をすればよい。

# 実際にやってみる
だいたいコピペ。
ECRとかLambdaのIAMとかは適宜作ってください。

以下の `Dockerfile` を用意。

```dockerfile

FROM public.ecr.aws/w2s0h5h2/p5-aws-lambda:base-5.32-paws.al2
COPY handler.pl /var/task/
CMD [ "handler.handle" ]
```

以下の `handler.pl` を用意

```handler.pl
use utf8;
use warnings;
use strict;
$| = 1;

sub handle {
    my $payload = shift;
    print "log test from perl";
    warn "test";
    return +{"hello" => "lambda"};
}

1;
```

以下のようなビルドスクリプトがあるといい感じかもしれない。
ローカルでビルドしたimagesとsha256が同じやつがECRにいないかをチェックして
いなければECRにpushするということをやる。

```run.sh
APP=xxx
REPO=$(aws ecr describe-repositories --repository-names $APP --query "repositories[0].repositoryUri" --output text | perl -pe '$_ = (split "/")[0]')
DATE=$(date '+%Y%m%d_%H%M%S')

docker build . -t $APP
docker tag "$APP:latest" "$REPO/$APP:$DATE"

LOCAL_BUILD=$(docker inspect $APP | jq '.[0].RepoDigests[0] | split("@")[1]' -r)
REMOTE_BUILDS=$(aws ecr list-images --repository-name $APP | jq '.imageIds[].imageDigest' -r)

REMOTE_COUNT=$(echo "$REMOTE_BUILDS" | sort | uniq | wc -l)
ALL_COUNT=$(echo "$LOCAL_BUILD\n$REMOTE_BUILDS" | sort | uniq | wc -l)

if [ $ALL_COUNT = $REMOTE_COUNT ]; then
    echo "build is same. skipping..."
else
    echo "build is different. push to ecr..."
    aws ecr get-login-password | docker login --username AWS --password-stdin $REPO
    docker push "$REPO/$APP:$DATE"
fi

```

ゆくゆくはCIもやりましょう。

# はまったこと・注意すること
### 標準出力のflushをオンにする。
Perlにおいては `$| = 1` をやればよい。

自作のコンテナでlambdaを動かす場合は、標準出力がバッファリングされないように設定しないとCloudwatch Logsに出ないっぽい。rubyのコンテナを参考にしたところ、標準出力をflushにしていたので倣ったところ出るようになった。

https://hub.docker.com/r/amazon/aws-lambda-ruby

最初普通に `print` をやっても出力されず、`warn`をやったら出力されたのでピンときた。
もしかして自作コンテナだとログは自分で CloudWatch Logsの `PutLogEvents` を呼ばないとだめなのか？と思ったけどそうじゃなくてよかった。

なお、Lambda上だとSTDOUTとSTDERRが一緒くたに扱われる上に、バッファリングがオフにしないといけないので

```
print 123;
warn 456
```

のようなコードが

```
123456 at /var/task/handler.pl line 9.
```

と出力されるので一瞬びっくりするかもしれない。
loggerのライブラリを使うべきで素のwarnとかprintは使うべきではないっぽいですね。

### CloudWatchのログ出力名は関数名と同じ
たとえばLambdaの関数名を `moge` とした場合、
作成されるCloudWatch Logsのロググループ名は `/aws/lambda/moge` 固定。

serverlessみたいなフレームワークにまかせっきりにしてたことなので知りませんでしたね。
名前が固定されているからといって何もせずにログが出るということはなく、下記みたいな権限設定が必要なのも忘れずに。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": "logs:CreateLogStream",
            "Resource": "arn:aws:logs:ap-northeast-1:000000000000:log-group:/aws/lambda/moge:*"
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Action": "logs:PutLogEvents",
            "Resource": "arn:aws:logs:ap-northeast-1:000000000000:log-group:/aws/lambda/moge:*:*"
        }
    ]
}
```

# まとめ
しかし、`lambda docker` で検索するとローカルでのlambdaの開発をdockerでやるみたいな話がいっぱい出てきて、lambdaをDocker containerで動かす話のググラビリティが低くて困る。どう検索したらいいんでしょうね。

上記文を書いて思ったけど、全然前者と後者の区別がつかないねこれ。

LambdaのDocker Containerの開発自体は、さすがにserverlessみたいなフレームワークほど手軽ではないものの、いうほど大変でないのでとてもよいです。