AWS Lambda の Custom Runtimes を利用して Crystal を実行してみる #reinvent
はい、どーも。モバイルアプリサービス部の吉田です。
この記事はAWS Lambda Custom Runtimes芸人 Advent Calendar 2018の25日目の記事です。
今回はCrystalをAWS Lambdaで動かしてみたいと思います。
Crystalとは?
Crystalという言語、あまり聞いたことがない人向けに軽く説明をしておきますと
- Ruby風の文法
- 型推論による、変数・メソッドの静的型付け
- 高速なネイティブコードを生成
といった特徴があります。型推論もですが、LLVMの高速なネイティブコードを生成することができるので、非常に高速なのが売りです。
CrystalでLambdaの実装をする
今回実装した内容はこちらです。
当初はイベントループをbootstrap内で回す形にしていたのですが、Lambdaを短時間のインターバルで呼び出すと、なぜかエラーになってしまったので、実行ファイル内でイベントループをする形にしています。 また、最小限の実装しかしてないので、あまりエラーハンドリングとかは真面目にやってませんので悪しからず・・・
require "http/client" def getContext endpoint = "http://#{ENV["AWS_LAMBDA_RUNTIME_API"]}/2018-06-01/runtime/invocation/next" res = HTTP::Client.get(endpoint) if res.status_code != 200 raise "Unexpected response when invoking: #{res.status_code}" end request_id = res.headers["Lambda-Runtime-Aws-Request-Id"] context = res.body.lines[0] [request_id, context] end def sendResponse(request_id, context) endpoint = "http://#{ENV["AWS_LAMBDA_RUNTIME_API"]}/2018-06-01/runtime/invocation/#{request_id}/response" HTTP::Client.post(endpoint, body: context, headers: nil) end while true request_id, context = getContext sendResponse(request_id, context) end
getContext
メソッドではCrystalの組み込みHTTPクライアントを使って AWS_LAMBDA_RUNTIME_API
から request_id
と context
を取り出しています。
sendResponse
メソッドでは、Lambda終了時のエンドポイントに対して、POSTをおこなっています。
そして最後のループで、これらの処理を回しています。
Lambdaのエントリポイントになるbootstrapは、単純に実行可能ファイルを呼び出すだけです。
#!/bin/sh set -euo pipefail EXEC="$LAMBDA_TASK_ROOT/$_HANDLER" $EXEC
ビルド環境を作る
次にビルド環境を作ります。
まずビルド用のシェルスクリプト build.sh
を用意します。
#!/bin/sh crystal build main.cr --static --release --no-debug
ここで重要なのが --static
オプションです。Crystalには依存関係も含め、すべてをまとめてコンパイルする機能があるので、これを利用してコンパイルします。
あえてstaticオプションを使わず、AWS Lambdaのレイヤー機能を使うこともできそうですね。
次はDockerfileですが、公式のdockerイメージがあるので、これを利用します。
FROM crystallang/crystal:latest RUN mkdir /lambda WORKDIR /lambda COPY main.cr . COPY build.sh . RUN crystal version
また、利用しやすいようにdocker-compose.ymlも以下のように準備します。
version: "3" services: crystal: build: context: . image: crystal-on-lambda command: ./build.sh volumes: - .:/lambda
ここまでできたらあとは
$ docker-compose up
でビルドします。ローカルにmainという実行可能ファイルができますので、これとbootstrapをzipにまとめます。
$ zip lambda.zip bootstrap main
あとはこれをAWSのコンソールからアップするだけで、Lambdaを実行できます。
実に簡単ですね!