Ruby
AdventCalendar
CloudFront
lambda
adventcalendar2018

AWS Lambda Ruby を利用して 1時間で作る画像のリサイズ API

Ateam Lifestyle x cyma Advent Calendar 2018の13日目は、株式会社エイチームライフスタイルのエンジニア 新卒1年目の@kiitan が担当します。

2018年11月30日 に Amazon Web Services の 年次イベント「AWS re:Invent 2018」でついに Lambda Ruby Runtime が発表されました。そこで早速触ってみました。


やりたいこと

今回やりたいことは、アクセスされるURLごとに動的に画像をリサイズして配信する機構を Lambda Ruby を利用して構築してみたいと思います。

以下のような構成で構築していきます。

スクリーンショット 2018-12-12 19.29.24.png


Lambda の作成

AWSコンソールにてLambdaの関数作成で「一から作成」で ruby を選択し S3 の読み取りアクセス権を付与しておきます。

スクリーンショット 2018-12-12 5.17.09.png


リサイズ関数の作成

Lambda は AWSコンソール上でも書けるのですが、 標準ライブラリ以外 を利用する場合は 他の言語同様ローカルでビルドする必要があります。

今回は、Gem を利用するので Gemfile を以下のように作成します。

source 'https://rubygems.org'

gem 'aws-sdk-s3'
gem "mini_magick"

bundle install 時にローカルにインストールされるように --path を指定してインストールします。

bundle install --path vendor/bundle

次に画像リサイズするための関数 handler.rb を作りましょう。


handler.rb


require 'aws-sdk-s3'
require 'base64'
require 'mini_magick'

def resize_image(event:, context:)
s3_client = Aws::S3::Client.new(
:region => ENV['REGION'],
:access_key_id => ENV['ACCESS_KEY'],
:secret_access_key => ENV['SECRET_ACCESS_KEY']
)
# S3 から 画像を取り込み
image_file = s3_client.get_object(:bucket => ENV['BUKET'], :key => event['pathParameters']['filename']).body.read
image = MiniMagick::Image.read(image_file)
# 今回は写真を扱う前提なので jpeg に変換
image.format('jpeg') unless image.details['Mime type'] === "image/jpeg"

# アスペクト比を維持してリサイズ
width = event['queryStringParameters']['width'].to_i
height = (width/image.width.to_f * image.height).round.to_i
image.resize("#{width}x#{height}")

# レスポンス作成
{
statusCode: 200,
headers: { 'Content-Type': 'image/jpeg' },
body: Base64.encode64(image.to_blob),
isBase64Encoded: true,
}
end


/filename?width=100 のようなURLでアクセスされることを想定しています。

ファイル名を示すURLパラメータは、 event['pathParameters']['filename'] から取得できます。 通常のパラメータは event['queryStringParameters']に格納されます。

次にレスポンスの部分を解説します。画像を文字列に変換して必要があるので BLOB型 に変換した画像を Base64エンコード しています。

必ず忘れずに isBase64Encoded: true を付与してあげてください(ここではまりました...)。

作った関数ファイルと vendor 以下を zip 圧縮します。

zip -r handler.zip handler.rb vendor

zip 圧縮したものを AWSコンソール 上からアップロードしましょう。この時にハンドラを file_name.function_nameに合わせるのを忘れないようにしましょう。

スクリーンショット 2018-12-12 6.06.40.png

ちなみに Lambda 環境変数はコンソール上で設定します。

スクリーンショット 2018-12-12 6.25.46.png


API Gateway の設定

次に Lambda の関数を呼び出すための API Gateway を作成します。

Lambda のページの左側の API Gateway を押せば作成できます。

スクリーンショット 2018-12-12 6.30.14.png

「新規API作成」 で今回は画像を扱うのでバイナリメディアタイプに image/jpeg を設定して作成します。

スクリーンショット 2018-12-12 6.29.55.png

ただこのままだと https://××××××.amazonaws.com/image/image_resize に設定されてしまっています。今回は https://××××××.amazonaws.com/image/filename?width=100 などでアクセスしたいので API Gateway のページに移動して不要な「image_resize」を削除します。上のアクションから「リソースの削除」をします。

スクリーンショット 2018-12-12 6.44.42.png

次に同様にアクションから「リソースを作成」していきます。

「プロキシリソースとして設定」チェックを入れてパラメータ名を「filename」にしてリソースの作成します。これでhttps://××××××.amazonaws.com/image/filenameでアクセスすることができます。

スクリーンショット 2018-12-12 6.47.05.png

次のページで実行したい Lambda 関数名「resize_image」を記入して保存します。

スクリーンショット 2018-12-12 6.48.45.png

ここまで来るとテストができるので、テストしてみましょう。

スクリーンショット 2018-12-12 6.51.09.png

GETメソッドを選択して「filename」に S3 で公開されている画像のファイル名を指定する。

レスポンス本文にバイナリが返っていることが確認できれば成功です。

スクリーンショット 2018-12-12 7.01.48.png

また正しく変換されているか確認するために、 curl でアクセスしてリサイズされた画像ファイルが落ちてきていることを確認しておきましょう。アクセスするためのURLは、Lambda のページで確認できます。画像がダウンロードできていれば成功です。

curl -v --request GET -H "Accept: image/jpeg" -H "Content-Type: image/jpeg" "https://××××××.amazonaws.com/image/sample.jpg?width=100" >> sample.jpeg

しかしここで終わりではありません。

https://××××××.amazonaws.com/image/sample.jpg?width=100 にブラウザからアクセスしても画像は表示されません。

API GateWay ではブラウザからのアクセス時に Accept: image/jpeg が設定されないのが原因です。

そこで CloudFront を利用します。


CloudFront の設定

新規作成をしていきます。

「Origin Domain」 に API Gateway の https://××××××.amazonaws.com の部分を設定します。「Origin Path」 に image を指定することで https://××××××.cloudfront.net/sample.jpegでアクセスすることができるようになります。

カスタムヘッダーに Accept: image/jpegContent-Type: image/jpegも追加しておきます。

スクリーンショット 2018-12-12 7.37.16.png

あとはDomain Nameに表示されている ××××××.cloudfront.net/sample.jpeg?width=100 にアクセスして画像が表示されれば成功です。

スクリーンショット 2018-12-12 7.43.37.png

初回アクセスは重いものの CloudFront でキャッシュしてあげれば問題も解消されるので画像のリサイズ問題の一つの解決策にはなるのではないでしょうか?


おわりに

今回初めて Lambda 触りましたがちょっとしたものをサクッと作るのには非常に良いですね。ただハマりどころとして 1MB のまでしかレスポンスが扱えない点は盛大にはまりました。何はともあれ普段から慣れ親しんでいる Ruby で Lambda が書けるのようになったのはいいことですね。いろいろ作りたい欲を掻き立てられますね!!

エイチームグループでは、serverless や マイクロサービス構成 など新しいことにチャレンジしています。エンジニアとしてチャレンジ精神旺盛な一緒に働ける仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。

https://www.a-tm.co.jp/recruit/

Ateam Lifestyle x cyma Advent Calendar 2018 の14日目は @k_hajime さんです。よろしくおねがいします!


参考