【3時間でできる?】AWSでS3+Lambda+API Gateway+CloudFrontのCDN構築2018

More than 1 year has passed since last update.

少しづつ広まってきているServerless技術を使って何かやろうとするとちょうどいい題材なのがS3に上げた画像を動的に縮小表示するというアプリです。

すなわち

https://xxx.cloudfront.net/somefile.jpeg?w=600

のようなURLでアクセスするとS3にある画像を動的に縮小して幅600pxにして表示してくれるというものです。

作るきっかけになったのは、自分のサイトで画像クチコミを載せていたのですが、圧縮すること無くそのまま上げてしまっていたので1ページ30MBみたいなサイズになって読み込み障害を起こすようになってしまったことでした。アップロード時に圧縮するのがセオリーだと思いますが、作った当時はそんなことは二の次としてたらあっという間に数が増えてしまったようです。今だとPietとかそういった選択肢もあるのかもしれません。お金があればAKAMAIやFastlyのようなCDNもアリでしょう。

それも厳しい人向けにAWSのLambdaなどのサーバレステクノロジーでやったときの内容を公開します。

以下設定画面とLambdaのソースになります。

構成はこんな感じですが、面倒そうに一瞬見えるかもしれませんが、全体通してS3の画像を圧縮して表示するシステムと思えばそれほど大変でもないです。以下の設定内容に沿ってまずはやってみることをオススメします。気合い入れたら3時間位で出来るかもです。

AWS Networking.png

動作イメージを簡単に言語化すると

1. 上記URLにアクセス

2. CloudFrontが最初の受け口になってAPI Gatewayに転送

3. API GatewayからLambdaをコールしてファイル名とクエリパラメータを渡す

4. Lambdaが起動してS3の画像を取得してImageMagickを使って圧縮後、Base64形式にして出力

5. 出力した画像を再度API Gatewayの出力として書き出し

6. CloudFrontがAPI Gatewayの書き出した画像情報にContent-typeなどのヘッダ情報をつけてHTMLで表示出来るように修正

となります。要はS3の画像をそのままURLにして表示するのでなく、AWSのAPI機能を使って自動化機能であるLambdaで圧縮かけて表示しているというところでしょうか。

まずはS3の設定からです。ここにBucketを用意して画像を置きます。


S3の設定

S3を開きます。Bucketの作成から行います。

s3-1.png

画像の置き場所となるfolderを作成します。ここではimagesとしています。以下参考にしてください。

このimages配下に適当な画像を置いておいてください。

s3-2.png


Lambdaの設定

次はLambdaです。functionを作成します。以下参照してください。

lambda1.png

lambda1.5.png

今回は触れませんがIAMでS3にアクセスに行くためのアカウントとRoleも作成します。S3ReadOnly権限でいいかなと思います。その情報をLambdaでも設定します。

lambda2.png

lambda3.png


Lambdaのコード

こちらはLambdaのコードです。Node.jsを想定しています。

が、ここで注意があります。Nodeのバージョンは設定可能なのですが、折角なので最新(2018年4月時点)の8.10に対応しようと思います。そうするとライブラリとして使おうとしているimagemagickをNode8.10バージョンでビルドしたものを取り込む必要があります。

その手順を書いてもいいですが、結構面倒で挫折してしまうと思われるので、私の方でビルドしたものをGithubにあげておきました。

https://github.com/hardreggaecafe/resizeimage_im_node8.10

使い方は簡単です。 git clone してindex.jsを書き換えた後にindex.jsとnode_moduleディレクトリを zip -r で固めます。これをLambdaのFunction code内のCode Entry TypeからUploadするだけです。zipファイルが10MB以内ならここから直接あげられますが、10MB超えるときには一度S3にzipファイルを置いて、そのパスを指定する必要があります。今回は恐らく10MBにギリギリ収まるかと思います。

lambda4.png

こちらがindex.jsのコードです。適宜必要な箇所は書き換えてください。


index.js

'use strict';

const im = require('imagemagick');
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 filename = event.path.replace(/^\//, "");
var bucket = 'some-bucket';

const width = event.queryStringParameters.w;
console.log("file="+filename+" bucket="+bucket);
const params = {
Bucket: bucket,
Key: 'images/' +filename
};
s3.getObject(params, (err, data) => {
if (err) {
console.log(err);
var message = "Error getting object " + filename + " from bucket " + bucket +
". Make sure they exist and your bucket is in the same region as this function.";
context.fail(message);
} else {
var contentType = data.ContentType;
var extension = contentType.split('/').pop();

im.resize({
srcData: data.Body,
format: extension,
width: width
}, function(err, stdout, stderr) {
if(err) {
context.fail(err);
return;
}
var contentType = filename.endsWith('jpg') ? "image/jpeg" : "image/png";
callback(null, {
"isBase64Encoded": true,
"statusCode": 200,
"headers": { "Content-Type": contentType },
"body": new Buffer(stdout, 'binary').toString('base64')
});
});
}
});
};



API Gateway の設定

次はAPI Gatewayの設定です。

手順の大まかなイメージは

1. filenameというresourceを作成する

2. GETのmethodを作成する

3. Method RequestのところにPath, query parameterとheaderの設定を入れる

4. Integration RequestでProxy設定やLambdaの設定を入れる

5. StageにAPIを上げる

6. その他の細かい設定(パラメータを受け渡し方法やCloudWatchへのログ吐き出しなど)

7. API GatewayとLambdaをまとめてテスト(※)

となります。

早速APIを作成します。

api_gateway1.png

Resource、MethodそしてStageはここから作成。

api_gateway2.png

Resourceの作成方法はこちら。

api_gateway3.png

MethodはGETを指定してMethod Requestの箇所とIntegration Requestをそれぞれ以下のように設定します。

api_gateway4.png

api_gateway5.png

api_gateway6.png

ここからはStageの設定。

api_gateway7.png

StageのLogの設定

api_gateway8.png

最後にAPIGateway全体の設定。

api_gateway9.png

でここまで作ったら本当に動くか確認したくなるのが人情。APIGatewayとLambdaをまとめてテストします。


API Gateway+Lambda+S3のテスト

GETの設定でこういうのがあります。

api_gateway9.5.png

api_gateway10.png

こんな風にfilenameと幅指定(w)とヘッダ情報を入力してテストします。200とバイナリが返ってくれば成功です。ダメな場合何処かで失敗しています。CloudWatchのログの見方も下につけたのでこちらも参照しながら調査してみてください。


CloudFrontの設定

最後はCloudFrontです。Distributionw作成します。

cloud_front1.png

General情報とOrigin情報を以下のように設定します。ここにAPIGatewayのURLが必要になってきます。

cloud_front2.png

cloud_front3.png

cloud_front4.png

Behaviorではキャッシュ情報とQueryStringの設定を行います。

cloud_front5.png

cloud_front6.png

ここまで設定したらGeneralの設定情報にDomainNameという項目があるので

https://xxx.cloudfront.net/somefile.jpeg?w=600

のようなURLをブラウザから入力してみてください。上手く行けばS3の置いている画像が圧縮されて表示されるはずです。

上手く行かない場合は以下のCloudWatchのログを見て何が問題かを調べてみてください。

ここに吐き出されない場合は今までの設定の何処かが間違っています。ログ出力の設定を見直してみてください。


CloudWatchでログを見る方法

動作ログ&デバッグログを確認する方法としてはCloudWatchのログ欄を見るということになります。

以下を参照ください。

clowdwatch1.png

clowdwatch2.png

clowdwatch3.png


結果

resize.jpg

劇的に縮小できました。が、スマホで上げた画像はExifという画像の属性情報の読み取りが上手く行かない関係で上下を正しく認識しないままになってしまいます。これはとりあえず次回の課題ですが縮小には成功しました。

気になる費用ですが、20日間ほど運用して

- API Gateway $48(23万アクセスで、1.5GBのキャッシュ利用)

- CloudFront $6.6

- S3 $1

- Lambda $0

なので占めて月間想定で$83ほど。Fastlyに月額20万も払うことを考えたら遥かに安いです。あっちはExifを認識して自動で回転(AutoOrient)機能とか色々ついているから一概に比較できないですが・・・。

といいつつ実はImageMagickよりも5倍早く圧縮し、AutoOrient機能もついているSharpというNodeModuleを組み込んだバージョンも作りました。その話は次回に書きます。お役に立てれば幸いです。


続きはこちらで

AWS・LambdaにSharpモジュールで画像圧縮を高機能に

https://qiita.com/hardreggaecafe/items/5acd30d42cc59534d72c