任意の.ros
ファイルを変換して,AWS Lambda Custom Runtimeが受け取る.zip
に変換するツールができあがったので紹介と使い方のメモを兼ねてここに書いておく.
実際に動作するランタイムは,IPv6まわりのトラブルの影響でClozureCLを使っているが,今後余裕ができればSBCLでも動かせるようにしたい.
使い方
まずLambda化したいroswellスクリプトを用意する.任意のRoswellスクリプトを変換できると言ったもののmain
関数は以下のフォーマットに従う必要があるので,適宜修正する*1.
- 第1引数としてhandlerを,第2引数としてlambda eventを受け取る
- eventはJSOWNのオブジェクト
true
はt
に変換されるfalse
は:f
に変換されるnull
は:null
に変換される[]
はnil
に変換される
- JSOWNのオブジェクトを返す必要がある
ちなみにJSOWNとはCommon Lisp用のJSONシリアライザ・デシリアライザであり,S式とJSONとを相互変換できる.
上記の要件を満たすmain
関数は例えば次のような形になるだろう.
(defun main (handler event) `(:obj ("result" . "ok") ("handler" . ,handler) ("event" . ,event)))
このmain
関数は,ハンドラとイベントを含んだ簡易なオブジェクトを生成し,それを返す.JSOWNオブジェクトを返すので,返り値は:obj
で始まる連想リストになっている.
準備は整った.lambda-over-lambda.ros
を使って先程のスクリプトを変換してみよう.同様のスクリプトがリポジトリ内のyou-should-try-convert-this-script.ros
に保存されているので,ぜひ試してみてほしい.
% git clone git@github.com:windymelt/lambda-over-lambda.git % cd lambda-over-lambda/ % ./roswell/lambda-over-lambda.ros you-should-try-convert-this-script.ros => out.zip
このスクリプトをCustom Runtimeとして登録する.
アップロードに成功したなら,テストボタンを使ってテストイベントを発行してみよう.
Roswell scriptがLambdaに変換された.たぶんどんなスクリプトも動くはずなので,みんなで遊んでみてほしい.ネイティブコンパイルされているのでけっこう速いぞ.
あらすじ
先日こういった記事を書いていた.
動かすのには成功したので,これを押し進めていたところ,今回のスクリプトが完成した.
仕組み
Lambda-over-Lambda(以下lol)はいくつかのステップでソースの.ros
ファイルをCustom Runtimeに変換する.
ランタイムコードの合成
lolはターゲットコードにLambda用のラップコードを合成し,必要なライブラリとあわせてzipファイルに固める.
ふつうの関数は引数を受取り結果を返すものだが,AWS Lambda Custom RuntimeではこれをHTTPリクエストの送受信で実現している. Lambdaが用意している特定のエンドポイントにアクセス(GET)するとイベントを受け取ることができ,また別の特定のエンドポイントにアクセス(POST)するとLambdaファンクションとして結果を返すことができるのだ.
Lolはイベントの取り出しと結果の返却をラップするための追加のコードを生成し,ソースと合体させてターゲット.ros
ファイルを生成する.((今のところ適当にtemporary.ros
といった名前で生成しているが,そのうちちゃんとしたい.))ラップコードはmain
関数を乗っ取り,Lambdaからイベントを受け取るためのやりとりをすませると,オリジナルのmain
関数を呼び出し,得られた結果をまた変換してLambdaに送り返す.
これにより,ソースroswellスクリプトは一般的な入出力方法しか使わずにLambdaとやりとりできるようになる.現在は以下のようなI/Oをサポートしている.
- eventをJSOWNライブラリのobjectとしてargvで受け取り,JSOWNライブラリのobjectを
main
の返り値とする
またこのほかにも,以下のようなI/Oが考えられる.まだ未実装だがこれから必要に応じて実装していきたい.
- eventの特定フィールドを標準入力として受け取り,標準出力をキャプチャしてLambdaの返り値に(JSONの1フィールドにするなどして)変換する
大変だった点
限定的なIPv6サポート
LambdaのランタイムではIPv6が正しく動作しないようで,これに起因するソケットのエラー(ソケットをオープンしようとするとシステムコールに失敗する)に見舞われ,開発が停滞してしまった.
解決策としてはIPv6(正確にはソケットライブラリであるUSOCKETのIPv6サポート)をオフにするという手段をとった.詳細は以下のエントリにまとめている.
OpenSSLのバージョン違い
LambdaのランタイムはAmazon Linuxで動作しているようで,インストールされているOpenSSLのバージョンもこれに準じているようだ.そのため自分で適当にLispバイナリをビルドしてアップロードしても,(ビルド時の情報をもとに共有ライブラリを読み込むようなので)共有ライブラリの読み込みに失敗して死んでしまう.こういうことはスクリプト型言語ではあまり陥ることのない穴なので面喰らってしまった.
Common Lisp,とくにネイティブコンパイラを備えたSBCLとClozureCLのような処理系では,スクリプト言語のような動的な一面とコンパイル型言語のような静的な一面をあわせもった挙動になるので注意してやる必要がある.例えばグローバルスコープで(defparameter
などで)宣言したスペシャル変数は,実行時ではなくrosスクリプトのビルド時に評価されるので,当然実行時に評価されるものと思ってコーディングすると痛い目を見ることになる.ファイルが一度読み込まれ,評価され,その状態がバイナリとして産み落され,あらためてmain
関数から実行される,というイメージを持つ必要がある.
ともかくOpenSSLの問題はDockerを使ってLambdaと互換な環境でバイナリを生成するという手段で乗りきった.amazonlinux:2017.03.1.20170812
というベースイメージでroswellをコンパイルし,処理系をインストールすることでなんとかAPIの互換性を保つことができた.OpenSSLうまく動かすのつらすぎる・・・
まあ動いてよかった
AWSという最新のインフラにもついていける体力がCommon Lispにもあることを示していきたい.COBOLに負けないぞ.
*1:必要が生じればさまざまなフォーマットに対応させるつもりだ