こんにちは。フォトウエディングの撮影スタジオ検索サイト「Photorait(フォトレイト)」を担当しているエンジニアの武田(@takedajs)です。

Photorait(フォトレイト)では入稿された画像をリサイズしてから保存し、公開側に合った大きさの画像を表示しています。動的に画像をリサイズする機能をまだ導入していないので、検証も兼ねて実装してみました。

Lambda、API Gateway、ImageMagickを利用した画像リサイズ機能を書いた記事が過去に投稿されていますが、今回の記事では、Lambda@Edgeとsharpを利用した画像リサイズ機能の実装について書きました。

やってみよう!AWSでWEBサーバー環境構築(Lambda|API Gateway|シリーズ第4回)

実装したもの

このような構成で画像リサイズ機能を実装しました。

リサイズ機能構成図

クライアントがwidthとheightのパラメーターをつけた画像URLにアクセスすると、それに合ったサイズにリサイズした画像を返します。

例: http://xxx.net/cat.jpg?width=500&height=300

Lambda@Edgeについて

Lambda@Edgeを利用することで、CloudFrontのイベントに合わせてLambda関数を実行できるようになります。詳しくは公式ページに書いてあるのでそちらを参照してください。

Lambda@Edge – AWS Lambda

画像リサイズ処理を実装

画像リサイズで利用するsharpの取得と、実際にリサイズ処理を行う実装コードを説明します。

sharp取得

sharpをLambda関数で利用するためには、Amazon Linux上でinstallしたsharpを利用する必要があります。今回は、Dockerで環境を作りその上で取得します。

作業ディレクトリにDockerfileを作成します。

FROM amazonlinux:latest
MAINTAINER takedajs

RUN ["/bin/bash", "-c", "curl -s https://rpm.nodesource.com/setup_8.x | bash -"]
RUN ["/bin/bash", "-c", "yum install -y gcc-c++ nodejs"]

CMD ["npm", "install", "--only=production"]

package.jsonを作成します。

{
    "dependencies": {
        "sharp": "^0.21.3"
    }
}

Dockerイメージを作成します。

$ docker build -t amazonlinux/nodejs .

Dockerコンテナを作成します。

$ docker run --rm -v "$(pwd)":/opt -w /opt amazonlinux/nodejs:latest

作業ディレクトリ配下が以下のような構成になり、node_modules配下にAmazon Linux上でinstallしたsharpが取得されます。

% tree -L 1
.
├── package.json
└── node_modules

実装コード

画像リサイズ処理のコードを書くindex.jsを作業ディレクトリ配下に作成します。

% tree -L 1
.
├── package.json
└── node_modules
└── index.js

index.jsでは、CloudFrontのオリジンレスポンスを受け取り、S3から取得した画像を指定されたサイズにリサイズした結果を返しています。

'use strict';

const AWS = require('aws-sdk');
const querystring = require('querystring');
const sharp = require('sharp');

const S3 = new AWS.S3();
const BUCKET = 's3_bucket_name';

exports.handler = (event, context, callback) => {

    let request = event.Records[0].cf.request;
    let response = event.Records[0].cf.response;

    if (response.status == 304 || response.status == 404 ) {
        callback(null, response);
        return;
    }

    let s3_params = {
        Bucket: BUCKET,
        Key: request.uri.substring(1)
    };

    let query_params = querystring.parse(request.querystring);
    let width = parseInt(query_params.width, 10);
    let height = parseInt(query_params.height, 10);

    S3.getObject(s3_params).promise()
        .then(data => sharp(data.Body)
            .resize(width, height, {
                fit: sharp.fit.inside
            })
            .toFormat('jpeg')
            .toBuffer()
        )
        .then(buffer => {
            response.status = '200';
            response.body = buffer.toString('base64');
            response.bodyEncoding = 'base64';
            response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/jpeg'}];
            callback(null, response);
        })
        .catch(err => {
            console.log(err);
        });
};

Lambda関数作成

実装したコードをLambdaにアップするために、Lambda関数を作成します。

ロール作成

Lambda関数作成に利用するロールをIAMで作成します。

使用するサービスにLambdaを選択します。

ロール作成

S3から画像を取得する権限を付与します。

アクセス権限ポリシー付与

作成したロールの信頼関係に、edgelambda.amazonaws.comを追記します。

信頼関係に追記

これでロールの作成完了です。

Lambda関数作成

Lambda関数作成ページで、以下を選択してください。
Lambda@EdgeはNode.js 6.10 も利用できますが、今回は8系を利用しています。

・ランタイムにNode.js8.10
・既存ロールに先程作成したロール

Lambda関数作成

作業ディレクトリにあるindex.js、node_modulesをzip化します。
作成したzipファイルをLambdaの関数パッケージにアップロードして保存します。
ハンドラはindex.handlerを記述してください。

関数パッケージアップロード

これでLambda関数が作成できました。

Lambda関数のテスト

アップロードした関数パッケージが正しく動くかLambda上でテストします。

テストイベント作成ページで、CloudFrontのイベントテンプレートを選択し、uriとquerystringを修正追加したイベントを作成します。

テストイベントの設定

作成したイベントを選択してテストを実行すると実行結果が表示されます。
bodyに画像情報が表示され、問題なく動作していることが分かります。

テストイベントの実行結果

Lambda@Edgeにデプロイ

CloudFrontのイベントでLambda関数を実行させるために、Lambda関数をLambda@Edgeにデプロイします。

まず、Lambda関数にCloudFrontのトリガーを追加します。

CloudFrontのトリガーを追加

下の方にLambda@Edgeへのデプロイがあるので選択し、追加ボタンを押下します。

トリガーの紐づけ

オリジンにS3を指定したCloudFrontを事前に作成しておき、ディストリビューションで作成したCloudFrontを選択します。
イベントにオリジンレスポンスを選択して、デプロイを押下します。

Lambda@Edgeへのデプロイ

デプロイには数十分かかるのでTwitterでも見て時間を潰してください。

実装結果

紐づけたCloudFrontのstatusがDeployedになればデプロイ完了です。
URLのwidthとheightを指定してアクセスすると、数値に合わせて画像がリサイズされており、正常に動作していることが分かります。
今回の処理では縦横比を固定しています。また、同じURLにアクセスするとキャッシュを返します。

画像リサイズ大

画像リサイズ小

猫ってやっぱり可愛いですね。猫飼いたいけど、ペット不可のマンションなんですよね。。。

最後に

最低限のリサイズ処理と設定で画像リサイズ機能を実装しました。
本番運用時には、正方形で切り取りと背景透過などの実装や、設定周りのチューニングをする必要がありそうです。
また、Lambda@Edgeへのデプロイに時間がかかるので、本番環境で問題が起きた時の対応をどうするかなど検討しなければいけないことが多々ありそうです。
実際に本番導入してそこで得た知見を書いた記事が上がっているので、そちらを参考にさせて頂きたいと思います。

エンジニア大募集中

Wedding Parkでは一緒に技術のウエディングパークを創っていくエンジニアを募集しています。
興味のある方はぜひ一度気軽にオフィスに遊びにいらして頂ければと思います。

ブライダル業界のデジタルシフトを加速させるリードエンジニア候補募集!

参考

Lambda@Edge – AWS Lambda

Amazon CloudFront & Lambda@Edge で画像をリサイズする | Amazon Web Services ブログ

AWS Lambda@Edge で画像をリアルタイムにリサイズ&WebP形式へ変換する – クックパッド開発者ブログ

Node.jsのNative ModuleをAmazon Linux on Dockerでコンパイルする | DevelopersIO

Join Us !

ウエディングパークでは、一緒に働く仲間を募集しています!
ご興味ある方は、お気軽にお問合せください(カジュアル面談から可)

採用情報を見る