Lambda(Node.js)でRubyスクリプトを実行する

モバイルアプリサービス部の五十嵐です。

AWS Compute Blogにて、Lmabda(Node.js)でRuby, PHP, Goで書かれたスクリプトを実行する方法が紹介されました。

今回はこの記事で紹介されている中から、Rubyスクリプトの実行を試してみました。また、記事にはありませんがRubygemsをインストールして使う方法も確認しました。

手順

Rubyのスクリプトを実行するLambdaを作成する手順は以下のとおりです。

  • AMIからAmazonLinuxのインスタンスを起動し、インスタンス内でLambdaの実行環境を作る
  • Lambdaの実行環境と実行するスクリプトをパッケージにしてデプロイする

準備

AWS ConsoleよりEC2インスタンスを起動します。

  1. Amazon Linux AMI を選択します。 スクリーンショット 2016-12-12 13.52.54

  2. インスタンスタイプを選択します。ここでは記事の通り t2.large を選択します。 スクリーンショット 2016-12-12 13.53.08

  3. 作成し終えたら任意のキーペアを設定します。 スクリーンショット 2016-12-12 13.55.22

インスタンスが起動したら、グローバルIPアドレス(EIP)を確認し、SSHログインします。

$ chmod 400 private.pem
$ ssh -i private.pem ec2-user@[グローバルIPアドレス]

環境構築

SSHログインしたら、Rubyの実行環境をインスタンス内で構築します。

$ sudo yum update -y
(省略)
$ wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz .
(省略)
$ mkdir LambdaRuby
$ tar -xvf traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz -C LambdaRuby

Lambda in Rubyの作成

ここからはLambdaのパッケージを作成します。今回はserverless frameworkを使用します。

serverless frameworkをインストールしていない場合はインストールします。

$ npm install -g serverless

serverless frameworkのプロジェクトを作成します。

$ sls create -t aws-nodejs -p LambdaRuby
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/igarashi.ryosuke/temp/serverless/LambdaRuby"
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.3.0
-------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

$ cd LambdaRuby
$ ll
total 16
-rw-r--r-- 1 igarashi.ryosuke staff 466B 12 12 14:20 handler.js
-rw-r--r-- 1 igarashi.ryosuke staff 2.2K 12 12 14:20 serverless.yml

handler.jsを以下のように書き替えます。

'use strict';

const exec = require('child_process').exec;

module.exports.hello = (event, context, callback) => {
const child = exec('./lambdaRuby.rb ' + ''' + JSON.stringify(event) + ''', (result) => {
// Resolve with result of process
context.done(result);
});

// Log process stdout and stderr
child.stdout.on('data', console.log);
child.stderr.on('data', console.error);
};

続いて実行したいRubyスクリプトを作成します。今回は記事のサンプルにあるlambdaRuby.rbを使います。

#!./bin/ruby

require 'json'

# You can use this to check your Ruby version from within puts(RUBY_VERSION)

if ARGV.length > 0
puts JSON.parse( ARGV[0] ).length
else
puts "0"
end

実行権限も必要なので設定します。

$ chmod +x lambdaRuby.rb

ローカル環境で実行できることを確認します。

$ ruby lambdaRuby.rb '{ "we" : "love", "using" : "Lambda" }'
2

テスト用のパラメータファイル event.json を作成します。

{"key3": "value3","key2": "value2","key1": "value1"}

EC2上で作成した実行環境をダウンロードします。

$ scp -r -i ~/.ssh/private.pem ec2-user@[グローバルIPアドレス]:~/LambdaRuby/bin .
$ scp -r -i ~/.ssh/private.pem ec2-user@[グローバルIPアドレス]:~/LambdaRuby/bin.real .
$ scp -r -i ~/.ssh/private.pem ec2-user@[グローバルIPアドレス]:~/LambdaRuby/info .
$ scp -r -i ~/.ssh/private.pem ec2-user@[グローバルIPアドレス]:~/LambdaRuby/lib .
$ ll
total 32
drwxrwxr-x 7 igarashi.ryosuke staff 238B 12 12 15:05 bin
drwxr-xr-x 18 igarashi.ryosuke staff 612B 12 12 15:05 bin.real
-rw-r--r-- 1 igarashi.ryosuke staff 53B 12 12 15:21 event.json
-rw-r--r-- 1 igarashi.ryosuke staff 423B 12 12 15:25 handler.js
drwxrwxr-x 6 igarashi.ryosuke staff 204B 12 12 15:05 info
-rwxr-xr-x 1 igarashi.ryosuke staff 187B 12 12 14:25 lambdaRuby.rb
drwxr-xr-x 9 igarashi.ryosuke staff 306B 12 12 15:05 lib
-rw-r--r-- 1 igarashi.ryosuke staff 2.2K 12 12 14:20 serverless.yml

デプロイします。

$ sls deploy
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (7.22 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...

Service Information
service: LambdaRuby
stage: dev
region: us-east-1
api keys:
None
endpoints:
None
functions:
LambdaRuby-dev-hello: arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:LambdaRuby-dev-hello

テストします。

$ sls invoke -f hello -p event.json
{
"errorMessage": "missing ) after argument list",
"errorType": "SyntaxError",
"stackTrace": [
"Module.load (module.js:343:32)",
"Function.Module._load (module.js:300:12)",
"Module.require (module.js:353:17)",
"require (internal/module.js:12:17)"
]
}

handler.js からRubyスクリプトにパラメータを渡す箇所のエスケープが正しくなかったので修正します。

- const child = exec('./lambdaRuby.rb ' + ''' + JSON.stringify(event) + ''', (result) => {
+ const child = exec('./lambdaRuby.rb ' + "'" + JSON.stringify(event) + "'", (result) => {

もう一度デプロイしてテストします。

$ sls deploy
$ sls invoke -f hello -p event.json
null

ログを確認します。

$ sls logs -f hello
START RequestId: a50c91de-c036-11e6-a481-8f341729cc0f Version: $LATEST
2016-12-12 15:46:02.046 (+09:00) a50c91de-c036-11e6-a481-8f341729cc0f 3

END RequestId: a50c91de-c036-11e6-a481-8f341729cc0f
REPORT RequestId: a50c91de-c036-11e6-a481-8f341729cc0f Duration: 288.31 ms Billed Duration: 300 ms Memory Size: 1024 MB Max Memory Used: 23 MB

正常に動作することが確認できました。

Rubygemsを使う

実用的なスクリプトを作るにはRubygemsも使える必要があるので、インストールしたgemも使えるかどうか試してみました。

先程のインスタンスに、SSHログインして、gemをインストールします。

$ ./LambdaRuby/bin/gem install faker --no-ri --no-rdoc
Fetching: i18n-0.7.0.gem (100%)
Successfully installed i18n-0.7.0
Fetching: faker-1.6.6.gem (100%)
Successfully installed faker-1.6.6
2 gems installed

$ ls -l LambdaRuby/lib/ruby/gems/2.2.0/gems/
合計 12
drwxrwxr-x 4 ec2-user ec2-user 4096 7月 14 2015 bundler-1.9.9
drwxrwxr-x 3 ec2-user ec2-user 4096 12月 13 07:27 faker-1.6.6
drwxrwxr-x 5 ec2-user ec2-user 4096 12月 13 07:27 i18n-0.7.0

ローカル環境に戻り、gemをインストールしたディレクトリごとファイルをダウンロードします。

$ scp -r -i ~/.ssh/private.pem ec2-user@[グローバルIPアドレス]:~/LambdaRuby/lib .
$ ls -l lib/ruby/gems/2.2.0/gems/
total 0
drwxrwxr-x 12 igarashi.ryosuke staff 408 12 12 16:05 babymetal-0.1.6
drwxrwxr-x 5 igarashi.ryosuke staff 170 12 12 15:05 bundler-1.9.9

Rubyスクリプトを修正します。

require 'faker'

puts Faker::Name.name

デプロイして、実行します。

$ sls deploy
$ sls invoke -f hello -p event.json
null

ログを確認します。

$ sls logs -f hello
START RequestId: 7de1992e-c106-11e6-b626-712e694e52b4 Version: $LATEST
2016-12-13 16:33:52.944 (+09:00) 7de1992e-c106-11e6-b626-712e694e52b4 Isabelle Ortiz

END RequestId: 7de1992e-c106-11e6-b626-712e694e52b4
REPORT RequestId: 7de1992e-c106-11e6-b626-712e694e52b4 Duration: 1296.70 ms Billed Duration: 1300 ms Memory Size: 1024 MB Max Memory Used: 31 MB

最初のサンプルと同様に、ログに結果が出力されていることが分かります。

まとめ

今回はRubyを試しましたが、他の言語も同様に言語の実行環境ごとパッケージにしてLambdaのコンテナ内で実行するということをやっているようです。既存のスクリプトをLambdaに移行したい場合の一つの選択肢として覚えておくと良いかもしれません。