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

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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

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に移行したい場合の一つの選択肢として覚えておくと良いかもしれません。