Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
AWS Lambda@Edge + CloudFront でサーバレス画像リサイズサーバ構築
AWS Lambda@Edge と CloudFront の組み合わせを使って画像リサイズを動的に行える環境を構築するメモ。
前提
- ServerlessFramework 利用
- リサイズは Sharp を利用
- 画像オリジンサーバは S3 を利用
- webp に対応している UA は webp 変換
- エンドポイント URL:https://example.com/sample/hoge.jpg?size=100x50
- S3 オリジンに保存されているパス: example-bucket/sample/hoge.jpg
- リサイズファイル保持パス: example-bucket/100x50/jpg/sample/hoge.jpg
手順
ServerlessFramework
$ mkdir sample$ cd sample$ npm init$ npm install -g serverless$ npm install --save serverless-plugin-embedded-env-in-code環境変数を埋め込むためにserverless-plugin-embedded-env-in-code というプラグイン作ったので、これを利用します。
ServerlessFramework の環境を用意します。
$ sls create -t aws-nodejs --name cloudfront-resizeserverless.yml
ここで Lambda の設定と権限を作ります。
service:cloudfront-edgepackage:individually:trueexclude:-node_modules/**-lambda_modules/**provider:name:awsruntime:nodejs8.10region:us-east-1memorySize:128timeout:5role:LambdaEdgeRolelogRetentionInDays:30stage:${opt:stage, 'development'}profile:${self:custom.profiles.${self:provider.stage}}plugins:-serverless-plugin-embedded-env-in-codecustom:profiles:development:profile-nameproduction:profile-nameotherfile:environment:development:${file(./conf/development.yml)}production:${file(./conf/production.yml)}functions:storage:handler:storage.redirectresize:handler:resize.performembedded:files:-resize.js-resize-func.jsvariables:File_Original_Url:${self:custom.otherfile.environment.${self:provider.stage}.File_Original_Url}Cache_S3_Bucket:${self:custom.otherfile.environment.${self:provider.stage}.Cache_S3_Bucket}timeout:20memorySize:512package:include:-node_modules/**resources:Resources:LambdaEdgeRole:Type:AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Statement:-Effect:AllowPrincipal:Service:-lambda.amazonaws.com-edgelambda.amazonaws.comAction:-sts:AssumeRolePolicies:-PolicyName:${opt:stage}-serverless-lambdaedgePolicyDocument:Version:'2012-10-17'Statement:-Effect:AllowAction:-logs:CreateLogGroup-logs:CreateLogStream-logs:PutLogEvents-logs:DescribeLogStreamsResource:'arn:aws:logs:*:*:*'-Effect:"Allow"Action:-"s3:PutObject"-"s3:GetObject"-"s3:PutObjectAcl"Resource:-"arn:aws:s3:::example-bucket-name/*"conf/development.yml
コードに埋め込みたい環境変数はこちらで管理します。
File_Original_Url:https://s3.amazonaws.com/example-bucket-nameCache_S3_Bucket:example-bucket-nameLambda@Edge
画像のリサイズには sharp ライブラリを利用します。また S3 からのファイルダウンロードはrequest-promise を利用します。S3 SDK のgetObject を使っても良いのですが、S3 以外のオリジンサーバを利用することを想定して通常の HTTP リクエストにしています。
$ npm install --save sharp request-promisestorage.js
まず CloudFront へのリクエスト時に URL をパースしてあげます。ここで行っているのは大きく2点。
?size=100x50のクエリストリングをパースして、定義済みのサイズに一番近いものに正規化する- webp 対応のブラウザの場合は webp に変換するように URL を修正
'use strict'constquerystring=require('querystring')constvariables={allowedDimension:[{w:128,h:128},{w:256,h:256},{w:512,h:512},{w:1024,h:1024},{w:2048,h:2048},{w:3072,h:3072},{w:4096,h:4096}],defaultDimension:{w:128,h:128},variance:20}constgetExt=url=>{constmatch=url.match(/\.((gif|jpg|jpeg|png)+)/)if(match){returnmatch[1]}return''}constbalanceSize=size=>{let[width,height]=size.split('x')letmatchFound=falseletvariancePercent=(variables.variance/100)for(letdimensionofvariables.allowedDimension){letminWidth=dimension.w-(dimension.w*variancePercent)letmaxWidth=dimension.w+(dimension.w*variancePercent)if(width>=minWidth&&width<=maxWidth){width=dimension.wif(height){height=dimension.h}matchFound=truebreak}}if(!matchFound){width=variables.defaultDimension.wheight=variables.defaultDimension.h}return[width,height]}constnormalizedExt=(ext,headers)=>{constaccept=headers['accept']?headers['accept'][0].value:''if(accept.includes('webp')){url.push('webp')}else{url.push(ext)}}module.exports.redirect=(event,context,callback)=>{constrequest=event.Records[0].cf.requestconstext=getExt(request.uri)constparams=querystring.parse(request.querystring)constoriginalUri=request.urileturl=[]if(params.size){const[width,height]=balanceSize(params.size)url.push(width+'x'+height)url.push(normalizedExt(ext,headers))}else{url.push('original')url.push(ext)}url.push(originalUri)constrewriteUri='/'+url.join('/')request.uri=rewriteUricallback(null,request)}resize.js
storage.js で正規化されたリクエスト URL を受け取って実際のレスポンスを返却します。すでに S3 にリサイズファイルのキャッシュがある場合はそれをそのまま返します。なければ S3 からファイルを取得して Sharp でリサイズ後に S3 に保存します。
'use strict'constAWS=require('aws-sdk')constS3=newAWS.S3({signatureVersion:'v4'})constSharp=require('sharp')constrequest=require('request-promise')constdownload=(url)=>{returnrequest({url:url,encoding:null})}constresize=(body,format,width,height)=>{returnSharp(body).resize(width,height).toFormat(format).toBuffer().then(buffer=>{returnbuffer})}constsave=(body,format,key)=>{returnS3.putObject({Body:body,Bucket:process.env.Cache_S3_Bucket,ContentType:'image/'+format,CacheControl:'max-age=31536000',Key:key,StorageClass:'STANDARD',ACL:'public-read'}).promise()}constsetResponse=response=>{response.status=200response.body=body.toString('base64')response.bodyEncoding='base64'response.headers['content-type']=[{key:'Content-Type',value:'image/'+format}]response.headers['cache-control']=[{key:'Cache-Control',value:'max-age=31536000'}]returnresponse}module.exports.perform=(event,context,callback)=>{letresponse=event.Records[0].cf.responseconstrequest=event.Records[0].cf.requestif(response.status==='404'){constmatch=request.uri.match(/^\/(.+?)\/(.+?)\/(.+)$/)constsize=match[1]constformat=match[2]constkey=match[3]constoriginalUrl=`${process.env.File_Original_Url}${key}`if(size==='original'||format==='gif'){download(originalUrl).then(body=>{save(body,format,request.uri).then(()=>{response=setResponse(response)callback(null,response)})})}else{resizeFunc.download(originalUrl).then(body=>{let[width,height]=size.split('x')width=width!=='undefined'?width:undefinedheight=height!=='undefined'?height:undefinedresize(body,format,width,height).then(body=>{save(body,format,request.uri).then(()=>{response=setResponse(response)callback(null,response)})})})}}else{callback(null,response)}}デプロイ
Lambda@Edge は npm パッケージをバンドリしてアップロードする必要があります。Sharp ライブラリはネイティブライブラリなので Mac などでインストールしたパッケージは Lambda では利用できません。そこで Docker を使って Sharp だけ置き換えるようにします。
Sharp を Lambda で使う
$ mkdir lambda_modules$ cd lambda_modules$ npm init$ npm install --save sharp request-promise serverless-plugin-embedded-env-in-code$ rm -rf node_modules/sharp$ docker run -v "$PWD":/var/task lambci/lambda:build-nodejs8.10 npm install$ rm -rf ../node_modules && mv ./node_modules ../ServerlessFramework デプロイ
$ sls deploy -v --stage development --aws-profile profile-nameこれでようやく Lambda@Edge のデプロイが完了しました。なんか長いですね。あと一息です。
CloudFront の設定
CloudFront のコンソールから「Behaviors」を開き作成します。
- Path Pattern:
/sample/* - Lambda Function Associations:
- Origin Response: resize.js
- Viewer Request: storage.js
Lambda Function ARN はバージョンも含めた完全なものを指定する必要があります。
これですべての設定は完了です。おつかれさまでした。
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme

