読者です 読者をやめる 読者になる 読者になる

CloudFront+S3の画像配信にリサイズ機能を追加する

CloudFront+S3の画像配信システムに、サムネイルとかに使う画像のリサイズ機能を追加してみる。

要するにオリジナル画像がこのURLだとすると、

http://xxx.cloudfront.net/sample.jpg

f:id:oretachino:20141220234355j:plain

こういうURLで100×100にリサイズできるようにする。

http://xxx.cloudfront.net/resize/100x100/sample.jpg

f:id:oretachino:20141220234512j:plain

システム構成

元の構成はこういうのを想定。
画像はS3に保存され、アクセスは全てCloudFront経由。

f:id:oretachino:20141220233902p:plain

これをパスが/resize/で始まる場合は、画像変換サーバを通してリサイズするようにする。

f:id:oretachino:20141220233919p:plain

画像変換サーバはEC2で、ちゃんとELBを使って冗長化もする。
既にELB+EC2でAPPサーバを運用しているなら、流用して追加コスト0で実現できるかも。

画像変換サーバの設定

今回はnginxのimage_filterモジュールを使う。
もちろんsmall_lightを使ってもいいし、自前で開発してもいい。

インストールはAmazonLinuxであれば、ただyum install nginxするだけ。
それ以外の場合ではたぶん、--with-http_image_filter_moduleをつけてnginxをコンパイルしないといけない。
※EPELのnginxは流石にバージョンが古すぎる。

image_filterもその1つなんだけど、AmazonLinuxの標準レポジトリのnginxは、公式rpmには含まれないいくつかのモジュールが追加されているので、確認してみると面白いかも。

# nginx -V
...
configure arguments: ... --with-http_image_filter_module ...

サイズを完全に自由指定できるようにするには、こんな感じで設定する。

server {
  listen 80 default_server;
  server_name _;

  root /var/nginx_root;

  location ~ ^/resize/(\d+)x(\d+)/(.*)$ {
    set $width $1;
    set $height $2;
    set $path $3;

    image_filter resize $width $height;
    image_filter_jpeg_quality 90;

    rewrite ^ /$path break;
  }
}

これでドキュメントルート配下の画像のリサイズが確認できるはず。
※画像が無い時に404にするには『error_page 415 =404 /404.html;』を追加する。

ただ開発環境はいいけど、本番環境にこの設定を入れるとDOS攻撃が怖い。
例えば幅を1ずつ増やしてDOS攻撃されると、

for i in {1..10000};do curl http://xxx.cloudfront.net/resize/${i}x1000/sample.jpg;done

画像変換サーバのCPUや、前段にキャッシュ用Proxyがある場合はそのメモリを使いきってしまうかもしれない。

これを防ぐ方法の1つは、cookpadのtofuのようにURLに予測不可能なハッシュ値を含めること。

http://img.cpcdn.com/cms_articles/3453/100x100c/cdcf6aff4c78c6619b67372f129bdef2.jpg?u=9769263&p=1418093860

しかしその場合はハッシュ関数とSEEDが漏れたらURLを変更しないといけなくなるかもしれない。まぁ直リン禁止なら問題ないけど。

もう1つの方法は、本番系は特定のサイズ指定のみ許可すること。
今回はこっち。

server {
  listen 80 default_server;
  server_name _;

  root /var/nginx_root;

  location ~ ^/resize/(100x100|200x200)/(.*)$ {
    set $type $1;
    set $path $2;

    if ($type = 100x100) {
      set $width 100;
      set $height 100;
    }

    if ($type = 200x200) {
      set $width 200;
      set $height 200;
    }

    image_filter resize $width $height;
    image_filter_jpeg_quality 90;

    rewrite ^ /$path break;
  }

  location ~ ^/resize/ {
    return 404;
  }
}

運用がちょっと面倒で応用も効きづらいけど、そのぶん実装は簡単。
設定が冗長になるのはchefとかで動的に作ればそんなに面倒じゃないはず。

CloudFront設定&画像変換サーバのProxy設定

事前にELBを作成して画像変換サーバをその配下に入れておく。

そしてManagementConsoleのCloudFront設定で、/resize/で始まる場合はS3でなくELBに渡すよう設定する。

  • 『Origins』でELBをOriginとして追加
  • 『Behaviors』でPath Patternを『resize/*』、OriginをELBにしたBehaviorを追加

あとはnginxでCloudFrontにProxyするように設定する。
ここで1つ注意があって、CloudFrontから来たアクセスを同じCloudFrontにProxyする場合、1回目通った時にループ検出のために付与されるViaヘッダをクリアしないとアクセスが拒否されてしまう。

というわけでnginxのlocationに追加する設定は以下の2行。

proxy_set_header Via "";
proxy_pass http://d2sloa042uzrzo.cloudfront.net;

これで作業は完了。

http://d2sloa042uzrzo.cloudfront.net/kuma.jpg http://d2sloa042uzrzo.cloudfront.net/resize/100x100/kuma.jpg

まとめ

いろいろめんどくさいから、CloudFront自体に画像のリサイズ機能つかないかな。