Serverless Frameworkを使ってRacketのプログラムをAWS Lambdaにデプロイしてみた

Qiita を退会したときに消えてしまった記事を復元したものです。ただし完全な復元ではなく、Linux を GNU/Linux に置き換えています。

目的

RacketのプログラムをServerless FrameworkでAWS Lambdaにデプロイして実行したい。

背景

昨年の年末にAWSがCustom AWS Lambda Runtimesという機能を発表し、任意のプログラミング言語がLambdaで使用できるようになり、マイナーなプログラミング言語のユーザの間で話題になりました。

私にとってはプログラミング言語RacketをAWS Lambdaで利用できるようになったことが喜ばしく、業務でしか関わることのなかったAWS Lambdaを趣味で利用しようという気になるほどインパクトの大きい発表でした。

しかし、待っていたところでRacketを本気で使用するためのLambda Runtimeが登場することはなさそうだったので新しく作る必要がありました。

先行事例

事例1

既にRacketのプログラムのLambda Runtimeを作成する方法について紹介されています。
https://qiita.com/ojima-h/items/9f42f90c2e63a80584d7

しかし、この記事ではあくまでもRacketをSchemeとして使用することが前提となっているために、本気でRacketをAWS Lambdaで使いたい場合には不足していました。

具体的には以下の問題が存在します。

  • #langによって言語を選択できない
    • #lang racketのデフォルトのloadによって読み込んでいるため
  • カスタムランタイムにないpackageを使用することができない
    • たとえばawsのような package を使うことができない

いずれもSchemeのCustom AWS Lambda Runtimeとしては問題のないことですが、Programming-Language Programming LanguageであるRacketにとっては致命的な問題です。この問題を解決するために、新しくRacketをLambdaへデプロイする方法を確立する必要があります。

事例2

実は、AWSがCustom AWS Lambda Runtimeに対応する前から任意のプログラミング言語が実行可能でした。


https://www.lambrospetrou.com/articles/aws-lambda-meets-racket/

こちらの方法は、別の言語とRacketのプログラムをコンパイルした結果のバイナリを一緒にデプロイし、初期化時にRacketのプログラムを起動してLambda関数が呼ばれる度に、引数を標準入力へ流し込むことによってLambda関数でRacketのプログラムを実行しています。

しかし、この方法はCustom AWS Lambda Runtimeが登場する前では大変有益でしたが、現在はこのような遠回りをする必要はありません。また、AWS Lambdaでは標準出力がログ出力に割り当てられていますが、この方法では標準出力が結果の返却に使われてしまうという点で都合がよくありません。

よってこの方法は今となっては微妙なのでやはり新しく作る必要がありました。

Serverless Frameworkについて

Serverless Framework自分でサーバの管理はしたくなくて、冗長性の確保やオートスケーリングなどのインフラ側を誰かに代わりにやって欲しいと思う人にはうってつけのツールです。詳しくはリンク先を参照してください。

今回の記事では、AWSのLambda関数とLambda Runtimeのデプロイを行うためにServerless Frmeworkを使用することにしました。

やったこと

Severless Frameworkを使ってRacketのプログラムをAWS Lambdaにデプロイするために行ったことを紹介します。

aws-bootstrap-runtimeの作成

aws-bootstrap-runtimeとは、Custom AWS Lambda Runtimeの実装をLambda関数側に移すためのCusmtom AWS Lambda Runtimeです。

......というと意味不明ですが、このCustom AWS Lambda Runtimeのやることは単純で、ただLambda関数としてデプロイされたbootstrapという名前の実行ファイルを実行するだけです。

こちらのチュートリアルで説明されている通り、Custom AWS Lambda Runtimeとはbootstrapという名前の実行ファイルを用意して、そこで初期化処理やLambda関数が呼ばれた際の処理などを行うことによって実装されるものです。

私も最初は真面目にbootstrapファイルにRacketのLambda Runtimeを一生懸命実装していたのですが、Minimal Racketでは不十分なのでFullのRacketをインストールしようとすると、Racketに同封された実際には不要なライブラリとドキュメントによって容量が膨れ上がりLambda Runtimeに置けるデータの容量の上限を超えてしまったり、実装に失敗するたびにいちいちバージョンを上がって永久に戻せなかったり、RacketのpackageをLambda関数側でデプロイするのが困難だったりしました。

そこで、Custom AWS Lambda Runtimeの実装はRacketのライブラリとして提供し、Racketのプログラムをコンパイルした結果をbootstrapという名前にして、aws-bootstrap-runtimeにデプロイすることによって解決しました。

ちなみに、このbootstrap runtimeはCustom AWS Lambda Runtimeが使用できる全てのリージョン対してデプロイされていて、全てのアカウントからアクセス可能な状態になっています。

aws-lambda-serverless

RacketのCustom AWS Lambda Runtimeの構築が面倒くさいという問題は解決したのですが、今度はLambda関数側のデプロイが大変になりました。 Custom AWS Lambda Runtimeの実装を含みつつ、Lambda関数に対応したRacketの手続きが呼び出されるようにしないといけません。 この問題を解決するのがaws-lambda-serverlessです。

これはServerless Frameworkを使用することを前提としたライブラリで、Lambda関数としてRacketの手続きを簡単にデプロイすることができます。

実例

では試しに、Hello Worldを返すようなLambda関数をこのライブラリを使って作ってみましょう。 しかし、ここで残念なお知らせがあり、今のところ64bitのGNU/Linux環境からしかデプロイできません(さらにAmazon GNU/Linux 2からしか試していません)。 これ以外の環境で作業している人はVMやコンテナ技術などの仮想環境を使用するか、GNU/LinuxなどのOSがインストールされたマシンを用意しましょう

また、RacketServerless Frameworkがインストールされていることが前提です。 もしもインストールしていない場合は、必ずインストールしましょう。

aws-lambda-serverlessをインストールする

raco pkg install https://github.com/tojoqk/aws-lambda-serverless.git

Lambda関数として呼び出すRacketの手続きを実装する

#lang racket
(require json)

(define (hello jsexpr)
  (jsexpr->string
   (hash 'body "Hello, world!"
         'input jsexpr)))
(provide hello)

これを、 handler.rkt という名前で保存します。

serverless.ymlを準備する(ただし、先頭に#lang aws-lambda-serverlessと記述する必要があります)

#lang aws-lambda-serverless

service: test

provider:
  name: aws
  runtime: provided
  memorySize: 128
  timeout: 1
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    layers:
      - arn:aws:lambda:ap-northeast-1:488514468674:layer:bootstrap:2

serverless.ymlをコンパイルしてbootstrapという名前の実行可能なバイナリを作る

raco exe --orig-exe -o bootstrap serverless.yml

デプロイする

sls deploy

これでLambda関数がデプロイできたはずです。 Lambda関数を呼び出してHello, world!を含むJSONが返ってきたら成功です。

sls invoke -f hello

まとめ

以上によってRacketのプログラムを気軽にAWS Lambdaにデプロイできるようになりました。 RacketでAWS Lambdaを使ってみたいなと思ったら是非参考にしてみてください。