ちょっと話題の記事

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

2015.06.22

東京リージョンでのリリースが待ち遠しいAWS Lambda。既にバリバリに活用されている方もいれば、夏に向けて絶賛勉強中の方もいらっしゃるかと思います。

そんな皆様に向けて、今回はAWS Lambdaそのものの機能ではなく、AWS Lambdaファンクションの開発ワークフローに関するお話をお届けします。

今回のスコープ(=AWS Lambdaファンクションのデプロイ)

開発ワークフローとか大げさなことを言いましたが、今回のスコープはAWS Lambdaファンクションのデプロイの部分です。

AWS Lambdaファンクションのデプロイは、AWS Lambdaファンクション本体(index.jsなど)と、AWS Lambdaファンクションで利用するnode.jsパッケージ等のファイル群一式をZIP化してアップロード、 というやり方が基本です。(ランタイムがnode.jsの場合はManagement Console上のエディタで直接コーディングする方法もありますが、利用シーンは限定的かと思います。)

作業的には大した手間ではありませんが、AWS Lambdaファンクションの開発中はこのデプロイ作業を何度が繰り返すことになるかと思いますので、できれば自動化しておきたいものです。

コマンドにすると二撃ぐらいなものなのでシェルスクリプトなどを使ってもよいですが、どうせなら使い慣れたビルドツールなどで自動化しておくと気持ちがいいでしょう。

今回はAWS Lambdaのランタイムとしてはnode.jsをターゲットにします。node.js、ビルドツールといえば、、、そう、gulpですね(異論はあるかと思いますが)。ラムダとガルプ、なんだか語呂もよさそうな感じです。

ということで、今回は上記のAWS Lambdaファンクションのデプロイをgulpを使ってタスク化してみましたのでその手順をご紹介します。

前提となる環境

  • OS : OSX 10.10.3
  • node.js : v0.12.4
  • gulp : v3.9.0

node.js、gulpのインストール方法については以下エントリをご参照ください。

これからはじめるGulp #1:bundler, rbenv, nodebrewでGulp環境を作ってみる | Developers.IO

また、 AWS LambdaファンクションのデプロイにはAWS SDK for Node.jsが使われます。クレデンシャル情報は.aws/credentialsのdefaultが利用されますので、予め設定しておきましょう。

# ~/.aws/credentials
[default]
aws_access_key_id =
aws_secret_access_key =

利用するGulpプラグイン

プラグインがあるのならば、それを使いたい。ググってみると以下が見つかりました。

lambduh-gulpはZIP化とAWS Lambdaファンクションの登録タスクが含まれています。他2つはAWS Lambdaファンクションの登録タスクのみをカバーしています。

lambduh-gulpでのZIP化は、ZIP化対象のディレクトリ名が決め打ちになっています。 ZIP化のタスクはgulp-zipなどのプラグインを使えば簡単にタスクを書けるので、自分で書いてしまうのがよいかと思います(ZIP化対象のディレクトリ名を変えたい場合など、何かと小回りが効きそうなので)。

gulp-awslambdaとnode-aws-lambdaの比較としては、後者の方が

  • AWS Lambdaファンクションの設定(リージョン、ファンクション名、timeout値など)をgulpfileから切り離して定義できる(作りになっている)。
  • AWS Lambdaファンクションへのイベントソースの登録までをカバーしている。(pull型のイベントのみ。)

というアドバンテージがあります。今回はnode-aws-lambdaを使ってみました。(※ただしイベントソースの登録は実行していません。)

よりシンプルにAWS Lambdaファンクションの登録/更新ができればよい、という場合はgulp-awslambdaを選択するのもよいかと思います。

Gulpの環境準備

node.jsのパッケージインストール(開発用)

npmを使って必要なnode.jsのパッケージをインストールします。

まずはnpm init -yでデフォルトのpackage.jsonを作成します。

$ mkdir lambda-gulp-sample
$ cd lambda-gulp-sample
$ npm init -y

続いて開発用のgulpのプラグインをインストールします。これらはAWS Lambdaファンクションの(ZIPファイル)には含めないので、--save-devで開発用パッケージとしてインストールしておきます。

$ npm install gulp node-aws-lambda gulp-zip del gulp-install run-sequence --save-dev

distディレクトリ作成

AWS Lambdaファンクション本体(jsファイル)と、デプロイメントパッケージに含めるnode.jsパッケージの格納先のディレクトリを作成しておきます。このディレクトリをZIP化してデプロイメントパッケージを作成します。ディレクトリ名はお好みでどうぞ。(今回はdistといディレクトリを作成しました。)

$ mkdir dist

ここまでの準備作業で、ファイル・ディレクトリ構成は以下のようになります。

lambda-gulp-sample
├── dist
├── node_modules
│   ├── del
│   ├── gulp
│   ├── gulp-install
│   ├── gulp-zip
│   ├── node-aws-lambda
│   └── run-sequence
└── package.json

AWS Lambdaファンクションの作成

index.jsの作成

まずはAWS Lambdaファンクションの本体を作成しましょう。ファイル名は任意ですが、ここではindex.jsとしておきます。コードの中身ですが、今回はAWS Lambda Walkthrough 2: Handling Amazon S3 Events Using the AWS CLI (Node.js) - AWS Lambdaにあるサンプルコードを使いました。S3バケットに画像ファイルをアップロードするとLambdaが発火してそのサムネイルが別のバケットに保存される、というものです。コードの詳細はリンク先を参照ください。

node.jsのパッケージインストール

AWS Lambdaファンクションで使用するnode.jsパッケージもインストールしておきます。--saveをつけて、package.jsonに依存関係が保存されるようにしておきます。

$ npm install async gm --save

package.jsonは以下のようになります。

{
"name": "lambda-gulp-sample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"del": "^1.2.0",
"gulp": "^3.9.0",
"gulp-install": "^0.4.0",
"gulp-zip": "^3.0.2",
"node-aws-lambda": "^0.1.1",
"run-sequence": "^1.1.1"
},
"dependencies": {
"async": "^1.2.1",
"gm": "^1.18.1"
}
}

後ほどGulpfileの作成で触れますが、ZIP化の際にこのpackage.json"dependencies"に含まれているパッケージをdistディレクトリにインストールするようにタスクを構成するので、ZIPに含めたいパッケージは--saveで、ZIPに含めたくないものは--save-devでインストールしておきます。

IAMロールの作成

AWS Lambdaファンクションの登録時にIAMロール(execution role)を指定する必要があるので、事前に作成しておきます。(既にロールが作成済みであればこの手順はすっ飛ばしてOKです。)

参考として、CLIでのロールの作成手順を載せておきます。

Policyファイルの作成

  • Trust Policyを以下の内容で作成します。(AWS LambdaからのAssumeRoleを許可する)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

AWS CLIでロールを作成

作成したPolicyファイルを引数に指定してaws iam create-roleコマンドを実行します。

$ aws iam create-role
--role-name lambda_s3_exec_role
--assume-role-policy-document file://trust_policy.json

ロールにAWSLambdaExecuteポリシーをアタッチします。このポリシーにはCloudWatchLogsへのフルアクセスと、S3へのGet/Putの許可が含まれています。

$ aws iam attach-role-policy
--role-name lambda_s3_exec_role
--policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute
  • AWSLambdaExecuteポリシーの内容
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:*"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::*"
}
]
}

gulpfile.jsの作成

準備が整ったので、gulpfile.jsを作成しタスクを登録していきましょう。今回はREADMEにあるサンプルをそのまま使ってみました。

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');

// distディレクトリのクリーンアップと作成済みのdist.zipの削除
gulp.task('clean', function(cb) {
del(['./dist', './dist.zip'], cb);
});

// AWS Lambdaファンクション本体(index.js)をdistディレクトリにコピー
gulp.task('js', function() {
return gulp.src('index.js')
.pipe(gulp.dest('dist/'));
});

// AWS Lambdaファンクションのデプロイメントパッケージ(ZIPファイル)に含めるnode.jsパッケージをdistディレクトリにインストール
// ({production: true} を指定して、開発用のパッケージを除いてインストールを実施)
gulp.task('node-mods', function() {
return gulp.src('./package.json')
.pipe(gulp.dest('dist/'))
.pipe(install({production: true}));
});

// デプロイメントパッケージの作成(distディレクトリをZIP化)
gulp.task('zip', function() {
return gulp.src(['dist/**/*', '!dist/package.json'])
.pipe(zip('dist.zip'))
.pipe(gulp.dest('./'));
});

// AWS Lambdaファンクションの登録(ZIPファイルのアップロード)
// (既にFunctionが登録済みの場合はFunctionの内容を更新)
gulp.task('upload', function(callback) {
awsLambda.deploy('./dist.zip', require("./lambda-config.js"), callback);
});

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

uploadタスクで、awsLambdaを使ってAWS Lambdaファンクションをデプロイしています。このタスクではAWS Lambdaファンクションのconfigファイルとしてlambda-config.jsを読み込むようにしています。次にこのconfigファイルを作成します。

lambda-config.jsの作成

リージョン、ハンドラ、IAMロールのARN、ファンクション名、Timeout値を指定します。(残念ながらメモリサイズの指定には対応していないようです。)

PULL型のイベント(Kinesis or DynamoDB Stream)についてはイベントソースの設定もこのconfigファイルで定義できます。

module.exports = {
region: 'us-west-2',
handler: 'index.handler',
role: 'arn:aws:iam::xxxxxxxxxxxx:role/lambda_s3_exec_role',
functionName: 'gulpSample',
timeout: 10
// eventSource: {
// EventSourceArn: ,
// BatchSize: 200,
// StartingPosition: "TRIM_HORIZON"
//}
}

デプロイの実行

ここまででAWS Lambdaファンクションをデプロイする準備が整いました。ディレクトリ・ファイル構成は以下のようになっています。

lambda-gulp-sample
├── dist
├── gulpfile.js
├── index.js
├── lambda-config.js
├── node_modules
│   ├── async
│   ├── del
│   ├── gm
│   ├── gulp
│   ├── gulp-install
│   ├── gulp-zip
│   ├── node-aws-lambda
│   └── run-sequence
└── package.json

デプロイを実行してみましょう。

$ gulp deploy
[23:42:14] Using gulpfile ~/Dropbox/Repos/lambda-gulp-sample/gulpfile.js
[23:42:14] Starting 'deploy'...
[23:42:14] Starting 'clean'...
[23:42:14] Finished 'clean' after 32 ms
[23:42:14] Starting 'js'...
[23:42:14] Starting 'node-mods'...
[23:42:14] Finished 'js' after 40 ms
npm WARN package.json lambda-gulp-sample@1.0.0 No description
npm WARN package.json lambda-gulp-sample@1.0.0 No repository field.
npm WARN package.json lambda-gulp-sample@1.0.0 No README data
async@1.2.1 node_modules/async

gm@1.18.1 node_modules/gm
├── array-parallel@0.1.3
├── array-series@0.1.5
└── debug@2.2.0 (ms@0.7.1)
[23:42:16] Finished 'node-mods' after 1.68 s
[23:42:16] Starting 'zip'...
[23:42:16] Finished 'zip' after 245 ms
[23:42:16] Starting 'upload'...
[23:42:19] Finished 'upload' after 2.96 s
[23:42:19] Finished 'deploy' after 4.94 s

aws cliを使ってAWS Lambdaファンクションがデプロイされたことを確認してみましょう。(※--profileオプションで指定しているのは"region = us-west-2"を定義してあるプロファイルです。)

$ aws lambda list-functions --profile=lambda-test
{
"Functions": [
{
"FunctionName": "gulpSample",
"MemorySize": 128,
"CodeSize": 161271,
"FunctionArn": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:gulpSample",
"Handler": "index.handler",
"Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambda_s3_exec_role",
"Timeout": 10,
"LastModified": "2015-06-21T14:42:19.349+0000",
"Runtime": "nodejs",
"Description": ""
}
]
}

デプロイしたAWS Lambdaファンクションが表示されました!

まとめ

ZIP化とかファイルのアップロードは地味に面倒な作業なので、コマンド一発で出来るようにしておくと開発効率も上がって皆が幸せになれるかと思います。

今回は既存のプラグインを使ってタスクを作成してみましたが、AWS Lambdaファンクションのデプロイタスクの実装はそれほど難しくはないかと思いますので、プラグインを参考に一から自前でデプロイタスクを書いてしまってもよいかと思います。

またAWS SDKは各言語ともAWS Lambdaファンクションのデプロイに対応しているので、皆さんの手に馴染んだ言語、ビルドツールを使ってタスクを作成するのもよいでしょう(RubyならRakeなど。)。

今回AWS Lambdaファンクションをデプロイするところまではタスク化できました。せっかくなのでAWS Lambdaファンクションの実行タスクも作ってみたいと思います。それはまた次回のエントリで。

AWS LambdaファンクションをGulpで実行 | Developers.IO

参考