AWS Lambda の Ruby ランタイムを試す

 AWS re:Invent 2018 の Keynote の中で AWS Lambda や Serverless 関連のアップデートが色々と発表されましたが、その中に AWS Lambda で Ruby がサポートされたという発表がありました。

aws.amazon.com

 下記のように AWS のブログでチュートリアルも公開されているので、今回はとりあえず Ruby で Lambda を動かしてみます。

aws.amazon.com

 現在サポートされている Ruby のバージョンは 2.5 で、 AWS SDK for Ruby はデフォルトで使えるようになっているようです。

まずは Hello World

 とりあえずは Lambda Function を作成して実行してみます。関数の作成画面では下記画像のようにランタイムとして Ruby2.5 が選択できるようになっています。

f:id:akanuma-hiroaki:20181201133930p:plain

 関数名やロール名は任意に決めて下記のような内容で作成します。

f:id:akanuma-hiroaki:20181201134519p:plain

 デフォルトで生成されるコードは下記のような内容です。

require 'json'

def lambda_handler(event:, context:)
    # TODO implement
    { statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end

 テストイベントを下記のように空のリクエストで設定します。

f:id:akanuma-hiroaki:20181201135128p:plain

 正しく実行できていれば下記のように成功レスポンスが表示されます。 

f:id:akanuma-hiroaki:20181201135426p:plain

 ログは下記のような出力になります。

Response:
{
  "statusCode": 200,
  "body": "\"Hello from Lambda!\""
}

Request ID:
"04397307-f507-11e8-abba-97121f20de35"

Function Logs:
START RequestId: 04397307-f507-11e8-abba-97121f20de35 Version: $LATEST
END RequestId: 04397307-f507-11e8-abba-97121f20de35
REPORT RequestId: 04397307-f507-11e8-abba-97121f20de35  Duration: 10.04 ms  Billed Duration: 100 ms     Memory Size: 128 MB Max Memory Used: 31 MB  

RubyGems を使う(Zipアップロード)

 Ruby を使うからにはやっぱり gem が使いたくなります。AWS ブログのチュートリアルでは SAM でアップロードする方法が紹介されていますが、先にシンプルに zip に固めたものをアップロードする方法を試してみます。

 まずは作業用ディレクトリを作ります。

$ mkdir lambda_gem_sample
$ cd lambda_gem_sample/

 gem は bundler を使って管理するので、下記の内容で Gemfile を作成します。今回はとりあえず gem が使えることが確認できれば良いので、 uuid という gem を使ってみます。

source 'https://rubygems.org'
gem 'uuid'

 bundler のインストール等については説明は割愛しますが、 インストールされているものとして下記コマンドを実行します。ローカルで使うだけであれば bundle install だけで使えますが、 zip に固めてアップロードするには gem のファイルもローカルにダウンロードしておく必要があるので、 bundle install --deployment も実行します。

$ bundle install
$ bundle install --deployment

 実際に実行する Ruby スクリプトは下記のような内容で lambda_function.rb というファイル名で作成します。生成した UUID をレスポンスの文字列に含めているだけのものになります。

require 'json'
require 'uuid'

def lambda_handler(event:, context:)
    uuid = UUID.new
    { statusCode: 200, body: JSON.generate("Generated UUID: #{uuid.generate}") }
end

 ここまでで必要なものは用意できたので、作業ディレクトリのファイルを zip に固めます。

$ zip -r lambda_gem_sample.zip ./*

 作成した zip ファイルを Lambda のコンソールからアップロードします。正しくアップロードされていれば下記のようにアップロードしたファイルがコンソールに表示されます。

f:id:akanuma-hiroaki:20181201141646p:plain

 空のテストイベントを作成してテストを実行すると、下記のように UUID の gem を使用したコードが実行され、生成された UUID が含まれた文字列がレスポンスとして返ってきます。

f:id:akanuma-hiroaki:20181201141943p:plain

RubyGems を使う(SAM)

 チュートリアルで紹介されている SAM を使う方法も試してみます。まずは作業ディレクトリを作成します。

$ mkdir hello_lambda_ruby
$ cd hello_lambda_ruby/

 Gemfile は下記のような内容で作成します。 aws-record は DynamoDB を操作するための gem です。

source 'https://rubygems.org'
gem 'aws-record', '~> 2'

 Gemfile を作成したら bundle install を実行します。

$ /Users/akanuma/.rbenv/shims/bundle install
$ /Users/akanuma/.rbenv/shims/bundle install --deployment

 Ruby のスクリプトは下記のような内容で hello_lambda_ruby_record.rb というファイル名で作成します。ちなみに AWS ブログに掲載されているコードをそのままコピペすると、 ENV[‘DDB_TABLE’] のシングルクォートが正しくない(アポストロフィーになってる?)ので実行時にエラーになります。

require 'aws-record'

class DemoTable
  include Aws::Record
  set_table_name ENV['DDB_TABLE']
  string_attr :id, hash_key: true
  string_attr :body
end

def put_item(event:,context:)
  body = event["body"]
  item = DemoTable.new(id: SecureRandom.uuid, body: body)
  item.save! # raise an exception if save fails
  item.to_h
end 

 AWS SAM はサーバレスアプリケーションの構成を管理するためのツールで、 Lambda アプリケーションの構造やセキュリティーポリシーの定義、AWSリソースの作成や管理を行うことができます。今回は DynamoDB を使用するのでそのための設定も含んでいます。設定ファイルは YAML で下記のような内容を template.yaml として作成します。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'sample ruby application'

Resources:
  HelloLambdaRubyRecordFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: hello_lambda_ruby_record.put_item
      Runtime: ruby2.5
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref RubyExampleDDBTable 
      Environment:
        Variables:
          DDB_TABLE: !Ref RubyExampleDDBTable

  RubyExampleDDBTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String

Outputs:
  HelloLambdaRubyRecordFunction:
    Description: Hello Lambda Ruby Record Lambda Function ARN
    Value:
      Fn::GetAtt:
      - HelloLambdaRubyRecordFunction
      - Arn

 AWS SAM のテンプレートは CloudFormation のコンソールか、 AWS CLI、 AWS SAM CLI のいずれかを使ってデプロイすることができます。このチュートリアルでは AWS SAM CLI でデプロイしていますので、まだインストールしていない場合はインストールしておきます。また、 s3 のバケット作成に AWS CLI も使用していますので、こちらもまだであればインストールしておきます。

 CLI の準備ができたら、アプリケーションのコードをホストするための s3 のバケットを作成します。

$ aws s3 mb s3://hello-lambda-ruby
make_bucket: hello-lambda-ruby

 AWS SAM CLI を使用して、アプリケーションをパッケージングします。これによって packaged-template.yaml というファイル名のテンプレートファイルが作成されます。

$ sam package --template-file template.yaml \
> --output-template-file packaged-template.yaml \
> --s3-bucket hello-lambda-ruby                                                                                                                                                                                                               
Uploading to 02055cc5fec807d137f97cd532f60cd5  993290 / 993290.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged-template.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/akanuma/workspace/hello_lambda_ruby/packaged-template.yaml --stack-name <YOUR STACK NAME>

 続いて AWS SAM CLI を使ってアプリケーションをデプロイします。この時にAWSユーザに CloudFormation の権限が足りていないと下記のようにエラーになります。

$ sam deploy --template-file packaged-template.yaml \
> --stack-name helloLambdaRubyRecord \
> --capabilities CAPABILITY_IAM

An error occurred (AccessDenied) when calling the CreateChangeSet operation: User: arn:aws:iam::365361468908:user/hiroaki.akanuma is not authorized to perform: cloudformation:CreateChangeSet on resource: arn:aws:cloudformation:ap-northeast-1:365361468908:stack/helloLambdaRubyRecord/*

 とりあえず CloudFormation のフルアクセス権限を追加したかったのですが、ポリシーを検索してもそれに当たるものが見つからなかったので、インラインポリシーで直接フルアクセス権限を追加してみました。

f:id:akanuma-hiroaki:20181201151456p:plain

 追加後に実行すると下記のようにデプロイが成功しました。

$ sam deploy --template-file packaged-template.yaml --stack-name HelloLambdaRubyRecord --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - HelloLambdaRubyRecord

 デプロイが終わると Lambda のアプリケーションコンソールに下記のようにアプリケーションが表示されます。

f:id:akanuma-hiroaki:20181201151636p:plain

 アプリケーションのリソースには Lambda Function と DynamoDB のテーブルが含まれています。

f:id:akanuma-hiroaki:20181201151828p:plain

 Lambda Function の方を選択して Lambda のコンソールを開き、下記のような内容のテストイベントでテストを実行します。

f:id:akanuma-hiroaki:20181201152026p:plain

 正しくデプロイされていてコードの内容に間違いがなければ、下記のように成功レスポンスが返ってきます。

f:id:akanuma-hiroaki:20181201152126p:plain

 DynamoDB のコンソールからテーブルの中身を見てみると、テストイベントで送信したリクエストの内容が保存されています。

f:id:akanuma-hiroaki:20181201152216p:plain

まとめ

 Lambda で Ruby が使えるようになったことで、 Ruby エンジニアにとってはかなりハードルが下がりましたね。bundler 管理で gem を使うこともできますので、色々と便利に使えそうです。ローカルでの開発と比べると、開発途中での gem の追加の容易さや、修正から実行確認の手返しの良さは劣るところになりそうですが、サーバレス環境での開発方法も最適な方法を探していきたいと思います。