AWS Lambdaを使ってS3にアップロードされた画像をリサイズする

  • 48
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

Lambdaファンクションを作る

ブループリント

AWSの管理画面からLambdaに行くと、ブループリントという画面が出て
用途に応じたLambdaファンクションのテンプレートが選べます。

今回は「image-processing-service」を元に作っていきます。

スクリーンショット 2015-11-17 20.08.11.png

名前

名前は適当にわかりやすい名前をつけます。今回はresizeFunction

スクリーンショット 2015-11-17 20.11.10.png

Lambdaファンクション

テンプレートのままだとS3にアクセスするコードがないので書き足します。
こんな感じになりました。

lambda
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を設定します。
スクリーンショット 2015-11-17 20.15.01.png
最初は何もないのでCreate new roleの下の「S3 execution role」を選択します。
すると別タブが立ち上がりロールの設定画面にうつります。
スクリーンショット 2015-11-17 20.19.21.png
デフォルトで問題ないはずなので許可を押します。

Advanced settings

メモリとタイムアウトの設定です。このままでOK。

スクリーンショット 2015-11-17 20.21.57.png

S3の設定

S3からバケットを作成し、画像ファイルをアップロードします。
S3のバケット名は全ユーザ共通なので人とかぶらないような名前にします。

スクリーンショット 2015-11-17 20.26.27.png

S3のプロパティ

S3の右上にある「プロパティ」から
Lambdaファンクションが実行できる権限を追加します。

スクリーンショット 2015-11-17 20.33.46.png

イベントに「Put」
送信先にLambda関数、
ARNに先ほどLambda関数につけた名前「resizeFunction」を入れ、保存します。

また、「アクセス許可」の「バケットポリシー」からInvocation Roleを設定できるようです。今回は飛ばします。

スクリーンショット 2015-11-17 20.37.47.png

テスト

Lambdaファンクションが出来たらテストをします。
Lambdaファンクションの編集画面で「Configure test event」をクリック
スクリーンショット 2015-11-17 19.51.33.png

すると渡されるeventが編集できるので、「S3 put」を選択。

スクリーンショット 2015-11-17 19.54.13.jpg

S3から渡ってくるはずのイベントをシミュレーション出来るので
Records.s3.bucket.nameに自分のS3のバケット
Records.s3.object.keyにS3のバケットに存在している画像ファイル名を指定してTest

event
{
  "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"
    }
  ]
}

画面下部にログが出て

スクリーンショット 2015-11-17 20.44.48.png

S3のバケットにも***_thum.jpgという画像が出来ています。

スクリーンショット 2015-11-17 20.46.47.png
S3のバケットに直接ファイルをアップロードしてもサムネイルが出来ます。

その他気をつけるところ

無限ループ

リサイズ済みの画像をS3にアップロードする時に
それを契機としてLambdaファンクションが走るので
Lambdaファンクションの無限ループが起きます。

今回はリサイズ済みの画像はファイル名の末尾に_thumとつけ、
ファンクション内で_thumとつくファイル名の場合は処理しないことで回避。

ロール違反?

Execution Roleはすべてデフォルトのを使っていたのですが、
一度ロールを変更した後、Invalid Access to S3のようなエラーが出て戻してもテストが通らなくなりました。
試行錯誤の結果、インラインポリシーをPolicy Generaterで生成したところ動くようになりましたが、元々使わなくても動いていたので原因ははっきりとはわかりません。