Edited at

日本で 128 番目くらいに Ruby で AWS Lambda を試したメモ


これは

初老丸アドベントカレンダー 2018 の 8 日目の記事になる予定です.

https://qiita.com/advent-calendar/2018/syoroumaru


加藤さん, 事件です.

ずっと待たれていたはず, もはや諦めかけていたかもしれない AWS Lambda のランタイムに Ruby が追加されたとの一報が我々の元に入りました.

https://aws.amazon.com/jp/blogs/compute/announcing-ruby-support-for-aws-lambda/

おお〜. なんちゃって Rubist の私もこれまで作ってきた Ruby スクリプトを Lambda で実行出来るかもしれないという興奮が今も続いております.


ということで

早速, 上記の URL に掲載されているサンプルを参考に簡単な Ruby スクリプトを Lambda 上で動かしてみたいと思います.


検証環境

以下のような環境で動作確認を取ります.

$ ruby --version

ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
$ python --version
Python 3.7.0
$ sam --version
SAM CLI, version 0.8.0
$ aws --version
aws-cli/1.16.66 Python/3.7.0 Darwin/17.7.0 botocore/1.12.56

初めて sam を使ってみましたが, とても簡単に Lambda ファンクションのデプロイまで出来ました. Serverless framework と比較すると機能不足は否めませんが, AWS 純正なので新サービスへの対応も早かったりするのかなということで, もう少し使い込んでみようと思います.


まずは, サンプルイベントを生成する

とりあえず, ローカルで Ruby で書いた関数を動かしてみたいので, Lambda をローカルで実行する際に利用するサンプルイベントを生成します.

$ sam local generate-event s3 put --bucket=foo --key=bar > event_file.json

これを実行すると, 以下のような JSON イベントが生成されます.

$ cat event_file.json

{
"Records": [
{
"eventVersion": "2.0",
"eventSource": "aws:s3",
"awsRegion": "us-east-1",
"eventTime": "1970-01-01T00:00:00.000Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"responseElements": {
"x-amz-request-id": "EXAMPLE123456789",
"x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "foo",
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::foo"
},
"object": {
"key": "bar",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}

今回は, この JSON イベントから S3 のオブジェクトキーを抜き出す関数を Ruby で書いてみます.


祝・Ruby で書く Lambda ファンクション

す, すいません. とりあえず, 雑に書きました.

def hello(event:, context:)

key = event["Records"][0]["s3"]["object"]["key"]
key
end

なんのひねりも, エラー処理もない, 自分の Ruby スキルのレベルが伺えるスクリプトです.


sam に必要な template.yaml

template.yaml (.yml ではない) を以下のように書きました.

AWSTemplateFormatVersion: '2010-09-09'

Transform: AWS::Serverless-2016-10-31
Description: 'serverless ruby sample.'

Resources:
ServerlessRubySample:
Type: AWS::Serverless::Function
Properties:
Handler: sample.hello
Runtime: ruby2.5

Outputs:
ServerlessRubySample:
Description: Serverless Ruby Sample Lambda Function ARN
Value:
Fn::GetAtt:
- ServerlessRubySample
- Arn

なんとなく CloudFormation のテンプレートっぽい佇まいです. Runtimeruby2.5 が眩しいです.


まずはローカルでテスト

sam にもローカルで Lambda ファンクションを実行出来る local invoke というサブコマンドが用意されていますので, これを利用することで簡単にローカルで実行することが可能です.

$ sam local invoke ServerlessRubySample -e event_file.json

2018-11-30 08:33:13 Found credentials in shared credentials file: ~/.aws/credentials
2018-11-30 08:33:14 Invoking sample.hello (ruby2.5)

Fetching lambci/lambda:ruby2.5 Docker container image......
2018-11-30 08:33:17 Mounting /path/to/serverless-ruby-sample as /var/task:ro inside runtime container
START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST
END RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72
REPORT RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Duration: 5.64 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 18 MB
"bar"

"bar" が出力されることを確認出来ます. あっという間にローカル環境で Ruby で書いた Lambda ファンクションが動いてしまいました.


あとはデプロイ

引き続き, デプロイです. sam では CloudFormation のテンプレートを書き出して, そのテンプレートを使って Create Stack するような感じでデプロイするようです. もしかしたら, Serverless Framework も同じような挙動なのかもしれません. 以下のように 2 ステップでデプロイしました.

# packaged-template.yaml を生成

sam package --template-file template.yaml \
--output-template-file packaged-template.yaml \
--s3-bucket oreno-sam-bucket

# packaged-template.yaml を利用してデプロイ
sam deploy --template-file packaged-template.yaml \
--stack-name ServerlessRubySample \
--capabilities CAPABILITY_IAM

以下のように出力されました.

# packaged-template.yaml を生成

$ sam package --template-file template.yaml \
> --output-template-file packaged-template.yaml \
> --s3-bucket oreno-sam-bucket
Uploading to b55e36e493168dc4d6627ee148c1ac97 1275 / 1275.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/kawahara/sandboxies/sam/serverless-ruby-sample/packaged-template.yaml --stack-name <YOUR STACK NAME>

# packaged-template.yaml を使って Create Stack しているのかな...
$ sam deploy --template-file packaged-template.yaml \
> --stack-name ServerlessRubySample \
--capabilities CAPABILITY_IAM> --capabilities CAPABILITY_IAM

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


動作確認

動作確認も Ruby で書きましょう. 以下のように aws-sdk-lambda を使ったコードです. 事前に aws-sdk-lambda をインストールしておきましょう.

require 'aws-sdk-lambda'

require 'json'

client = Aws::Lambda::Client.new(region: 'ap-northeast-1')

payload =<<"EOS"
{
"Records": [
{
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "foo",
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::foo"
},
"object": {
"key": "bar",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}
EOS

resp = client.invoke({
function_name: 'ServerlessRubySample-ServerlessRubySample-xxxxxxxxxxx',
invocation_type: 'RequestResponse',
log_type: 'None',
payload: payload
})

res = JSON.parse(resp.payload.string)
puts res

function_name だけは, マネジメントコンソールで確認しましょう. このスクリプトを適当に invoke.rb とか名前をつけて保存しておきます.

$ bundle exec ruby invoke.rb

以下のように出力されました.

$ bundle exe ruby invoke.rb

bar

おお〜, いい感じです. CloudWatch Logs にもログが記録されていることを確認しています.


以上

Ruby で Lambda ファンクションが書けるなんて, 今年の頭に誰が予想していたでしょうか (結構, 予想していたりしたらすいません). 本当に嬉しい限りです.

レッツ Ruby on Lambda〜〜.