asset_syncを使って、RailsのassetsをS3から配信するパターンはほぼ定石となっている感があり、現在携わっているサービスも例外に漏れずこのパターンを採用しています。
そのasset_syncですが、導入がカンタンであまり迷うことがないので、勢いブラックボックスになりがちなのかなと思ってます。(少なくとも自分は詳しく知りませんでした^^;)
今回、assetsの配信周りで発生したトラブルを契機に、capistrano-railsとasset_syncについて調べたので、備忘録としてまとめておきます。
なお、運用環境は以下の通りです。
- sprockets (3.6.3)
- capistrano (3.4.1)
- capistrano-rails (1.1.7)
- asset_sync (1.0.0)
- fog (1.38.0)
capistrano-rails
Capistrano v3向けのRails固有タスクを追加するためのgemです。
Capfileに
require 'capistrano/rails/assets'
と書くだけで、updated hook に deploy:compile_assets 等のタスクが登録されます。
after 'deploy:updated', 'deploy:compile_assets'
after 'deploy:updated', 'deploy:cleanup_assets'
after 'deploy:updated', 'deploy:normalize_assets'
after 'deploy:reverted', 'deploy:rollback_assets'
では、deploy:compile_assetsが何をしているかというと、以下を見るとわかります。
namespace :assets do
task :precompile do
on release_roles(fetch(:assets_roles)) do
within release_path do
with rails_env: fetch(:rails_env) do
execute :rake, "assets:precompile"
end
end
end
end
end
rake assets:precompile
rake assets:precompileで実行しているタスクの詳細を知りたい場合は、
bundle exec rake assets:precompile --trace
を実行すればokです。
$ bundle exec rake assets:precompile --trace
** Invoke assets:precompile (first_time)
** Invoke assets:environment (first_time)
** Execute assets:environment
** Invoke environment (first_time)
** Execute environment
** Execute assets:precompile
** Invoke assets:sync (first_time)
** Invoke assets:environment
** Execute assets:sync
assets:precompile後にassets:syncを実行していることがわかります。
どういった仕組みで実行しているかは、asset_syncのREADMEに書かれています。以下に、一部抜粋します。
if Rake::Task.task_defined?("assets:precompile:nondigest")
Rake::Task["assets:precompile:nondigest"].enhance do
Rake::Task["assets:sync"].invoke if defined?(AssetSync) && AssetSync.config.run_on_precompile
end
else
Rake::Task["assets:precompile"].enhance do
Rake::Task["assets:sync"].invoke if defined?(AssetSync) && AssetSync.config.run_on_precompile
end
end
Rake::Task#enhance により、assets:precompile => assets:sync という順序でタスクが実行されるようになっています。
asset_sync
assets:syncタスクはシンプルでAssetSync.syncを実行するだけです。
namespace :assets do
desc "Synchronize assets to S3"
task :sync => :environment do
AssetSync.sync
end
end
コードを追うと、AssetSync::Storage#syncに辿り着きます。これがS3への同期処理のコア部分です。
def sync
# fixes: https://github.com/rumblelabs/asset_sync/issues/19
log "AssetSync: Syncing."
upload_files
delete_extra_remote_files unless keep_existing_remote_files?
log "AssetSync: Done."
end
さらに、upload_files と delete_extra_remote_files の実装を見てみます。
def upload_files
# get a fresh list of remote files
remote_files = ignore_existing_remote_files? ? [] : get_remote_files
# fixes: https://github.com/rumblelabs/asset_sync/issues/19
local_files_to_upload = local_files - ignored_files - remote_files + always_upload_files
local_files_to_upload = (local_files_to_upload + get_non_fingerprinted(local_files_to_upload)).uniq
...
upload_filesメソッドではlocal_files と remote_files の差分をチェックして、remoteに存在しないファイルをアップロードしていることが見てとれます。
def delete_extra_remote_files
log "Fetching files to flag for delete"
remote_files = get_remote_files
# fixes: https://github.com/rumblelabs/asset_sync/issues/19
from_remote_files_to_delete = remote_files - local_files - ignored_files - always_upload_files
delete_extra_remote_filesでも差分をチェックしていますが、こちらはlocalに存在しないファイルを削除していることがわかります。
なお、delete_extra_remote_filesを実行するかは設定(config.existing_remote_files)により、決めることができます。
# AssetSync::Config
def existing_remote_files?
['keep', 'ignore'].include?(self.existing_remote_files)
end
まとめ
だいぶ長くなったのでまとめます。
- capistrano-railsはdeploy:compile_assets等のcapタスクを追加するgem
- asset_syncはrake assets:precompileからinvokeされる
- asset_syncはlocalとremoteのファイル差分を計算し、よしなに同期してくれる