y2q_actionman’s ゴミクズチラ裏

内向きのメモ書きを置いてます

AWS Lambda の Custom Runtime として Common Lisp (sbcl) を使う

この文章は、 lisp Advent Calendar 2018 - Qiita の 12/6 分の記事として書かれました。

表題の通りです。色々書いたり、気の利いたことをやろうと思ったのですが、今日 (12/6) は疲れた&時間がないのでこんな感じで・・


参考にしました

Custom Runtime を Lisp で書く。

bootstrap という名前の実行可能ファイルを作り、 環境変数を読んで初期化 し、 所定の URI からリクエストを読み、所定の URI に吐くということを繰り返す 必要があります。

せっかく単一バイナリが吐ける sbcl を使うので、 Lisp で書いてしまいました。

環境変数を読んで初期化するのが bootstrap 関数、 ループに入ってリクエストを処理するのが main-loop になります。

変なことをしている find-handler については下で書きます。

Custom Runtime をビルドして登録

Docker でビルド環境を作る

Custom Runtime を AWS Lambda の環境でビルドします。 以下の Dockerfile を作りました。

やってることは単純です:

  1. SBCL を公式から拾って導入。 amazonlinux 環境の yum で拾えなかったので仕方なくSBCL公式から拾うことに。
  2. quicklisp を公式から拾って導入。
  3. quicklisp で bootstrap のビルドに絶対使うものを拾っておく。

最後の quicklisp ライブラリについては、 Docker のビルド時に拾う他に、 bootstrap.lisp のビルド時に拾う手もあるのですが、「これは絶対使うんだから」と混ぜてしまいました。

Docker build 時に拾うものは以下です。

以下のようにして test という名でビルドしました:

docker build -t test .

ビルドしてバイナリを作る

以下のようにビルド用スクリプトを書きました。

これもやってることは単純です:

  1. SBCLを起動し、 bootstrap.lisp の依存ファイルと、続けて bootstrap.lisp 自体をロード。
  2. save-lisp-and-die で単一バイナリにする。名前は bootstrap とし、 aws-bootstrap-test:bootstrap から動きなさいと指示。
  3. zip で固める

以下のように docker でこのスクリプトを起動し、 aws_lambda_bootstrap.zip を作成しました

docker run --rm -v `pwd`:/out test /out/build_bootstrap.sh

できた Custom Runtime を Layer として AWS Lambda に登録

できた zip を、 lisp-layer という名前にして layer として登録しました。

aws lambda publish-layer-version \
    --layer-name lisp-layer \
    --zip-file fileb://aws_lambda_bootstrap.zip

f:id:y2q_actionman:20181206230027p:plain

デバッグでめっちゃ更新したので、すでにバージョン5になってます

AWS Lambda 関数を作る

最低限のものを作る

上で作った layer を使う AWS Lambda 関数を作ればよいので、早速作ります。

今回の bootstrap.lisp には、 handler に default-handler と指定すると適当な返答をするという仕込みをしてあるので、それを使ってみます。 ただ、 AWS Lambda に渡すのに、最低限何か中身を持った zip が必要らしいので、ほぼ空のファイルを作り・・

以下のように zip を作りました。

zip empty.zip empty.lisp

これを empty-test という名で AWS Lambda 関数としてupします:

aws lambda create-function \
    --function-name empty-test \
    --zip-file fileb://empty.zip \
    --handler default-handler \
    --runtime provided \
    --role <ロール名> \
    --layers <作った lisp-layer の ARN>

AWS のコンソールでテストを作り:

f:id:y2q_actionman:20181206231021p:plain

実行。うまく動いているようです

f:id:y2q_actionman:20181206232815p:plain

Lisp コードを Load させる

せっかく Lisp を使っているので、動的にコードを load したいものです。そういう仕込みも bootstrap.lisp にしておいたので試してみます。

適当なハンドラを書きました。

これを zip に入れて

zip outer-handler.zip outer-handler.lisp

これを outer-handler-test という名で AWS Lambda 関数としてupします:

aws lambda create-function \
    --function-name outer-handler-test \
    --zip-file fileb://outer-handler.zip \
    --handler outer-handler.lisp#outer-handler \
    --runtime provided \
    --role <ロール名> \
    --layers <作った lisp-layer の ARN>

--handler outer-handler.lisp#outer-handler の箇所に仕込みがあります。 今回作った bootstrap は _HANDLER 環境変数経由でこの値を読んで、 outer-handler.lisp を Load し、 cl-user::outer-handler シンボルを探して funcall しようとします。上で少し触れた find-handler 関数はこれをやっています。

コンソールのテストから呼んでみます

f:id:y2q_actionman:20181206232301p:plain

呼べたようです。

既知の問題

時々 poll(2) に失敗したよ エラーが出ます。なんなんでしょうこれ。

f:id:y2q_actionman:20181206232743p:plain

調べても未回答の stackoverflow しか出ず、謎です。

java.net.SocketException: Operation not permitted (select/poll failed) when adding Keen events asynchronously in AWS Lambda - Stack Overflow

成果物

github.com

感想

自宅 AWS 環境、自宅 Docker 環境を作るのに疲れました。そのために言葉がでないのでここは週末に書き直します。