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 を利用して構築してみたいと思います。
以下のような構成で構築していきます。
Lambda の作成
AWSコンソールにてLambdaの関数作成で「一から作成」で ruby を選択し S3 の読み取りアクセス権を付与しておきます。
リサイズ関数の作成
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
を作りましょう。
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['BUCKET'], :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
に合わせるのを忘れないようにしましょう。
ちなみに Lambda 環境変数はコンソール上で設定します。
API Gateway の設定
次に Lambda の関数を呼び出すための API Gateway を作成します。
Lambda のページの左側の API Gateway を押せば作成できます。
「新規API作成」 で今回は画像を扱うのでバイナリメディアタイプに image/jpeg
を設定して作成します。
ただこのままだと https://××××××.amazonaws.com/image/image_resize
に設定されてしまっています。今回は https://××××××.amazonaws.com/image/filename?width=100
などでアクセスしたいので API Gateway のページに移動して不要な「image_resize」を削除します。上のアクションから「リソースの削除」をします。
次に同様にアクションから「リソースを作成」していきます。
「プロキシリソースとして設定」チェックを入れてパラメータ名を「filename」にしてリソースの作成します。これでhttps://××××××.amazonaws.com/image/filename
でアクセスすることができます。
次のページで実行したい Lambda 関数名「resize_image」を記入して保存します。
GETメソッドを選択して「filename」に S3 で公開されている画像のファイル名を指定する。
レスポンス本文にバイナリが返っていることが確認できれば成功です。
また正しく変換されているか確認するために、 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/jpeg
と Content-Type: image/jpeg
も追加しておきます。
あとはDomain Name
に表示されている ××××××.cloudfront.net/sample.jpeg?width=100
にアクセスして画像が表示されれば成功です。
初回アクセスは重いものの CloudFront でキャッシュしてあげれば問題も解消されるので画像のリサイズ問題の一つの解決策にはなるのではないでしょうか?
おわりに
今回初めて Lambda 触りましたがちょっとしたものをサクッと作るのには非常に良いですね。ただハマりどころとして 1MB のまでしかレスポンスが扱えない点は盛大にはまりました。何はともあれ普段から慣れ親しんでいる Ruby で Lambda が書けるのようになったのはいいことですね。いろいろ作りたい欲を掻き立てられますね!!
エイチームグループでは、serverless や マイクロサービス構成 など新しいことにチャレンジしています。エンジニアとしてチャレンジ精神旺盛な一緒に働ける仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/
Ateam Lifestyle x cyma Advent Calendar 2018 の14日目は @k_hajime さんです。よろしくおねがいします!
参考
-
構成など設定周りを参考にさせていただきました。
-
公式チュートリアル