assets on S3の導入

こんにちは。エンジニアの志村です。 最近暑くて参りますね…。アイスばっかり食べてます。

さて今回ですが、Assets on S3を導入しましたのでその際のメモです。
結構この形でassetsを配信しているサービスは多いですよね。
今回は、Cloudfront+S3 / asset_sync+capistranoという定番のパターンで実装しております。

assets on S3とは

デプロイ時にassetsファイルをS3に配置し、CDN経由で配信する方法です。
通常であればnginxやApache等のWebサーバーを介して静的ファイルは配信されています。
assets on S3はassetsファイルをS3に配置し、CloudfrontやAkamai等のCDN経由で配信します。

Webサーバー負担軽減やCDNを噛ませることによる高速化を目的として用いられることが多いかと思います。

仕組み

下記のような流れになります。

  1. デプロイ時にrake assets:precompileを走らせる。その際にasset_syncを使用し、S3にassetsファイルをアップロードする。
  2. manifestファイルをEC2にアップロードする。
  3. rake assets:precompile時に作成されるassetファイルを削除する(public/assetsに格納されている)

こんな感じでしょうか。 他にもCloudfrontやS3の設定があるので、下記に記していきます。

Rails側の設定

ではまずasset_syncを導入し、bundle installをします。

asset_syncの導入

gem 'asset_sync'

次にasset_syncのconfigファイルを生成します。

$ rails g asset_sync:install --provider=AWS

今回はAWSを使用するのでproviderオプションにはAWSを設定します。 そうするとasset_sync.rbという設定ファイルが生成されます。

  • config/initializers/asset_sync.rb
AssetSync.configure do |config|
  # fogを使用するサービス
  config.fog_provider = 'AWS'
  # S3の存在するリージョン
  config.fog_region = 'ap-northeast-1'
  # S3のバケット名
  config.fog_directory = 'assets'
  # IAMのアクセスキー
  config.aws_access_key_id = 'xxxx'
  # IAMのシークレットアクセスキー
  config.aws_secret_access_key = 'xxxx'
  # 既にS3上に存在しているファイルの扱い keep, delete, ignoreから選択
  config.existing_remote_files = 'keep'
  # gzip圧縮をするかどうか
  config.gzip_compression = true
end if defined?(AssetSync)

上記で使用してるIAMに関しては、次の章のS3の部分で説明します。 これでasset_sync自体の設定は完了しました!

assetsのURLをCloudfrontにする

assetsファイルはCloudfrontより配信されます。 通常の設定では、http://domain/assets/配下のファイルが配信されますが、これをhttps://Cloudfrontのエンドポイント/assetsに変更し、Cloudfrontから配信するように設定します。

  • config/environments/production.rb
〜中略〜

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  config.action_controller.asset_host = '//xxxxx.cloudfront.net'

〜中略〜

これでassetsファイルがCloudfrontより配信されるようになりました!

次はcapistranoの設定を行います

capistranoの設定

流れとして、

  1. cap 〜 deployコマンドを叩いた時に、ローカルにてrake assets:precompileを走らせる(asset_syncが自動的にS3にアップロードしてくれる)
  2. manifestファイルをサーバのpublic/assetsディレクトリに転送する
  3. ローカルで生成されたpublic/assetsディレクトリを消去する

この中で特に大切なのは2.かと思います。
railsは、productionにデプロイされる際にprecompileを行います。
その際にdigestを付与してファイル名-digest.scssのようなファイル名に変更します。
このdigestはprecompile時にランダムに設定されるのでrails側で管理が必要になります。
そのファイル名を管理するのがmanifestファイルです。これが存在しないと、railsはassetのパスを正しく指定出来ません。

ではcapistranoの設定に移っていきます。 こちらのコードを参考にさせて頂きました。

qiita.com

  • config/deploy.rb
〜中略〜

namespace :deploy do
  # rake db:assets:precompileを実行する
  task :asset_sync do
    run_locally do
      Bundler.with_clean_env do
        execute :rake, 'assets:precompile'
      end
    end
  end

  # manifestファイルをアップロードする
  task :upload_manifest do
    on roles(:app) do |host|
      if test "[ ! -d #{release_path}/public/assets ]"
        execute "mkdir -p #{release_path}/public/assets"
      end
      file_path = Dir::glob('public/assets/.sprockets-manifest*').first
      upload!(file_path, "#{release_path}/public/assets")
    end
  end

  # ローカルに残されたprecompile後のassetsファイルを削除する
  task :assets_cleanup do
    run_locally do
      Bundler.with_clean_env do
        execute :rake, 'assets:clobber'
      end
    end
  end

  task :restart do
    invoke 'unicorn:restart'
  end
end

before 'deploy:starting', 'deploy:asset_sync'
after 'deploy:publishing', 'deploy:upload_manifest'
after 'deploy:publishing', 'deploy:assets_cleanup'
after 'deploy:publishing', 'deploy:restart'

〜中略〜

上記のようになります。
rakeタスクに関しては新しくファイルに切り分けても良いかと思いますが、そんなにデプロイスクリプトが多いわけでも無かったので僕はそのままdeploy.rbに書いてしまっています。
release_pathの部分はそれぞれの環境に合わせたpathを指定すればOKです。
developmentではsprocketsを使用しているのでそれをそのままuploadしております。

AWS側の設定

S3の設定

S3はバケットを作成し、CORSの設定を行います。 CORSの設定をしないと、Font Awesomeが正しく表示されません。 詳しくは

dev.classmethod.jp

を御覧ください。

  1. AWSコンソールの「S3」を選択

  2. 「バケットの作成」を選択

  3. バケット名、リージョンを設定

  4. 作成したバケットを選択し、「プロパティ」→「アクセス許可」→「CORS設定の編集」を選択 f:id:cluex-developers:20160810172755p:plain

  5. ここに下記のコードを貼り付け、「保存」をします 下記のコードを使用させて頂きました。

sora33.hatenadiary.com

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Cloudfrontの設定

基本設定

assets用のディストリビューションを作成します。

  1. AWSコンソールの「Cloudfront」を選択

  2. 「Web」を選択 f:id:cluex-developers:20160810152149p:plain

  3. Origin Settingsを入力する

項目 設定 説明
Origin Domain Name 先ほど作成したS3バケット名 オブジェクトの取得先
Origin Path 空のまま S3のルートディレクトリの設定
Origin-ID 分かりやすい名前 オリジンを区別する一意のID
Restrict Bucket Access Yes CloudfrontのURLからのみS3のオブジェクトにアクセス出来る
Comment 分かりやすい名前 identityを区別するIDのようなもの
Grant Read Permissions on Bucket Yes バケット内のオブジェクトの読み取り許可
Origin Custom Headers 空のまま リクエストをオリジンに転送する際にカスタムヘッダーを含めるかどうか

f:id:cluex-developers:20160810152400p:plain

※ Default Cache Behavior Settings, Distribution Settingsに関しては環境に応じて入力して下さい。今回は特にいじらずに行きます。

「Create Distributions」を選択するとCloudfrontのDistributionが作成されます。

Behaviorの設定

さて、Behaviorの設定を行っていきましょう。 Behaviorは振り分けのルールです。
例えば、

  • https://xxxx.cloudfront.net/assetsにリクエストが来た場合にはassetsバケットに転送
  • https://xxxx.cloudfront.net/imagesにリクエストが来た場合にはimagesバケットに転送

のようなことが可能になります。

  1. 「Behavior」タブの「Create Behavior」を選択 f:id:cluex-developers:20160810155354p:plain

  2. Settingsを入力

項目 設定 説明
Path Pattern /assets/* 振り分けパターン。今回は/assets配下のオブジェクト全てをS3から配信したいので/assets/*とする
Origin 先ほど設定したOrigin-ID どのオリジンからオブジェクトを取得するかを選択
Viewer Protocol Policy 環境によって選択 アクセスする際のプロトコルの選択
Allowed HTTP Methods GET, HEAD オリジンに転送するHTTPメソッド。今回は取得のみなのでGET, HEADを選択
Forward Headers Whitelist -> Origin どのヘッダを転送するか 今回CORSの設定を行っているので、OriginをAddする
Grant Read Permissions on Bucket Yes バケット内のオブジェクトの読み取り許可
Object Caching Use Origin Cache Headers MaximumTTL等を変更しないのであればUse Origin Cache Headersのままで良い

f:id:cluex-developers:20160810173203p:plain

「Create」を選択すると、このBehaviorが有効になります。

設定の反映までは10分〜20分ぐらいかかるので気長に待ちましょう!

以上です。 asset_syncのおかげでそこまで多くの手順を踏むこと無く、assets on S3の導入が出来ました。 次回はLambdaを使用してInvalidationを走らせる処理について書きたいと思います。