透過 AWS ServerlessImageHandler 實現動態圖片處理

#AWS #Serverless #無伺服器 #API Gateway #Lambda #Cloudinary #Image Handler #動態縮圖 #Thumbor
Yusheng Li
技術文章
透過 AWS ServerlessImageHandler 實現動態圖片處理

即便是到了 2019 年,每次長途通勤我仍然是飽受行動網路速度不穩定的折磨,特別是每次搭乘臺鐵經過臺北到板橋區間,時有時無的訊號簡直快要把我逼瘋。這時只能夠透過提前下載好的 Netflix 影集、Audible 的有聲書或是 Kindle 來消磨時間,但若手癢拿起手機點開電子報中的文章,緩慢的載入速度還是立刻把人打回現實。

為了帶來更好的體驗,現在大家開始提倡諸如 Accelerated Mobile Pages (AMP)、Progressive Web App (PWA,漸進式網絡應用程式) 的標準,想要盡可能改善在網路不穩定的情況之下,行動存取的體驗。

早期我們都會被要求透過 Google 的 PageSpeed 服務,來稽核手邊的網頁服務是否有做到基本的優化。去年 (2018) 年末,Google 更是一舉發布 web.dev 這個服務,試圖逼死所有的開發者,透過 Lighthouse 的檢測,直接數值化網站中可能影響瀏覽體驗的瓶頸。

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

在這個前提之下,大家開始汲汲營營的著手試圖消滅網站中可能的效能瓶頸,於是前端開始向我們提出許多調整伺服器設定、優化回應速度等的要求。

今天以圖片作為討論的重心,你最常會在檢測報告中看到跟圖片相關的效能建議包含:

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

令人感動的是 web.dev 的檢測不單單只是提出問題與抱怨,他還直接在檢測報告的後面附上了詳細的指南,幾乎是 Step by Step 的告訴你應該又如何改善,完全就是網站優化的明燈。

Defer offscreen images 算是被抱怨的項目中最容易改善的項目,只要針對不是在 First View 中需要的圖片,進行 Lazyloading 的處理就能搞定,Google 甚至還連處理這件事情的套件都幫你選好了。

其他項目分別是 Properly size images、Efficiently encode images、Serve images in next-gen formats:

  • Property size images 基本上是針對螢幕大小,回傳足夠尺寸的圖片
  • Efficiently encode images 是要求針對圖片品質進行必要程度的無損 / 有損壓縮
  • Serve images in next-gen formats 則是考量到圖片轉換為 Webp 之後可以大約減少 25% 到 34% 的大小,要求在支援 Webp 的瀏覽器中,以 Webp 格式回應圖片請求。

如果處理的圖片項目屬於靜態資源,那麼以上三個項目都可以很輕易的透過現代化的打包工具 (Eg: Webpack) 來達成,但如果今天網站的多數資源來自於 User-Generated Content (UGC,用戶生成內容),就必須得要在使用者上傳圖片之後進行壓縮。一般來說我們會期望這樣相對耗時的處理過程,不要影響到使用者發布內容的體驗,所以通常會在背景程式中進行處理。

但是假設使用者每次會上傳 5 - 20 張不等的圖片,根據需求必須要壓成至少 3 種不同的大小、2 種不同的格式,我們得要設定一定等級的伺服器等待處理使用者的圖片。而這些提前準備好的圖片,有時根本沒有見天日的時刻。尤其當設計改變、圖片顯示的大小改變時,可能得要重新壓過所有的過往圖片。於是我們開始想

如果這些圖片能在需要時即時長出來,那該有多好。

這樣的處理模式可以透過 Cloudinary 之類昂貴的第三方服務做到 Image Processing on the fly 的功能,或是是透過 Thumbor 之類的 Open Source 專案來打造出自己的 On-Demand Imaging service。

要自行打造這樣的服務,你需要

  • 原圖儲存的空間 (諸如 AWS S3 Bucket 或是 Google Cloud Storage)
  • 一個運作 Thumbor API 服務的伺服器
  • 一般來說我們還會希望有個 CDN 服務來快取處理過的圖片

要讓這樣的服務達到最小可用的等級,會需要夠快的處理速度、夠快的平行處理能力。網路上有許多教你如何打造這類服務的教學,其中有不少是透過 AWS Lambda、Google Cloud Functions 等 Serverless 架構來達成優異的效能、與穩定的平行處理表現。

就在我閱讀相關的教學與分享的過程中,我無意間看到了 AWS Serverless Image Handler 的白皮書,這個服務的架構可以說是幾乎與我夢想中的藍圖相當符合:

Serverless Image Handler 架構

  • 原圖存於 AWS S3 Bucket
  • 透過 API Gateway 接收對於圖片的請求
  • 請求由 AWS Lambda 這個 Serverless 服務來執行
    • 自 S3 Bucket 中取出圖片
    • 由 Thumbor 為基礎的 Function 來進行圖片的壓縮、裁圖、格式轉換、加上浮水印等動作
    • 在 API Gateway 的前面以 CloudFront 完成 CDN 服務,快取處理過後的圖片

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

(Serverless Image Handler 架構示意圖,摘自:AWS Serverless Image Handler Implementation Guide)

部署

這份白皮書當然不僅僅止步於概念化的階段,在線上的 AWS Serverless Image Handler Implementation Guide 關於 Deployment 的章節中,可以找到關於如何透過 AWS 提供的 CloudFormation Stack 部署這個服務的詳細教學,首先你應該有 1 個儲存 S3 圖片用的 Bucket,這邊因為篇幅關係,就不對於 AWS 帳戶設定、S3 Bucket 設定做 Step by Step 的解說。

按下文中 「Lauch Solution 的按鈕」,你就會被導引到 AWS CloudFormation 的服務中,並自動帶入這個 AWS 預先幫你寫好的 Templates

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

接著你需要至少填入

  • Origin S3 Bucket:需要處理的圖片儲存的 S3 Bucket 名稱
  • Origin S3 Bucket Region:該 S3 Bucket 位於的區域

若需要 CORS 設定來避免別人在他們的網站中嵌入你的圖片

  • Enable CORS 選項改為 「Yes」
  • CORS Origin 設定為可嵌入圖片的網站位址,這邊必須注意包含域名、協定等資訊都必須要完全符合,以我們官網為例,必須要為 https://5xruby.tw
  • 需要特別注意的是,目前並不支援 Multiple CORS Origin 的設定

關於 Deploy UI 的設定

  • 若勾選 Deploy UI 為 「Yes」、且設定 UI Prefix 為 「demo/」,則部署完成後可在 https://[CLOUDFRONT_ENDPOINT]/demo 中看到一個示範用的 UI 介面
  • 請特別注意,在撰文時的最新 Serverless Image Handler 版本 v 3.1.0 中,當 Deploy UI 的選項被開啟,那麼 Serverless Image Handler 可能會沒有辦法處理設定好的 Origin Bucket 的圖片,若沒有特別想要玩玩看示範用的 UI 介面,建議不要開啟 Deploy UI 的選項

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

  • 依據需求在下一頁的選項中,為這個 CloudFormation Stack 中的資源建立 Tag、指定 IAM 角色或是進階選項,這邊除了建議以有意義的 Tagging 標註這個 CloudFormation Stack 生成的資源外,其他選項沒有需求的話可以暫時保留預設就好。

  • 最後在 Review 步驟中勾選:「I acknowledge that AWS CloudFormation might create IAM resources with custom names.」,同意 AWS Cloudformation 可能幫你建立有自訂名稱的 IAM 角色中並按下「完成」,就會被導引到 CloudFormation 的頁面中了

  • 此時你應該會在頁面中看到剛剛設置的 CloudFormation Stack 在「Create In Progress 的狀態」,並且可以在下面的 Event 中看到詳細的各 Resorce 設定的情形,按官方的文件中表示,整個過程大約需要 25 分鐘。

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

價格

在白皮書的 Overview 章節關於 Cost 的段落,指明了可能會產生的費用包含來自於 S3 Bucket、CloudFront、Lambda 與 API Gateway 的費用,以服務設定於美東區域 (N. Virginia) 為例,每月一百萬次的圖片處理請由、15 GB 的圖片儲存、以及 50 GB 的資料傳輸費用,會產生的費用大致如下:

AWS Service Estimated Price
Amazon API Gateway $ 3.50
AWS Lambda $ 3.10
Amazon CloudFront $ 6.00
Amazon S3 $ 0.23
Total $ 12.83

需要注意的是 AWS 的 PUT 跟 GET Requests 所產生的費用會因為每個網站性質、圖片數量有很不一樣的情況,所以並沒有被計算於其中,另外若有使用到「智慧裁圖」的功能,必須支付 AWS Rekognition 的圖片辨識費用。

真的可以達成需求嗎?

部署完成後,你應該可以在 CloudFront 中看到對應的 Endpoint 設定,或是在剛剛 CloudFormation Stack Detail 的 Output 中找到部署後的 Endpoint。

透過請求 https://[CLOUDFRONT_ENDPOINT]/[THUMBOR_FILTER]/[STORAGE_PATH] 的方式,就能夠動態的達成圖片的動態縮圖。

為了示範,我特意的打開了 Demo UI 的選項:

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

可以在畫面中看到,除了基本的長寬的切圖、格式轉換、 Quality 壓縮等設定外,甚至包含動態浮水印、智慧裁切的方式。

以上面那張大圖(點我看原圖)進行以下的裁切設定:

200x200/smart/filters:format(png):watermark(https://d3soe0wkhi4rba.cloudfront.net/demo/img/aws-logo-watermark.png,120,130,0)
  • 大小:200x200
  • 智慧裁切
  • 轉換為 PNG
  • 以 AWS Logo 在右下角進行浮水印繪製

可以得到如下的結果:

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

關於支援的 Filter 及詳細用法,可以參閱

安全性考量

除了可以透過 CORS 設定來避免別的網站盜用你的圖片以外,也可以透過 Thumbor 的 SafeURL 避免其他任意修改網址中的 Thumbor 參數,以獲得其他樣貌的圖片,要啟用 Safe URL 的功能,可以在對應的 AWS Lambda Function 中設定以下的 Environment Variables:

post-透過 AWS ServerlessImageHandler 實現動態圖片處理

  • ALLOWUNSAFEURL: True
  • SECURITY_KEY: 設定為任何你喜歡的密鑰

更新完 Lambda Function 後,以後的請求必須要加上 HMAC-SHA1 加密過的參數才可以被 Serverless Image Handler 接受,步驟上為:

  • 將請求的 Path 以 HMAC-SHA1 算法與你設定的密鑰加密為 Signature
  • 將 Signature 以 URL Safe Base64 算法編碼
  • 將請求網址改為:https://[CLOUDFRONTENDPOINT]/[ENCODEDSIGNATURE]/[原先的請求 PATH]

更多相關資訊,詳見

黑魔法?

截至目前為止,我們好像都沒有實際看到 AWS 幫我們部署到 Lambda 上面的 Code、也沒有看到 CloudFormation Template 裡面的內容,感覺好像裡面有很多黑魔法發生,其實 Serverless Image Handler 的原始碼開源在 AWS Labs 的 GitHub 帳號中,可以前往 Serverless Image Handler 的 Repo 中檢視完整的原始碼,甚至加上自己需要的功能之後部署自己的版本,在 Repo 的 README 中有關於如何打包新的 Lambda Function 的教學。

Auto WebP

當瀏覽器支援 WebP 這個較好的壓縮格式時,瀏覽器會在請求的 Header 中加上類似的 Accept: image/webp,image/* 設定,Thumbor 便能接收這樣的設定並自動以 image/webp 回傳。

由於 AWS Serverless Image Handler 的 Lambda Function 其實只是幫你運行了 Thumbor,你可以輕易的在 Lambda Environments 中加上新的設定來開啟這樣的功能。

  • 新增新的 Environment Variables AUTO_WEBPTrue
  • 在對應的 CloudFront 中允許 Accept Header 作為 Caching 條件

讓我們透過 CURL 來檢查一下結果,如果是沒有對應 Accept Header 的請求,預設以原始格式回傳:

curl -I "https://CLOUDFRONT_ENDPOINT/500x200/prop/5/8856b82425b926b2d93ef314e893e0a7.jpg"

HTTP/2 200
content-type: image/jpeg
content-length: 25121
date: Tue, 22 Jan 2019 14:00:13 GMT
x-amzn-requestid: 09c9e79d-1e4e-11e9-9aaf-6748fac7bc66
x-amzn-remapped-content-length: 25121
x-amz-apigw-id: T6I9EFhAtjMFnHg=
cache-control: max-age=31536000,public
expires: Wed, 22 Jan 2020 14:00:13 GMT
etag: "3e260286d1529193c185ffa93650ee4d37166ed4"
x-amzn-trace-id: Root=1-5c4721ed-c9282248e551fab4a0798714;Sampled=0
x-amzn-remapped-date: Tue, 22 Jan 2019 14:00:13 GMT
vary: Accept
x-cache: Miss from cloudfront
via: 1.1 fbf94e317a2eadeb551cc7c3ef6e546d.cloudfront.net (CloudFront)
x-amz-cf-id: _Mc6-ERgc6my1EpWsEIIjM-7aUr9ELAqYXCULfrCEz7pYfEFmG1uJQ==

如果是有 Accept Webp 格式的請求:

curl -I --header "accept: image/webp,image/*,*/*;q=0.8" "https://CLOUD_FRONT_ENDPOINT/500x200/prop/5/8856b82425b926b2d93ef314e893e0a7.jpg"
HTTP/2 200
content-type: image/webp
content-length: 17458
date: Tue, 22 Jan 2019 13:55:18 GMT
x-amzn-requestid: 58bfca53-1e4d-11e9-8e00-63a2ea8deee6
x-amzn-remapped-content-length: 17458
x-amz-apigw-id: T6IOpEdrtjMFc5Q=
cache-control: max-age=31536000,public
expires: Wed, 22 Jan 2020 13:55:18 GMT
etag: "a4fdc702a62407d8d08fdefdeaccb2279bf282fd"
x-amzn-trace-id: Root=1-5c4720c4-6329e9b86ed8d27cf33ed564;Sampled=0
x-amzn-remapped-date: Tue, 22 Jan 2019 13:55:17 GMT
vary: Accept
age: 421
x-cache: Hit from cloudfront
via: 1.1 3c61ae3f6890de150027c363309d9c7f.cloudfront.net (CloudFront)
x-amz-cf-id: z0i0Yn3WBtjzMlVDId0Do1GjNUQMIRs3p8hMA9b2i0sqvdarhGUIfg==

可以看到相同大小的圖片請求,硬是小了 30% 的大小,而且透過 Accept Header 判斷回傳格式,還可以確保 HTML DOM 節點的乾淨,是個非常優雅的解決方法。

Known Issues

這個方案也並非盡善盡美,除了上述提到的不支援 Multiple CORS Origin 設定、Demo UI 無法跟現存的 Bucket 並用外,這篇文撰寫時最新的 Serverless Image Handler 版本 3.1.0 所搭配的 Thumbor 版本為 6.5.1,在處理透明 PNG 的智慧裁切時會遇到預期外的錯誤,暫時也不支援 SVG 的圖片格式。

另外由於 Lambda 服務本身對於 Response Body 的限制,若裁切後的圖大於 6MB 可能會造成回傳失敗。

結語

AWS Serverless Image Handler 結合了 S3 Bucket 的儲存服務、API Gateway 跟 AWS Lambda 搭建出動態縮圖 API,並以 CloudFront 作為 CDN 快取服務,可算是一個相當完整且具有潛力的解決方案,雖然目前仍有些功能上的欠缺或是問題,但是使用者可以透過開源的原始碼自行針對自己的需求客製。

Lambda 的平行處理及優異的效能,讓自行搭建 On Demand Image Processing Service 這件事情變為不再遙不可及,也讓工程師能夠專心於提供使用者更好的服務體驗。