Movatterモバイル変換


[0]ホーム

URL:


LoginSignup
12

Go to list of users who liked

9

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS Lambda@Edge + CloudFront でサーバレス画像リサイズサーバ構築

Posted at

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-resize

serverless.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-name

Lambda@Edge

画像のリサイズには sharp ライブラリを利用します。また S3 からのファイルダウンロードはrequest-promise を利用します。S3 SDK のgetObject を使っても良いのですが、S3 以外のオリジンサーバを利用することを想定して通常の HTTP リクエストにしています。

$ npm install --save sharp request-promise

storage.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

AWS_CloudFront_Management_Console.png

Lambda Function ARN はバージョンも含めた完全なものを指定する必要があります。

これですべての設定は完了です。おつかれさまでした。

12

Go to list of users who liked

9
0

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12

Go to list of users who liked

9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?


[8]ページ先頭

©2009-2025 Movatter.jp