06 December 2015

今天跟大家介紹一個好用的縮圖伺服器 thumbor ,我們已經在線上用了一陣子,還真是方便的服務,我想即使現在用不著,也應當加入你的工具箱裡。

thumbor 是一個 python based 的 server,原理大致上是你有一個圖源的伺服器,例如:

http://example.com/foo/my-image.jpg

只要將圖源的網址經過 thumbor 後,就能得到任意裁切大小的新圖

http://my-thumbor.foo.com/200x100/http://example.com/foo/my-image.jpg

my-thumbor.foo.com 是 thumbor 的網址,後面接上 200x100 的路徑,最後再加上圖源網址,這樣回傳的圖檔就是 200x100 大小的新圖了。

ok,就是這麼簡單的概念,看到這裡你也能想像 thumbor 的最大用途是什麼了:

thumbor 能夠即時的產生任意大小的縮圖,適合不同的使用情境

縮圖 (thumbnail) 這種概念已經很久了,用戶上傳一張照片,讓伺服器裁切成幾份小圖也是工程師常做的任務。幾年前還好,大概只要裁一兩份小圖就夠網頁用了,但現在有各種不同尺吋的手機,還有平板,而桌機也有了 hi-dpi 的需求。瞬時一兩張小圖不夠用了,而且手機還是用 3/4G 網路,裁了太大的圖用戶就是等等等.... 等個沒完沒了,而且也吃掉用戶網路的用量 (現在 4g 沒吃到飽了)。

這些新的需求浮現,所以像 thumbor 這樣專屬處理縮圖的應用就變得很重要了。透過 thumbor,我們可以滿足像是下列的情境:

  • 當用戶是用 4" 小手機,就餵給他 100x200 的小圖
  • 當用戶是 6" 高檔 iPhone,就給他高解析 200x400
  • 當用戶用的是橫著放的平版,就給他 200x100 不同長寬比例的圖
  • … 等等。

安裝 thumbor

下面將介紹如何安裝 thumbor 伺服器到虛擬機裡 (vagrant),請預先安裝好 vagrantvirtualbox、以及部署工具 ansible 。詳細的安裝步驟就不提了,前面的連結都有詳細的安裝法。

1. 啟動新的 VM (ubuntu 14.04)

開啟個目錄 thumbor-tutorial

$ mkdir thumbor-tutorial
$ cd thumbor-tutorial

建立 vagrant 的設定檔 Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.provider "virtualbox" do |v|
    v.memory = 512 
  end
  config.vm.box = "ubuntu-14.04"
  config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
  config.vm.define "thumbor" do |v|
    v.vm.network "private_network", ip: "192.168.33.99"
  end
end

建立好後,啟動 vagrant vm

$ vagrant up

# 等一陣子,沒錯誤的話就能 ssh 進 vm
$ vagrant ssh

vagrant up 的指令按照 Vagrantfile 設定檔的指示,建立一個 512MB ram 的 linux VM,安裝 ubuntu 14.04,它的 ip 是 192.168.33.99

2. 設定 ansible 自動化安裝 thumbor

成功後,接下來就是要使用部署工具 ansible 幫我們自動化安裝 thumbor

# 先下載別人寫好的 thumbor 部署設定
$ ansible-galaxy -p roles install savagegus.thumbor

# 成功後會下載到 `roles` 這個目錄裡,會有兩個設定

ansible-galaxy 裡有許多別人貢獻、預先做好的安裝設定,很多常見的應用都已經有人做好了,thumbor 也不例外,這裡我們使用的是 savagegus.thumbor

接下來是指定 ansible 部署 thumbor 到 vagrant 的 VM 裡。

  • 先新增 inventory 檔,裡面是放要安裝的主機設定,這裡是指向 剛剛建立的 vagrant vm 192.168.33.99
thumbor ansible_ssh_host=192.168.33.99

## 指定所以設定的主機都使用 vagrant 這 user、及其 private ssh key
[all:vars]
ansible_ssh_user=vagrant
ansible_ssh_private_key_file='.vagrant/machines/thumbor/virtualbox/private_key'

注意:此設定檔後半段關於 user/key 的設定只限於 vagrant 使用,正式上線時不需要。

  • 再來建立安裝 thumbor 的設定檔 site.yml (在 ansible 的術語叫 playbook)
- hosts: thumbor 
  roles:
    - role: savagegus.thumbor
      sudo: True
      thumbor_results_storage: thumbor.result_storages.file_storage
      thumbor_bind_address: '0.0.0.0'

安裝設定本身很短,因為大部份都交給剛才下載的 savagegus.thumbor 處理好了。不過,原本的設定只能從 localhost 連進 thumbor 伺服器 (按設定檔它希望你經過 nginx),這裡我們覆蓋掉預設值,讓所有網址都能直接連進 thumbr 伺服器,方便測試。

部署 thumbor

呼~ 要開始正式部署了,一個 ansible 指令搞定:

$ ansible-playbook -i inventory site.yml

如果一切正常無誤的話,你會看到 ansible 一步步下載、安裝、設定 thumbor 所有需要的套作。savagegus.thumbor 這個 role 也包含了安裝 nginx 伺服器,所以你也會看到 nginx 的安裝過程。

thumbor 是個 python 套件,理論上只要 pip install thumbor 就應該能裝完,但是我們要部署的是一個伺服器,伺服器總是要設許多權限、log 檔位置、啟動 script… 等等諸多繁雜的工作,這些工作就是讓 ansible 來做了,有興趣可以看看 roles/savagegus.thumbor/tasks/main.yml 就知道部署 thumbor 的所有細節

安裝完後,可以在瀏覽器測試 thumbor 是否正常運作:

# 正常的話會看到 `WORKING` 的結果
http://192.168.33.99:8888/healthcheck

# 正常的話會看到一張 200x200 的圖
http://192.168.33.99:8888/unsafe/http://dummyimage.com/200x200.png

# 注意上述的測試都是跳過 nginx,直接測 thumbor 本身 (在 port 8888)

如果有任何問題的話,可以用 vagrant ssh 連進 VM,查看 /var/log/upstart/thumbor-worker-8888.log 錯誤訊息

使用 thumbor

讓我們來試試幾個 thumbor 的功能:

  • 範例的源圖網址:
    https://http.cat/100.jpg

  • crop 成 200x300 圖片:
    http://192.168.33.99:8888/unsafe/200x300/https://http.cat/100.jpg

  • 轉成 png:
    http://192.168.33.99:8888/unsafe/filters:format(png)/https://http.cat/100.jpg

  • 圓角 + 灰階:(產生的網址太長,下面多加了斷行)

  http://192.168.33.99:8888/unsafe/
         filters:round_corner(40,255,255,255):grayscale()/
         https://http.cat/100.jpg

thumbor 的基本功能是縮圖與裁切,你也看到了在網址上加上 /200x300/ 這種大小就能很簡單做到,但 thumbor 還有強大的濾鏡功能,上面就展示了轉換格式,以及裁圓角與加上灰階等等進階功能,而且能任意組合,你可以在 thumbor 的 wiki 上查到更多的濾鏡說明 。濾鏡的語法就是在路徑上加上 /filters:foo:bar/ 而已,非常簡單。

secure thumbor

眼尖的你可能發現了,上面的測試網址都帶有 /unsafe/ 的路徑,這在 thumbor 裡是指網址不需經過驗證,能直接轉圖。這在測試環境下很方便,不過正式上線的話,網址沒驗證就很容易被別人惡用 (thumbor 是個任意轉圖、快取的伺服器,沒驗的話別人可以偷接)。

thumbor 提供兩個方式驗證:

  • 第一個是限定圖源網址,比方說你只限定上游的圖源必須是你自家的網站,這樣就能保證不能偷接。我們可以修改 thumbor 的設定檔

roles/savagegus.thumbor/templates/thumbor.conf.j2:

## ...etc

ALLOWED_SOURCES = [
  's3.amazonaws.com',
  'http.cat'
]

## ...etc

預設值是不限任何網站,而上面的範例我們修改成限定圖源網站必須是 aws s3 與 http.cat。

  • 第二個是直接驗證網址本身,我們需要設定一個私有鍵,並且關閉 /unsafe/ 的功能,我們可以在

roles/savagegus.thumbor/templates/thumbor.conf.j2 裡,關閉 unsafe 的功能:

## ...etc

ALLOW_UNSAFE_URL = False 

## ...etc

私有鍵的設定檔則放在 roles/savagegus.thumbor/templates/thumbor.key.j2 裡,長度不居。下面的值僅供本文範例使用,請不要直接抄用在你的正式主機上:

Vzt9X7LHbcyGsaAzVLV54rfuEpEQWspbynaRZeq9+WCBfC7iFAiSThYHI79zwKt

建議:你可以用 openssl rand -base64 48 指令產生高強度的任意字串,這裡的 48 是 byte 長度

好了,ansible 設定檔改完之後,就是再部署一次進 vagrant vm:

$ ansible-playbook -i inventory site.yml

一鍵搞定部署!爽!而新的 thumbor.key 則會安裝到 /etc/thumbor.key 路徑

部署完畢之後,剛才上述範例的那些 /unsafe/ 網址不能再使用了,用了 thumbor 只會吐 status 400 bad request 給你。你必須使用加上驗證碼的網址,像是剛才的 crop 200x300,就會變成:

http://192.168.33.99:8888/lNmQrS_bz-er8L6nIO1qFPgGOm4=/
       200x300/https://http.cat/100.jpg

unsafe 那段需要填入經 HMAC 算過的驗證碼。如此一來,沒有私有鍵的人便無法任意產生合法的 thumbor 網址。

thumbor-url 與 API

前述的驗證碼需要經過一定手續計算而得,為了方便測試,thumbor 有提供 command line 工具 thumbor-url 可以產生驗證網址。因為我們的 thumbor 裝在 vagrant 裡,所以要進入 vagrant vm 內去執行

#進入 vm
$ vagrant ssh   

#進入後,執行 thumbor-url 產生網址路徑
$ thumbor-url -l /etc/thumbor.key /200x300/https://http.cat/100.jpg

如果一切正常的話,terminal 會輸出

URL:
/lNmQrS_bz-er8L6nIO1qFPgGOm4=/200x300/https://http.cat/100.jpg

只要將該路徑前面冠上 thumbor 的主機就是個合法的 thumbor 網址。

command line 工具只限測試使用,正式在 app 裡運用時,必須透過 API 的幫忙,不然自己算 HMAC 太累了。thumbor 網站列出了很多平台的函式庫 ,例如 android /java 用的 Pollexor,使用上就非常簡潔:

Thumbor.create("http://192.168.33.99:8888/", 
               "... thumbor private key...")
       .buildImage("https://http.cat/100.jpg")
       .crop(10, 10, 90, 90)
       .resize(40, 40)
       .smart()
       .toUrl();

Pollexor 採用 fluent API 設計概念,裁剪及套用濾鏡都相當的直覺好用。

smart thumbor

thumbor 的縮圖最強的地方是它有智慧的功能。一般在裁剪圖時,大小不符合就是剪裁圖的正中央,而 thumbor 內建進階的圖形辨識功能,可以偵測人臉的位置或是影像的核心部位。要開啟這個功能,我們要替 thumbor 安裝 opencv 函式庫

  • roles/savagegus.thumbor/tasks/main.yml 加上 opencv 的安裝
- name: install pgmagick deps
  ...

# 加上 opencv 的安裝  
- name: install opencv deps
  apt: pkg={{item}}
  with_items:
    - libcurl4-openssl-dev
    - libopencv-dev
    - libmagick++-dev
    - graphicsmagick
    - python-opencv

- name: install pgmagick
  ....
  • 然後,到 roles/savagegus.thumbor/templates/thumbor.conf.j2 裡打開人臉辨識與重點偵測的功能
DETECTORS = [
      'thumbor.detectors.face_detector',
      'thumbor.detectors.feature_detector'
    ]

設定好後,一鍵完成部署:

$ ansible-playbook -i inventory site.yml

部署完後,就可以在裁切圖時加上 /smart/ 這個功能,正確裁剪到人臉的位置:

原圖

  • 無 smart,裁切成 300x100 -

無 smart

http://192.168.33.99:8888/UsXPegr1vNw-sUO5wJKpq9Wy8FM=/
       300x100/https://http.cat/405.jpg
  • 開啟 smart,同樣裁切成 300x100

開啟 smart

http://192.168.33.99:8888/HZBfomhL9aMALrBdHEhM_asJMs8=/
       300x100/smart/https://http.cat/405.jpg

上面的簡單測試可以看出 smart 裁圖的中心移到了人臉上。有了智慧裁圖之後,運用就更自由了,例如在手機上,就能裁成直的圖,而在平板上,就能裁成橫的,都不會失去太多圖片的核心部位。

注意,thumbor 產生過的網址會快取在磁碟一份,之後就不會再重新算一次,所以如果想看同網址但不同的結果的話,要清除 /tmp/thumbor/result_storage 路徑

其他

這裡展示的步驟,只是可以安裝到 VM 裡測試而已,雖然 ansible 已經讓部署簡化許多,但要正式上線的還有許多設定需要調整,例如圖片快取的暫存路徑是設在 /tmp/ 下,這一定不夠放,需要額外的安排。另外圖片快取過期的時間你也要考慮一下,thumbor 是可以設定圖片的過期時間 (會反應在 http cache control header 上),過期後再存取一次也會重新產生圖,不過過期的圖片卻不會自動刪除,所以跑久了磁碟很快就佔滿了,這也要自行處理。

本文所展示的 thumbor 功能只是其中一小部份而已,例如 thumbor 本身也可以當圖源網站 (圖上傳到 thumbor 本身,後面可接資料庫),這部份我本身沒有實務經驗,所以這裡我就不提了。更多的資料請見 thumbor github

Real world thumbor

我已經在不同的服務上正式的使用 thumbor。兩個服務用途和目的相差很大。

  • 案例一:縮圖給手機 app 用

這個應用是用戶會上傳自己拍的圖,而且一次多張。當圖片是一張、二張、三張時,呈現的編排方式會不同,所以需要直裁切、橫裁切兩種變化,透過 thumbor 之後,就能即時產生不同方向的裁圖,達到最佳顯示效果和下載速度。而且,像是圖片的編排方式其實設計師常常會更動設計的,有了 thumbor 之後便能應付未來臨時變動的設計。這個應用裡,我們也開啟了 smart 功能,因為大部份的用戶圖片都是自拍,而 thumbor 框到臉的機率大概在 8 成上下,還不錯。

這個服務的部署架構是這樣的:

  AWS cloud front    (CDN)
        v
 EC2 load balancer   
        v
 3 * thumbor server  (ec2)
        v
      AWS S3         (圖源) 

最前面掛了 cloud front CDN,後面則是經過 load balancer,最後才到 thumbor,thumbor 雖然裝了三台,但因為前端有 CDN 擋著,所以流量很低,不需要裝到三台這麼多。只是一般 high availiabilty 的架構都是習慣以 3 台做為基本單位去部署的,所以我們才會運用到三台。

  • 案例二:最佳化 png 圖檔

另外一個服務是我個人的 pet project,用戶的使用習慣是上傳遊戲的截圖,所以大約有 40% 的圖片都是 png。png 圖檔超級大啊,截個圖隨便都是超過 500KB,不僅下載慢、頻寬也都吃光光了。

為了應付這個問題,特別去找縮小 png 圖的工具,這個領域裡 pngquant 是最知名的工具了,它的作法是將 24bit png 轉成 8bit png ,然後用特別的演算法和 dither 讓圖片看起來幾乎一模一樣。經過這道手續,原本 600KB 的 png 大小就降成 1/3,200KB。這實在是差很多。

我原本打算是在上傳圖片的過程中就先用 pngquant 處理好再上傳的 (Java app server),可惜 pngquant 並沒有 java 版本,這表示要去寫 JNI 或是呼叫外部 process 去處理了,這寫起來會死人。後來往 thumbor 的方向去找,發現只要裝 thumbor-plugins 就行了,pngquant 的整合早已有人寫好。

於是就改了一下 ansible 設定檔,加進 plugins,然後一鍵搞定部署,線上就用了起來。這個 pet project 的架構是這樣的:

     Cloudflare    (CDN)
         v
       nginx
         v
   thumbor server   
         v
       AWS S3      (圖源)

因為是 pet project,所以用了免費的 CDN cloudflare,上線之後,從 cloudflare 的統計資料來看,每日的流量從 ~270GB 下降到 ~200GB 左右,真是不錯的結果,當然用戶下載圖的時間也整整少了 2/3,很有感。而 pngquant 最佳化的圖雖然有失真,但其實不把兩張圖並在一起看根本就看不出差別,這結果很滿意啊。

結語

thumbor 是個好工具!加進自己的工具箱裡,未來遇到跟圖片有關的問題,自然就多了一個解決的選項。本篇的範例可在 github clone 下來自己玩,而 pngquant 的設定則是在另一個 branch 供各位參考。