AWS lambdaを利用してS3の画像アップロードをトリガーにサムネイル画像を自動生成する
目次
インフラエンジニアの寺岡です。
今回のテーマはAWSのサービスの中の一つの「lambda」です。
AWS Lambda (サーバーレスでコードを実行・自動管理) | AWS
ちなみに読み方は「ラムダ」です。間の「b」はどこに行ったのか。
ということは置いておいて・・・
この手の記事はすでにたくさん存在していてもはや何番煎じになるかもわかりませんが
自主的な勉強も兼ねて少し触ってみました。
今回はlambdaを利用してサムネイル画像を作ります。詳細は以下をご覧ください↓
■そもそも「lambda」って何ですか?
はい、まず初めにlambdaの概要から。
お決まりの如くAWSの公式ドキュメントから引用します(
AWS Lambda とは - AWS Lambda - AWS Documentation
AWS Lambda はサーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービスです。
・・・・なるほどですね。
lambdaは、何かしらの「イベント」をトリガーにして
予め登録しておいたコードを実行することができます。
また、このイベントをトリガーにして非同期に処理が実行されるので
常時EC2インスタンス等を起動しておく必要がなくなります。
サーバーをプロビジョニングしたり管理しなくても良いというのはこの部分のことですね。
さてさて、先ほどから「イベント」という言葉が出てきていますが
一言で申しますと以下のようなものです。
- S3のバケットにファイルがアップロードされた
つまり、「S3のバケットにファイルがアップロードされたら何らかの処理を自動実行する」
ということが可能になります。
もちろんEC2インスタンス上のAPIを叩きに行くなんてことはしません。
S3とlambdaだけで完結します。
はい、ここまでの内容とブログタイトルをご覧になった方はピンときたと思いますが
今回は「S3のバケットにファイルがアップロードされたら画像のサムネイル画像を自動生成する」
・・・・・・・をやっていきたいと思います(
■サムネイル画像が出来るまでの道のり
- 画像をアップロードするS3のバケットを用意
- lambda関数の記述
- 関数の動作テスト
- lambdaトリガーの設定
- 動作確認
①画像をアップロードするS3のバケットを用意
まずは、入れ物を用意しないと始まらないということでさくっとS3にバケットを作成します。
バケットの作成とポリシーの設定は以下の記事をご覧ください。
以前に私が書いたものですのでどさくさに紛れて宣伝します(
②lambda関数の記述
いよいよ本題、lambdaの関数を記述します。
「サムネイル画像を自動生成する」ためのコードを記述してlambdaに登録します。
コードに関しては、pythonやnode.jsベースで記述できるのですが
今回は個人的な好みを尊重してnode.jsで記述します(python好きな方は申し訳ございません
まずはLambda関数の作成というボタンをクリックします、すると以下のような画面になると思います。
この画面でlambda関数の設計図を選択します。
予め用途に併せてテンプレート的なものを用意してくれていますのでありがたく利用させてもらいます。
ますランタイムの選択から「Node.js6.10」を選択。
設計図に関してはs3周りの処理を行いたいので、「s3-get-object」というものを利用します。
設計図をクリックすると・・・
こんな感じでトリガーの設定画面になります。
バケット | ①で作ったバケットを選択します。 |
---|---|
イベントタイプ | 今回は「画像がアップロードされたら」関数を実行したいのでPutを選択します。 |
プレフィックス | 今回はバケット直下にアップロードを行うので省略します。 |
サフィックス | ファイル名の末尾が「jpg」のファイルのみを関数の実行対象とします。 |
トリガーの有効化のチェックは入れません。
後程記述する関数の動作確認が終わってから手動で有効にします。
入力が終わったら次へをクリックします。
この画面で実際に関数を記述していきます。
関数名を記入する項目がありますがこの部分はお好きな名前で大丈夫です。
設計図の選択画面で「s3-get-object」を選択したので
S3からファイルを取得するためのコードが予め記述されています。
'use strict'; console.log('Loading function'); const aws = require('aws-sdk'); const s3 = new aws.S3({ apiVersion: '2006-03-01' }); exports.handler = (event, context, callback) => { //console.log('Received event:', JSON.stringify(event, null, 2)); // Get the object from the event and show its content type const bucket = event.Records[0].s3.bucket.name; const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')); const params = { Bucket: bucket, Key: key, }; s3.getObject(params, (err, data) => { if (err) { console.log(err); const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`; console.log(message); callback(message); } else { console.log('CONTENT TYPE:', data.ContentType); callback(null, data.ContentType); } }); };
しかし、このままだとS3からファイルを取得することはできますが
サムネイル画像を生成することが出来ません。
少しこのコードに追記します。
'use strict'; console.log('Loading function'); var fs = require('fs'); var im = require('imagemagick'); const aws = require('aws-sdk'); const s3 = new aws.S3({ apiVersion: '2006-03-01' }); exports.handler = (event, context, callback) => { const bucket = event.Records[0].s3.bucket.name; const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')); const params = { Bucket: bucket, Key: key, }; s3.getObject(params, (err, data) => { if (err) { console.log(err); const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`; console.log(message); callback(message); } else { var contentType = data.ContentType; var extension = contentType.split('/').pop(); console.log(extension); im.resize({ srcData: data.Body, format: extension, width: 100 }, function(err, stdout, stderr) { if (err) { context.done('resize failed', err); } else { var thumbnailKey = key.split('.')[0] + "-thumbnail." + extension; s3.putObject({ Bucket: bucket, Key: thumbnailKey, Body: new Buffer(stdout, 'binary'), ContentType: contentType }, function(err, res) { if (err) { context.done('error putting object', err); } else { callback(null, "success putting object"); } }); } }); } }); };
追記するとこんな感じになります。
S3からファイルを取得するまでは同じですが
取得した画像からimagemagickを利用してリサイズを行い
加工後の画像をサムネイル画像として再度S3にアップロードしています。
コードを記述したら、以下のようにIAM Roleの設定を行います
今回はこの部分は割愛してテンプレートから新しいRoleを自動で作成してしまいましょう。
ここで作ったロールに対してS3のオブジェクトを操作するポリシーがアタッチされる形になるので
正しく設定しないと関数がエラーになったりします。
(S3に対するアクセス権限がなくてAccess Deniedはよくあるお話です)
次へを押すと確認画面になりますので、「関数の作成」ボタンを押しましょう。
するとlambda関数が作成されます。
一覧に、先ほど作成した関数が表示されていますね!
③関数の動作テスト
作成した関数が正しく動作するかをテストします。
画面上部のアクションからテストイベントの設定を選択します。
すると、テストイベントの入力の画面になります。
テストイベントをjson形式で記述するのですが
このjsonをlambdaがS3から受け取って関数を実行するとお考えください。
今回は、test.jpgという画像ファイルを予めS3にバケットにアップロードしておき
その画像がアップロードされたことを想定してlambda関数が正常に動作するかをテストします。
このテストイベントにもテンプレートが用意されているので
S3 Putというテンプレートを利用しましょう。
以下を入力して、保存してテストをクリックします。
{ "Records": [ { "eventVersion": "2.0", "eventTime": "1970-01-01T00:00:00.000Z", "requestParameters": { "sourceIPAddress": "*" }, "s3": { "configurationId": "testConfigRule", "object": { "eTag": "0123456789abcdef0123456789abcdef", "sequencer": "0A1B2C3D4E5F678901", "key": "test.jpg", "size": 1024 }, "bucket": { "arn": "arn:aws:s3:::lambda-img-resize", "name": "lambda-img-resize", "ownerIdentity": { "principalId": "EXAMPLE" } }, "s3SchemaVersion": "1.0" }, "responseElements": { "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", "x-amz-request-id": "EXAMPLE123456789" }, "awsRegion": "ap-northeast-1", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "EXAMPLE" }, "eventSource": "aws:s3" } ] }
実行結果として成功したときのメッセージが返ってきました!
S3バケットの中身に変化があったか確認してみましょう。
はい、何事もなかったかのようにサムネイル画像が生成されています。
関数の実行自体は問題なさそうですね。
④lambdaトリガーの設定
いよいよ終盤、トリガーの設定を行います。
先程まではあくまでテストなので
今の段階で新しく画像をアップロードしてもサムネイル画像は作成されません。
画像がアップロードされたら関数が自動実行されるようにトリガーの設定を行う必要があります。
作成したlambda関数からトリガータブをクリックします。
関数を作成する段階でトリガー自体は作成しているので、あとはこれを有効化するだけです。
有効化をクリックします。
⑤動作確認
では、実際に画像をS3にアップロードして確認してみましょう。
・・・・・・・作成されてますね、素晴らしい。。。
■まとめ
いかがでしたでしょうか。
コードを書く必要があるので少し複雑になってしまいますが
EC2いらずでここまで出来てしまうので便利ですしおもしろいですね。
今回は軽く触ってみただけですが、もう少し高度なこともやってみようと思います。
以上です、ありがとうございました。
![Top Scroll �A�C�R��](https://beyondjapan.com/cms/wp-content/themes/beyond/img/totop.png)