Lambdaファンクションを作る
ブループリント
AWSの管理画面からLambdaに行くと、ブループリントという画面が出て
用途に応じたLambdaファンクションのテンプレートが選べます。
今回は「image-processing-service」を元に作っていきます。
名前
名前は適当にわかりやすい名前をつけます。今回はresizeFunction
Lambdaファンクション
テンプレートのままだとS3にアクセスするコードがないので書き足します。
こんな感じになりました。
var im = require('imagemagick');
var fs = require('fs');
var aws = require('aws-sdk');
var s3 = new aws.S3({ apiVersion: '2006-03-01' });
//最後に呼ばれる
var postProcessResource = function(resource, fn) {
var ret = null;
if (resource) {
if (fn) {
ret = fn(resource);
}
try {
fs.unlinkSync(resource);
} catch (err) {
// Ignore
}
}
return ret;
};
var resize = function(event, context) {
//どのサイズに縮小するか
if (!event.height && !event.width) {
event.width = 100;
}
//Lambdaファンクションの動作環境での作業場所
var resizedFile = "/tmp/resized." + (event.outputExtension || 'png');
var buffer = new Buffer(event.base64Image, 'base64');
delete event.base64Image;
delete event.outputExtension;
event.srcData = buffer;
event.dstPath = resizedFile;
try {
im.resize(event, function(err, stdout, stderr) {
if (err) {
throw err;
} else {
console.log('Resize operation completed successfully');
//S3にputする
s3.putObject(
{"Bucket":event.bucket,
"Key":event.outPutName,
"Body":new Buffer(fs.readFileSync(resizedFile))
},
function(err, data){
console.log(err);
console.log(data);
//putが終わったら成功としてLambdaファンクションを閉じる
context.succeed(postProcessResource(resizedFile, function(file) {
return new Buffer(fs.readFileSync(file));
}));
});
}
});
} catch (err) {
console.log('Resize operation failed:', err);
context.fail(err);
}
};
//最初に呼ばれる関数
exports.handler = function(event, context) {
//S3から渡ってくるバケットの名前の想定
var bucket = event.Records[0].s3.bucket.name;
event.bucket = bucket;
//画像ファイル名
var key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
var params = {
Bucket: bucket,
Key: key
};
var reg=/(.*)(?:\.([^.]+$))/;
var match = key.match(reg);
//_thumのついているファイルは処理しない(処理するとS3に無限にアップロードされる)
if( !match[1].lastIndexOf( "_thum" ) ){
return;
}
//変換後のファイル名
event.outPutName = match[1]+"_thum."+match[2];
//S3のファイルを取得
s3.getObject(params, function(err, data) {
event.base64Image = new Buffer(data.Body).toString('base64');
resize(event, context);
}
)};
execution role
execution rolesを設定します。
最初は何もないのでCreate new roleの下の「S3 execution role」を選択します。
すると別タブが立ち上がりロールの設定画面にうつります。
デフォルトで問題ないはずなので許可を押します。
Advanced settings
メモリとタイムアウトの設定です。このままでOK。
S3の設定
S3からバケットを作成し、画像ファイルをアップロードします。
S3のバケット名は全ユーザ共通なので人とかぶらないような名前にします。
S3のプロパティ
S3の右上にある「プロパティ」から
Lambdaファンクションが実行できる権限を追加します。
イベントに「Put」
送信先にLambda関数、
ARNに先ほどLambda関数につけた名前「resizeFunction」を入れ、保存します。
また、「アクセス許可」の「バケットポリシー」からInvocation Roleを設定できるようです。今回は飛ばします。
テスト
Lambdaファンクションが出来たらテストをします。
Lambdaファンクションの編集画面で「Configure test event」をクリック
すると渡されるeventが編集できるので、「S3 put」を選択。
S3から渡ってくるはずのイベントをシミュレーション出来るので
Records.s3.bucket.nameに自分のS3のバケット
Records.s3.object.keyにS3のバケットに存在している画像ファイル名を指定してTest
{
"Records": [
{
"eventVersion": "2.0",
"eventTime": "1970-01-01T00:00:00.000Z",
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"s3": {
"configurationId": "testConfigRule",
"object": {
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901",
"key": "S3に存在している画像ファイル名.jpg",
"size": 1024
},
"bucket": {
"arn": "arn:aws:s3:::mybucket",
"name": "S3に存在しているバケット名",
"ownerIdentity": {
"principalId": "EXAMPLE"
}
},
"s3SchemaVersion": "1.0"
},
"responseElements": {
"x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH",
"x-amz-request-id": "EXAMPLE123456789"
},
"awsRegion": "us-east-1",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"eventSource": "aws:s3"
}
]
}
画面下部にログが出て
S3のバケットにも***_thum.jpgという画像が出来ています。
S3のバケットに直接ファイルをアップロードしてもサムネイルが出来ます。
#その他気をつけるところ
無限ループ
リサイズ済みの画像をS3にアップロードする時に
それを契機としてLambdaファンクションが走るので
Lambdaファンクションの無限ループが起きます。
今回はリサイズ済みの画像はファイル名の末尾に_thumとつけ、
ファンクション内で_thumとつくファイル名の場合は処理しないことで回避。
ロール違反?
Execution Roleはすべてデフォルトのを使っていたのですが、
一度ロールを変更した後、Invalid Access to S3のようなエラーが出て戻してもテストが通らなくなりました。
試行錯誤の結果、インラインポリシーをPolicy Generaterで生成したところ動くようになりましたが、元々使わなくても動いていたので原因ははっきりとはわかりません。