アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

CloudFront + nginx + S3 でのサムネイル画像の動的生成

こんにちは、tahara です。

以前 nginx を使ったサムネイル画像の動的生成 でサムネイル画像の動的生成について書きました。 その後 Paperclip の画像保存先をファイルシステムから S3 に変更しましたので、 あらためて CloudFront + nginx + S3 でのサムネイル画像の動的生成について書きたいと思います。

今回も 簡単!リアルタイム画像変換をNginxだけで行う方法 | cloudrop をおおいに参照させていただきました(感謝です)。

Paperclip の設定です。 CloudFront を使うにあたり次のような感じにします。

  • s3_host_name に CloudFront のホスト名
  • url に ":s3_alias_url"
  • s3_protocol に "" (http でも https でもいけるように)
# Paperclip
config.paperclip_defaults = {
  storage: :s3,
  s3_credentials: {
    access_key_id: "XXXXXXXXXXXXXXXXXXXX",
    secret_access_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  s3_host_name: "s3-ap-northeast-1.amazonaws.com",
  bucket: "your.backet.name",
  path: "/system/:class/:attachment/:id/:style.:extension",
  s3_host_alias: "xxxxxx.cloudfront.net",   # CloudFront のホスト名
  url: ":s3_alias_url",
  s3_protocol: "",       # empty string to generate scheme-less URLs
  :default_style => :normal,
  :default_url => "/assets/missing_:class_:attachment_:style.png",
  :auto_orient => true
}

config/initializers/paperclip.rb で nginx の HttpImageFilterModule を使って crop, resize するためのメソッドを書きます。 前回と違い S3 に非公開で保存した画像を参照するための expiring_url でも crop, resize するメソッドを追加しています。

module Paperclip
  module ClassMethods
  # nginx の HttpImageFilterModule で crop, resize するメソッド
  class Attachment
    def crop(width, height, quality=75)
      ret = "#{url}&w=#{width}&h=#{height}"
      if quality != 75
        ret += "&q=#{quality}"
      end
      ret
    end

    def resize(width, height, quality=75)
      crop(width, height, quality) + "&t=r"
    end

    def expiring_crop(width, height, quality: 75, time: 1800)
      ret = "#{expiring_url(time)}&w=#{width}&h=#{height}"
      if quality != 75
        ret += "&q=#{quality}"
      end
      ret.sub(/^.*?\?/, url.sub(/\?.*$/, '?'))
    end

    def expiring_resize(width, height, quality: 75, time: 1800)
      expiring_crop(width, height, quality: quality, time: time) + "&t=r"
    end
  end
end

nginx の設定では次に注意です。

  • S3 への proxy_pass のために
    • resolver 172.16.0.23;
  • ブラウザからのリクエストに Basic 認証の Authorization ヘッダが付いていると S3 に蹴られるので
    • proxy_set_header Authorization "";
  • CloudFront 経由で expiring_url を使った時 S3 に蹴られるので
    • proxy_set_header X-Amz-Cf-Id "";
  • proxy_pass での image_filter のために
    • image_filter_buffer 5m;

nginx.conf はこんな感じです(関係のあるとこだけ抜粋)。

http {
    # S3 への proxy_pass のために必要。172.16.0.23 は AWS での DNS みたい
    resolver 172.16.0.23;
    server {
        location ~* ^/system/.*\.(jpg|jpeg|jpe|png|gif)$ {
            if ($query_string ~ [?&][wh]=.*) {
               rewrite ^/(.*)$ /image_filter/$1 last;
            }
            rewrite ^/(.*)$ /s3_original/$1 last;
        }
        location ~* ^/system/.*$ {
            rewrite ^/(.*)$ /s3_original/$1 last;
        }
        location ~* ^/s3_original/(.*)$ {
            internal;
            expires 1y;
            set $file $1;
            proxy_set_header Authorization "";
            proxy_set_header X-Amz-Cf-Id "";
            proxy_pass http://s3-ap-northeast-1.amazonaws.com/example.com/$file$is_args$args;
        }
        location ~* ^/image_filter/(.*)$ {
            internal;
            set $file $1;
            set $width 150;
            set $height 150;
            set $quality 75;
            if ($arg_w ~ (\d*)) {
                set $width $1;
            }
            if ($arg_h ~ (\d*)) {
                set $height $1;
            }
            if ($arg_q ~ (100|[1-9][0-9]|[1-9])) {
                set $quality $1;
            }
            if ($arg_t = "r") {
                rewrite ^ /resize last;
            }
            rewrite ^ /crop last;
        }
        location /resize {
            internal;
            expires 1y;
            proxy_set_header Authorization "";
            proxy_set_header X-Amz-Cf-Id "";
            proxy_pass http://s3-ap-northeast-1.amazonaws.com/example.com/$file$is_args$args;
            image_filter_buffer 5m;
            image_filter  resize  $width $height;
            image_filter_jpeg_quality $quality;
            error_page 415 = @empty;
        }
        location /crop {
            internal;
            expires 1y;
            proxy_set_header Authorization "";
            proxy_set_header X-Amz-Cf-Id "";
            proxy_pass http://s3-ap-northeast-1.amazonaws.com/example.com/$file$is_args$args;
            image_filter_buffer 5m;
            image_filter  crop  $width $height;
            image_filter_jpeg_quality $quality;
            error_page 415 = @empty;
        }
        location @empty {
            empty_gif;
        }
    }
}

なかなか proxy_set_header X-Amz-Cf-Id ""; にたどり着けず泣きそうになりましたが、 これで画像アップし放題、リサイズし放題です!

もう一つ注意すべきことが、 model.photo.exists? を使うとその都度 S3 に HEAD リクエストを投げます。 model.photo.present? を使いましょう。 さもないと、レスポンスタイムがひどいことになります。

最後に、ひき続きエンジニアを募集していますので、よろしくお願いします!