こんにちは、岩橋聡吾です。

やってみよう!AWSでWEBサーバー環境構築、久しぶりの続編です。
第4回は「AWS Lambda」「Amazon API Gateway」を使ったサーバーレスな画像リサイズAPIをクラウド上に構築して見たいと思います。

アーキテクチャ・設計概要

今回は以下のようなアーキテクチャで、Clientが指定した任意のサイズの画像を返却するAPIを構築していきます。
スクリーンショット 2018-03-23 14.53.29

◉S3:
クラウドストレージ。
[設計概要] オリジナル画像の置き場を作成。

◉Lambda:
最低限のプログラムのみでアプリケーションの運用が可能なサーバーレスプラットフォーム。他のAWSサービスと連携が可能。
[設計概要] S3から画像データを受け取り、リサイズを実施、それをBase64形式にして返却されるよう作成。

◉API Gateway:
APIの玄関。手軽に柔軟で拡張的なAPIベースの構築が可能。
[設計概要] Lambdaから受け取ったBase64形式の画像データをバイナリに変換して返却されるよう作成。

◉CloudFront:
初回アクセス時に返却したコンテンツをキャッシュ。次回以降は有効期限内であればキャッシュよりコンテンツを返却。
[設計概要] 受けたリクエストにヘッダーを設定してバックエンドに渡すようなリクエスト窓口を作成。

この4つのサービスを連携させて、

 https://xxx.xxx.xxx/{ファイル名}?width={横幅}

のような形で呼び出しができるAPIを構築していきます。

S3

S3(Amazon Simple Storage Service)はインターネット用のクラウドストレージです。よくご存知の方も多いと思いますので細かな説明は割愛して、早速設定していきたいと思います。

今回は、リサイズ元のオリジナル画像の置き場を作ります。

バケット作成

コンソールからS3のページに遷移をして、「+パケットを作成する」ボタンをクリック、モーダルが表示されます。

スクリーンショット 2018-03-10 22.35.47

・パケット名
・リージョン

を設定して「作成」ボタンをクリックします。

オリジナル画像セット

検証用に先ほど作ったバケットに直接画像をアップします(neko.jpg)。

スクリーンショット 2018-03-21 12.17.16

因みに今回はこのねこちゃんの画像をアップしました。

neko

Lambda

AWS Lambda はサーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービスで、最低限のプログラムのみでアプリケーションの運用が可能です。他のAWSサービスのイベントを受け取り、ここで設定した関数をキックさせたりできるので、これまで以上にAWS上でバリエーション豊かなアーキテクチャーを実現することができます。

今回は、リクエストから「画像名」と「指定サイズ」受け取り、S3より「オリジナル画像」を取得し「指定サイズ」に加工し返却するような関数を設定します。

関数の外枠作成

コンソールからLambdaのページに遷移をして、「関数の作成」ボタンをクリックします。

スクリーンショット 2018-03-14 14.49.18

・名前
・ランタイム:C#, Go, Java, Node.js, Python から選択
・ロール:「テンプレートから新しいロールを作成」を選択
・ロール名
・ポリシーテンプレート:「S3 オブジェクトの読み取り専用アクセス権限」を追加

を設定して「関数の作成」ボタンをクリックします。

スクリーンショット 2018-03-14 15.32.00

これでLambdaとS3の連携が設定(LambdaとCloudWatchLogsとの連携はデフォルトで設定されます)され、関数の外枠ができました。

関数の中身作成

【関数コード】

スクリーンショット 2018-03-21 18.36.59

・コード エントリ タイプ:「コードをインラインで編集」を選択
・ランタイム(今回はNode.js 6.10としました)
・ハンドラ(デフォルト)
・index.js(今回は以下の内容で作成)

'use strict';

const imgmgck = require('imagemagick');
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });

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

    // S3にあるオリジナル画像情報
    const s3OrgImageFile = {
        Bucket: 'resize-s3-images',   // S3で作ったバケット
        Key: event.filename,   // リクエストで指定された画像名
    };

    // リクエストで指定された画像横幅
    const width = event.width;


    // S3にあるオリジナル画像取得
    s3.getObject(s3OrgImageFile, (err, data) => {
        if (err) {
            // 取得失敗
            console.log('取得失敗:' + err);
            throw err;
        } else {
            // 取得成功
            console.log('取得成功');
            
            // リサイズ実施
            imgmgck.resize({
                srcData: data.Body,
                width: width
            }, function(err, stdout, stderr) {
                if (err) {
                    // リサイズ失敗
                    console.log('リサイズ失敗:' + err);
                    throw err;
                } else {
                    // リサイズ成功
                    console.log('リサイズ成功');
                    callback(null, new Buffer(stdout, 'binary').toString('base64'));
                }
            });
        }
    });
};

【基本設定】

スクリーンショット 2018-03-21 13.28.32

・メモリ(MB)(今回は256MBとしました)※大きさに応じて課金額が変わります
・タイムアウト(デフォルト)

右上の「保存」ボタンをクリックします。

テスト

Lambdaでは作成した関数をクラウド上でテストすることが可能です。

先ずはテストを設定していきます。
右上の「テスト」ボタンをクリック、モーダルが表示されます。

スクリーンショット 2018-03-21 17.40.04

・イベントテンプレート:「API Gateway Authorizer」を選択
・イベント名(今回はFromApiGatewayとしました)
(イベント内容は以下)

{
    "authorizationToken": "incoming-client-token",
    "methodArn": "arn:aws:execute-api:[region]:[account_id]:[restApiId]/[stage]/[method]/[resourcePath]",
    "type": "TOKEN",
    "filename": "neko.jpg",    
    "width": "300"
}

「作成」ボタンをクリックします。
これでテストの設定ができました。右上のプルダウンより「FromApiGateway」を選択、「テスト」ボタンをクリックしテストを実施します。

スクリーンショット 2018-03-21 17.40.37

上手くいったようです、無事Base64形式の画像データが返却されました。

API Gateway

Amazon API Gateway はスケーラブルなAPIの玄関を作るのに優れています。また簡単に、保守、監視、保護などが行えます。Lambdaとの相性も良く組み合わせることで、サーバーレスなAPIの構築が可能です。

ここでは、リクエストを受け付ける為のエンドポイントとその受け渡し先(先ほど作ったLambda関数)を設定していきます。

APIの外枠作成

コンソールからAPI Gatewayページに遷移をして、「+APIの作成」ボタンをクリックします。

スクリーンショット 2018-03-14 15.17.47

・API名(今回はresize-s3-imagesとしました)
・エンドポイントタイプ:「エッジ最適化」を選択

を設定して「APIの作成」ボタンをクリックします。これでAPIの大枠ができました。

次にリソース(APIのエンドポイント)を作っていきます。
リソースの「/」上で、「アクション」をクリック、「リソースの作成」を選択します。

スクリーンショット 2018-03-14 15.26.11

・プロキシーリソースとして設定:チェックなし
・リソース名
・リソースパス:波括弧でパラメータ定義(リクエスト受け手のLambdaに渡すパラメータ、 今回は{filename}としました)

を設定して「リソースの作成」ボタンをクリックします。これでリソース(APIのエンドポイント)ができました。

API諸設定

【メソッド】
メソッドを作成します。

直前で作ったリソース「/{filename}」上で、「アクション」をクリックし「メソッドの作成」を選択、さらに表示されたプルダウンより「GET」を選択します。

スクリーンショット 2018-03-19 21.33.27

・結合タイプ:「Lambda 関数」を選択
・Lambda プロキシ統合の使用:チェックなし
・Lambda リージョン:前述のLambdaで設定したものと同リージョンを指定
・Lambda 関数:前述のLambdaで設定したものを指定
・デフォルトタイムアウトの使用:チェック

を設定して「保存」ボタンをクリックします。

【統合リクエスト】
以下のような形でAPIの呼び出しを想定している為、{ファイル名}・{横幅}に当たる部分のパラメータをLambdaに渡すように設定する必要があります。

 https://xxx.xxx.xxx/{ファイル名}?width={横幅}

メソッドの実行画面で「統合リクエスト」をクリック、本文マッピングテンプレートを開きます。

スクリーンショット 2018-03-21 10.55.57

スクリーンショット 2018-03-21 21.10.26

・リクエスト本文のパススルー:「テンプレートが定義されていない場合 (推奨)」を選択
(Content-Typeは image/png,image/jpeg,image/gif としてテンプレート内容は以下)


    {
        "filename": "$input.params('filename')",
        "width": "$input.params('width')"
    }

を設定して「保存」ボタンをクリックします。

【統合レスポンス】
LambdaからはBase64形式で画像データが返却されるので、ここでバイナリデータに変換する必要があります。

メソッドの実行画面で「統合レスポンス」をクリックします。

スクリーンショット 2018-03-21 10.55.57

スクリーンショット 2018-03-19 21.49.08

・コンテンツの処理:「バイナリに変換(必要な場合)」を選択

を設定して「保存」ボタンをクリックします。

デプロイとローカル確認

【デプロイ】
デプロイをして公開します。

「アクション」をクリックし「APIのデプロイ」を選択、モーダルが表示されます。

スクリーンショット 2018-03-19 22.11.14

・デプロイされるステージ:「新しいステージ」を選択
・ステージ名(今回はproductionとしました)

を設定して「デプロイ」ボタンをクリックします。

【ローカル確認】

ショルダーメニューから「ステージ」を選択、「production」をクリックします。「URLの呼び出し」を取得できます。

スクリーンショット 2018-03-21 21.59.57

ローカルのターミナル上でcurlコマンド使ってリサイズ画像取得の確認できます。

$ curl --request GET "Accept: image/jpeg" -H "Content-Type: image/jpeg" https://zwhfujd9te.execute-api.us-west-2.amazonaws.com/production/neko.jpg?width=300 > neko_resized.jpg

以下のようなリサイズ画像が取得できていれば成功です。

neko_resized

CloudFront

CloudFrontはAWSクラウド上のCDN(コンテンツデリバリーネットワーク)です。こちらは第3回で紹介させて頂いたので細かな説明は割愛します。

何故CloudFrontが必要か

さて、何故今回のアーキテクチャーにCloudFrontが必要になるのでしょうか。

 https://zwhfujd9te.execute-api.us-west-2.amazonaws.com/production/neko.jpg?width=300

上はここまでに作成してきたAPIのURLです。これをimgタグにセットすれば(もしくは直接ブラウザで叩けば)使える!と思われるかもしれませんが、上手くいきません。この場合以下のようなレスポンスが返されます。

スクリーンショット 2018-03-21 23.28.10

imgタグにセットするだけでは、リクエストヘッダーに「Content-Type:image/xxx」「Accept:image/xxx」がセットされない為、API Gateway側の「統合リクエスト」の「本文マッピングテンプレート」でサポートしていないヘッダーとなり「Unsupported Media Type」が返却されます。

では、API Gateway側の「統合リクエスト」の「本文マッピングテンプレート」を調整すればいいのでは?と考えました。が…確かにこの場合、そのパケットはLambdaにたどり着けるのですが、今度は「統合レスポンス」でバイナリ変換がされなくなります。これは「統合レスポンス」がバイナリ変換するかどうかを、リクエストヘッダー「Content-Type:image/xxx」「Accept:image/xxx」を見て決めている為です。

そこでCloudFrontの登場です。CloudFrontからOrigin(今回でいうと「zwhfujd9te.execute-api.us-west-2.amazonaws.com」)へパケットを転送する際に、カスタムヘッダーを設定できる機能があります。これを使ってリクエストに強制的に「Content-Type:image/xxx」「Accept:image/xxx」のヘッダーをつけてあげると上手くいきそうです。やってみましょう。

CloudFrontにカスタムヘッダーをつけて設定

コンソールからCloudFrontのページに遷移をして、「Create Distribution」ボタンをクリックします。

スクリーンショット 2018-03-23 0.12.32

Web(上)の方の「Get Started」ボタンをクリックします。

【Origin Settings】
スクリーンショット 2018-03-23 0.27.10

・Origin Domain Name:「zwhfujd9te.execute-api.us-west-2.amazonaws.com」を選択
・Origin ID(今回はresize-imagesとしました)
・Origin Protocol Policy:「HTTPS Only」を選択
・Origin Custom Headers:Accept・Content-Type共にimage/png,image/jpeg,image/gifを設定 ※これが一番やりたかったこと

【Default Cache Behavior Settings】
スクリーンショット 2018-03-23 0.15.36

・Query String Forwarding and Caching:「Forward all, cache based on all」を選択 ※これを設定しないとGETパラメータをOriginへ渡せません

を設定して「Create Distribution」ボタンをクリックします。

これでCloudFrontの設定が完了しました。今作ったDistributionを見てみましょう。

スクリーンショット 2018-03-23 12.15.55

上の画面の「General」タブで、配布された「Domain Name」が確認できます。これを使ってブラウザで以下のようなURLを叩いて画像が表示されるかを確認します。

 https://d298y89zsryi7u.cloudfront.net/production/neko.jpg?width=300

スクリーンショット 2018-03-23 12.13.16

上手くいきました(設定したDistributionが反映されるまで数分かかる場合があります)。URLのwidthを任意の値に変えても取得することが確認できました。CloudFrontのキャッシュ機能の説明については割愛していますが、Lambdaでの画像リサイズは決して軽い処理ではないのでCloudFrontで上手くキャッシュができるようにチューニングすることをお薦めします。

これで、サーバーレスな画像リサイズAPIの完成です。

第4回目の最後に

今回のやってみようAWSシリーズでは、「Lambda」を使ったサーバーレスな画像リサイズAPIをクラウド上に構築手順をざっとご紹介しましたが、AWSではまだまだ多くのインフラサービスが提供されており、そのうちのどれもが実際のWEBサービス運営に役立つ強力な武器となります。今後もこのようなシリーズ記事を通じて皆さんに有益な情報を提供できればと思います。
またこのシリーズでこれまでインフラと縁遠かった方でもAWSに対する理解が少しでも深まっていれば幸いです。

ご清覧頂きありがとうございました。
Wedding Parkでは一緒に技術のウエディングパークを創っていくエンジニアを募集しています。
興味のある方はぜひ一度気軽にオフィスに遊びにきてください。

◉シリーズ
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第1回)
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第2回)
・やってみよう!AWSでWEBサーバー環境構築(シリーズ第3回)

◉筆者のおすすめ記事
・【DB設計入門|ER図|MySQL】コンビニレシートから学ぶ!データモデリング手法
・【機械学習入門|Python|scikit-learn】結局何ができる?cheat-sheetから解説してみる篇

Join Us !

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

採用情報を見る