AWS LambdaファンクションをGulpで実行

2015.06.29

以下のブログエントリで、GulpでAWS AWS Lambdaファンクションをデプロイする手順をご紹介しました。

AWS LambdaファンクションをGulpでデプロイ

せっかくなので、このブログエントリで作成したGulpfileにタスクを追加して、AWS Lambdaファンクションの実行もGulpからやってみたいと思います。

Gulpプラグイン?

上記のブログエントリでAWS LambdaファンクションをデプロイするためのGulpプラグインを3つほどご紹介しましたが、いずれもAWS Lambdaファンクションを実行するタスクは含まれていません

そもそもGulpでAWS Lambdaファンクションを実行したいというモチベーションが無いということかもしれませんが、めげずに自前でタスクを作成したいと思います。

AWS SDK for JavaScript in Node.jsClass: AWS.Lambdainovokeというメソッドが用意されているので、これを使いたいと思います。

タスクの実装、その前に

S3バケットの作成

AWS LambdaファンクションをGulpでデプロイ で作成したAWS Lambdaファンクションは「S3バケットに画像ファイルをアップロードすると、そのサムネイルが別のバケットに保存される」という内容のものでした。

実際のこのAWS Lambdaファンクションを実行するにあたり、事前にS3バケットを2つ作成して、一方のバケットにサムネイルの元となる画像をアップロードしておきます。

  • 元画像アップロード用バケット : yy.sourcebucket
  • サムネイル画像保存用バケット : yy.sourcebucketresized
  • 元画像ファイル : lambda-test.jpg

「元画像アップロード用のバケット」と「元画像ファイル」の名前はイベントデータとしてAWS Lambdaファンクションに渡されるので、任意の名前で作成して構いません。

一方で「サムネイル画像保存用バケット」の名前はAWS Lambdaファンクション内で指定する形となります。今回実行するAWS Lambdaファンクションはサムネイル画像を「<元画像アップロード用バケット> + resized」という名前のバケットに保存する作りとなっているので、このネーミングルールに従ってバケットを作成します。

参考までに、AWS CLIでバケットを作成する手順を記載しておきます。

# バケット作成
# (--profileで指定している"lambda-test"は、region = us-west-2"を定義したプロファイルを指定)
$ aws s3 mb s3://yy.sourcebucket --profile lambda-test
$ aws s3 mb s3://yy.sourcebucketresized --profile lambda-test

# ファイルアップロード
$ aws s3 cp lambda-test.jpg s3://yy.sourcebucket --profile lambda-test

# ファイル確認
$ aws s3 ls s3://yy.sourcebucket --profile lambda-test

イベントデータの準備

今回実行するAWS Lambdaファンクションですが、本来は、、

  • S3にファイルをアップロード(S3イベントが発生)
  • イベントデータ(ファイルがアップロードされたバケット名やファイル名)をインプットとしてAWS Lambdaファンクションが実行される

という動きになります。

今回のようにマニュアルでAWS Lambdaファンクションを実行する場合は、S3イベントは発生しません。ので、イベントデータもマニュアルでLambdaに渡してやる必要があります。

今回はイベントデータとして以下のようなjsonファイルを用意しました(ファイル名はpayload_s3.jsonとしました)。中身はAWS Lambda Walkthrough 2: Handling Amazon S3 Events Using the AWS CLI (Node.js)にあるサンプルほぼそのままで、バケット名と画像ファイル名だけ修正しています。

{
"Records":[
{
"eventVersion":"2.0",
"eventSource":"aws:s3",
"awsRegion":"us-west-2",
"eventTime":"1970-01-01T00:00:00.000Z",
"eventName":"ObjectCreated:Put",
"userIdentity":{
"principalId":"AIDAJDPLRKLG7UEXAMPLE"
},
"requestParameters":{
"sourceIPAddress":"127.0.0.1"
},
"responseElements":{
"x-amz-request-id":"C3D13FE58DE4C810",
"x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
},
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"testConfigRule",
"bucket":{
"name":"yy.sourcebucket",
"ownerIdentity":{
"principalId":"A3NL1KOZZKExample"
},
"arn":"arn:aws:s3:::yy.sourcebucket"
},
"object":{
"key":"lambda-test.jpg",
"size":1024,
"eTag":"d41d8cd98f00b204e9800998ecf8427e",
"versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko"
}
}
}
]
}

npmモジュールの追加

aws-sdk-jsと、ログ出力用にgulp-utilを追加でインストールします。

$ npm install aws-sdk gulp-util --save-dev

タスクの実装

準備が整ったので、GulpfileにAWS Lambdaファンクションの実行タスクを追加します。

var gulp = require('gulp');
var zip = require('gulp-zip');
var del = require('del');
var install = require('gulp-install');
var runSequence = require('run-sequence');
var awsLambda = require('node-aws-lambda');

// (追加)aws-sdkとgulp-utilモジュールの読み込み
var AWS = require('aws-sdk');
var gutil = require('gulp-util');

// (追加)
// aws-sdk - Lambdaのservice interface objectを生成
// イベントデータ(jsonファイル)の読み込み
// ここから
var lambdaConf, lambda, payload;

lambdaConf = require('./lambda-config.js');

AWS.config.credentials = new AWS.SharedIniFileCredentials();
AWS.config.update({
region: lambdaConf.region
});
lambda = new AWS.Lambda();

payload = require('./payload_s3.json');
// ここまで

gulp.task('clean', function(cb) {
del(['./dist', './dist.zip'], cb);
});

gulp.task('js', function() {
return gulp.src('index.js')
.pipe(gulp.dest('dist/'));
});

gulp.task('node-mods', function() {
return gulp.src('./package.json')
.pipe(gulp.dest('dist/'))
.pipe(install({production: true}));
});

gulp.task('zip', function() {
return gulp.src(['dist/**/*', '!dist/package.json'])
.pipe(zip('dist.zip'))
.pipe(gulp.dest('./'));
});

gulp.task('upload', function(callback) {
awsLambda.deploy('./dist.zip', lambdaConf, callback);
});

// (追加)AWS Lambdaファンクションの実行タスク
// ここから
gulp.task('invoke', function() {
var invoke_params;
invoke_params = {
FunctionName: lambdaConf.functionName,
InvocationType: 'RequestResponse',
LogType: 'Tail',
Payload: JSON.stringify(payload)
};
return lambda.invoke(invoke_params, function(err, data) {
if (err) {
gutil.log(err, err.stack);
} else {
if (data.FunctionError) {
gutil.log("An error occurred while executing the Lambda function.");
gutil.log("Error Type:", data.FunctionError);
} else {
gutil.log("The Lambda function was successfully executed.");
}
gutil.log("Status Code:", data.StatusCode);
var resultLog = new Buffer(data.LogResult, 'base64');
gutil.log(resultLog.toString());
}
});
});
// ここまで

gulp.task('deploy', function(callback) {
return runSequence(
['clean'],
['js', 'node-mods'],
['zip'],
['upload'],
callback
);
});

// (追加)デフォルトタスク(AWS Lambdaファンクションのデプロイ&実行)
gulp.task('default', function() {
return runSequence(
['deploy'],
['invoke']
);
});

追加したAWS Lambdaファンクションの実行タスクですが、SDKを使ってLambdaのinovokeメソッドを呼んでいるだけです。

  • InvocationType: 'RequestResponse'でAWS Lambdaファンクションを同期実行
  • LogType: 'Tail'でAWS Lambdaファンクションの最新の実行ログ(4KB)を取得
  • ログはBase64でエンコードされているので、デコードしてコンソールに表示

といった感じです。詳しくはAPIドキュメントを参照頂くのが良いかと思います。

Gulpのデフォルトタスクとして、AWS Lambdaファンクションのデプロイ&実行タスクも追加してみました。

AWS Lambdaファンクションの実行

追加したタスクを実際に動かしてみます。

$ gulp invoke
[00:45:44] Using gulpfile ~/lambda-gulp-sample/gulpfile.js
[00:45:44] Starting 'invoke'...
[00:45:44] Finished 'invoke' after 28 ms
[00:45:47] The Lambda function was successfully executed.
[00:45:47] Status Code: 200
[00:45:47] START RequestId: bd3e3856-1dac-11e5-bc61-cbaeb77d45fa
2015-06-28T15:45:45.946Z bd3e3856-1dac-11e5-bc61-cbaeb77d45fa Reading options from event:
{ Records:
[ { eventVersion: '2.0',
eventSource: 'aws:s3',
awsRegion: 'us-west-2',
eventTime: '1970-01-01T00:00:00.000Z',
eventName: 'ObjectCreated:Put',
userIdentity: { principalId: 'AIDAJDPLRKLG7UEXAMPLE' },
requestParameters: { sourceIPAddress: '127.0.0.1' },
responseElements:
{ 'x-amz-request-id': 'C3D13FE58DE4C810',
'x-amz-id-2': 'FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD' },
s3:
{ s3SchemaVersion: '1.0',
configurationId: 'testConfigRule',
bucket:
{ name: 'yy.sourcebucket',
ownerIdentity: { principalId: 'A3NL1KOZZKExample' },
arn: 'arn:aws:s3:::yy.sourcebucket' },
object:
{ key: 'lambda-test.jpg',
size: 1024,
eTag: 'd41d8cd98f00b204e9800998ecf8427e',
versionId: '096fKKXTRTtl3on89fVO.nfljtsv6qko' } } } ] }
2015-06-28T15:45:47.844Z bd3e3856-1dac-11e5-bc61-cbaeb77d45fa Successfully resized yy.sourcebucket/lambda-test.jpg and uploaded to yy.sourcebucketresized/resized-lambda-test.jpg
END RequestId: bd3e3856-1dac-11e5-bc61-cbaeb77d45fa
REPORT RequestId: bd3e3856-1dac-11e5-bc61-cbaeb77d45fa Duration: 1934.13 ms Billed Duration: 2000 ms Memory Size: 128 MB Max Memory Used: 44 M

うまく動いたようです。S3のバケットも確認してみます。

$ aws s3 ls s3://yy.sourcebucketresized --profile lambda-test
2015-06-29 00:45:48 14824 resized-lambda-test.jpg

サムネイル画像保存用バケットに画像が追加されました!

デフォルトタスクも追加したので、単にgulpと実行するとAWS Lambdaファンクションのデプロイと実行が、セットで行われます。

まとめ

GulpでAWS AWS Lambdaファンクションのデプロイと実行、お試しレベルの実装ではありますが参考になれば幸いです。

今回はローカルのZIPファイルをアップロードし実行する形を取りましたが、git pushするとリモートのリポジトリからAWS Lambdaファンクションがデプロイされる、みたいなフローの方がなんだか良さそうな気がします。それでは、また。