RailsでPaperclipを使ってAmazon S3に画像を保存する

っということでAWS Advent Calendar 2012 on Zusaarの8日目の記事です。

経緯

Local Search – 近所のイベントを探そうの各イベントに画像をもたせようと思い立ち調査を開始。

案1:画像をWebサーバーに保存する

最初に思いついたのがアップロードされた画像をWebサーバーのローカルに保存する方法です。

取り敢えず実装してみるも幾つかの問題点が浮上しました。

  1. DeployにCapistranoを使っているのでゴニョゴニョしないといけない
  2. Webサーバーをスケールアウトした場合に各Webサーバー間で画像の同期を行う必要がある

「1」についてはDeployの度にシンボリックリンクを作れば(参考:Capistrano 実践Tips集)何とかなりそう。

でも、「2」も何とかなるけどイケてないですね。

案2:画像をデータベースに保存する

直接データベースに画像を保存すればレプリケーションを使ってスケールアウトも可能です。

っで実装してみますが、やはり問題点が浮上。

  1. 画像が表示される度にデータベースから呼び出されるので負荷が大きい
  2. Facebookなんかで使われているOGPで画像が正しく表示できない

「1」は予想していましたが、「2」は予想外でした。

色々と試してみるも、動的に作成される画像はOGPの画像として表示することができませんでした。(何か良い方法があれば教えてください。何卒。)

案3:画像をAmazon S3に保存する

っで最後にたどり着いたのが今回の本題である画像をAmazon S3に保存する方法です。

この方法は当初から考えていたのですが「なんか大変そう」っということで敬遠していました。

ただ、よくよく調べるとPaperclipなるgemを使う事でS3への保存・呼び出しまで簡単に出来そうです。

っということでやっと本題です。

Paperclipを使ってAmazon S3に画像ファイルを保存する

今回はサンプルとしてEventモデルを持ったプロジェクトを作ります。

name属性を持ったEventモデルをscaffoldでサクッと作り、その後に「rake db:migrate」を流します。

rails g scaffold events name:string
rake db:migrate

GemfileにPaperclipとAWS-SDKを追加して「bundle install」します。

# Gemfile
gem ‘paperclip’
gem ‘aws-sdk’
bundle install

先ほど追加したEventモデルに画像用のカラムを追加します。

カラムと言っても画像その物を保存するのでは無く、ファイルパスやコンテンツタイプなどを保持するカラムです。

カラムの追加はPaperclipに用意されているコマンドを使います。

rails g paperclip event image

これはEventモデルにimageという名前で画像を追加するという意味です。

下記の様なMigrationファイルが出来るので、「rake db:migrate」を流してデータベースに画像情報を保存する為のカラムを追加します。。

# db/migrate/~_add_attachment_image_to_events.rb
class AddAttachmentImageToEvents < ActiveRecord::Migration def self.up change_table :events do |t| t.has_attached_file :image end end def self.down drop_attached_file :events, :image end end
rake db:migrate

Eventモデルに画像用の設定を行います。 画像の保存先を「4行目 :storage => :s3」としてAmazon S3に設定します。

Amazon S3の各種設定は「config/s3.yml」で外出しにしたいと思いますので取り敢えず「5行目 :s3_credentials」に「config/s3.yml」のパスを指定します。

最後に「6行目 :path」で画像を保存する際のパスルールを指定します。

下記の設定だと例えばEvent.IDが1の画像のパスは「image/1/original.png」となります。

# app/model/event.rb
class Event < ActiveRecord::Base has_attached_file :image, :storage => :s3,
:s3_credentials => “#{Rails.root}/config/s3.yml”,
:path => “:attachment/:id/:style.:extension”
end

Amazon S3の設定情報をAWSから取得します。

「AWS Management Console」の右上から「Security Credentials」を選択します。

2012120701

表示されたセキュリティ証明書のアクセスキーIDとシークレットアクセスキーを控えておきます。

2012120702

画像を保存するためのバケット(保存場所)をS3に作ります。

「AWS Management Console」に戻ってS3を開きます。

「Create Bucket」を押下して新規にバケットを作成します。

バケット名は好きな名前を指定しRegionを東京リージョンに設定します。

ここで設定したバケット名は後ほど使うので控えておいて下さい。

後は「Create」を押下してバケットを作成します。

2012120703

「config/s3.yml」を新規に作成します。

「2行目 bucket:」には先程作ったバケット名を指定します。

「3行目 access_key_id:」と「4行目 secret_access_key:」はセキュリティ証明書で控えたキーを指定します。

あとはホスト名を「5行目 s3_host_name:」に指定します。東京リージョンの場合には「s3-ap-northeast-1.amazonaws.com」をホスト名として指定します。

# config/s3.yml
bucket: paperclip.aguuu.com
access_key_id: 1234…
secret_access_key: 1234…
s3_host_name: s3-ap-northeast-1.amazonaws.com

これでPaperclipからAmazon S3に画像を保存するための設定は完了しました。

後はイベント登録画面に画像をアップロードするためのフィールドを追加します。

form_forに画像アップロードのための「2行目 :html => { :multipart => true }」を指定します。

「7行目 f.file_field」を追加して画像用の項目を指定します。(今回はimage)

また当画面は編集と新規作成で共有するので、すでに設定されている画像を表示する為に「5行目 image_tag」を追加します。

# app/view/events/_form.html.erb
<%= form_for(@event, :html => { :multipart => true }) do |f| %>
# 〜省略〜
<%= image_tag @event.image.url if @event.image.present? %>
<%= f.label :image %>
<%= f.file_field :image %>
# 〜省略〜
<% end %>

イベントの詳細画面でも画像を表示したいので「4行目 image_tag」を追加します。

# app/view/events/show.html.erb
# 〜省略〜
Image:
<%= image_tag @event.image.url if @event.image.present? %>
# 〜省略〜

これで完了です。

後は画像を追加したり変更したりしてみて下さい。

ね?簡単でしょ?

画像はoriginal.*というファイル名で保存されるため、編集画面で画像を変更するとファイルは上書きされる為、画像の入れ替えを意識する必要もありません。

他にもPaperclipを使えばアップロードした画像を指定のサイズにリサイズして複数のスタイルで保存することもできます。

S3に保存する事で画像のリクエストはS3のサーバーに分散され、Webサーバーへの負荷の軽減が見込めます。

またS3は従量課金制なので大きな画像を大量に保存したり、多くのリクエストが発生すればそれなりのお値段となるので使用するWebサイトによっては注意する必要があります。(Local Searchに至っては数百円/月程度と思われます。)

殆ど、Paperclipの説明になってしまいましたが、これで終わり。

今回、使ったプログラムソース一式は下記からどうぞ。

tochi/paperclip_sample · GitHub

2013-01-27追記

第13回 岡山Ruby, Ruby on Rails勉強会で発表した資料の追加。