AssetSyncでAzure BlobにRailsの静的ファイルを自動的に保存する
AzureでRailsアプリを運用されている皆様に朗報です。
アセットプリコンパイルで自動的にクラウドストレージに静的ファイルをアップロードしてくれるAssetSync gemがAzure Blobにも対応しました!! AssetSync PR#363
とりあえず結論
Gemfileに下記を追加
gem 'asset_sync', '~> 2.4.0' gem 'fog-azure-rm'
$ bundle install $ bundle exec rails g asset_sync:install --use-yml --provider=AzureRM
上記のコマンドで生成されたconfig/asset_sync.ymlを下記のように編集
defaults: &defaults fog_provider: AzureRM azure_storage_account_name: ストレージアカウント名 azure_storage_access_key: ストレージアクセスキー fog_directory: コンテナー名 fog_region: リージョン # 西日本ならjapanwest existing_remote_files: keep development: <<: *defaults enabled: false test: <<: *defaults enabled: false production: <<: *defaults enabled: true
そしてアセットプリコンパイルの実行です。
$ RAILS_ENV=production bundle exec rake assets:precompile
あとは、アップロードしてある静的ファイルの方を参照させるようにconfig/environments/production.rbを設定します。
Rails.application.configure do ... if defined?(AssetSync) && AssetSync.enabled? config.action_controller.asset_host = "//#{AssetSync.config.azure_storage_account_name}.blob.core.windows.net/#{AssetSync.config.fog_directory}" end ... end
解説
それぞれ設定内容などについて解説していきます。
Gemfile
gem 'asset_sync', '~> 2.4.0' gem 'fog-azure-rm' gem 'azure-core', '~> 0.1.14', require: false # ← なくてもいいです
AzureBlob対応したAssetSync gemは2.4.0になるので2.4.0以降を指定します。 fog-azure-rm gemはAssetSyncがAzureBlobを操作するために必要となります。
azure-core gemはfog-azure-rmをインストールすれば依存関係で自動的にインストールされるので明示的にGemfileに記述する必要はありませんが、0.1.14以降を利用することを明示したいために記述することをオススメします。
というのも、azure-core gemはアセットプリコンパイルされた静的ファイルたちをAzure Blobへアップロードするために利用されていますが、0.1.14より前のバージョンではAzure側から接断されてしまったときのリトライ処理がうまく動作せずに失敗してしまうバグがあるためです。 Azure Core PR#52 Azure Core PR#55
たぶん0.1.14をGemfileで明示しなくても bundle update azure-core
するだけでもよいはず。
AzureSyncの設定
AzureSyncを設定する方法には以下の3種類があります。
- config/initializers/asset_sync.rb
- config/asset_sync.yml
- 環境変数
3つ目の環境変数は、1つ目2つ目の設定ファイルが存在しなかった場合に参照されます。
1つ目と2つ目の設定ファイルはいずれか一方でもいいですし、両方を併用することもできます。 併用した場合は、initializerの方が先に読み込まれ、yamlの値で上書きされる、という順番で評価されます。
yamlの方でもERB形式のロジックを記述できるので充分な表現力がありますが、一部の設定項目(アップロード対象ファイルを追加する設定やログ出力設定)はinitializerの方でしか設定できませんので、それを使いたい場合はinitializerを使うか併用するとよいと思います。
設定例
私が開発・メンテしているRailsアプリでは下記のような内容で併用するようにしました。
yamlの方には認証情報などのクレデンシャルなものを記述し、ansibleやcapistranoなどのデプロイツールを使ってステージング毎に違う設定ファイルを配置する想定です。
config/initializers/asset_sync.rb
if defined?(AssetSync) AssetSync.configure do |config| # Don't delete files from the store config.existing_remote_files = "keep" # Use the Rails generated 'manifest.yml' file to produce the list of files to # upload instead of searching the assets directory. config.manifest = true # Log silently. Default is `true`. But you can set it to false if more logging message are preferred. # Logging messages are sent to `STDOUT` when `log_silently` is falsy config.log_silently = false end end
config/asset_sync.yml
defaults: &defaults fog_provider: AzureRM azure_storage_account_name: ストレージアカウント名 azure_storage_access_key: ストレージアクセスキー fog_directory: Blobコンテナー名 fog_region: japanwest development: <<: *defaults enabled: false test: <<: *defaults enabled: false production: <<: *defaults enabled: <%= $STAGE != "DEV" %> # ← DEV/STAGING/PRODUCTIONと開発工程毎に分かれている定数値でAssetSyncの利用を切り分け
アップロードした静的ファイルを参照させる設定
Railsのビューで生成されるlinkタグやimgタグのURLをアップロードしたAzure側のホストに向けるためにはconfig/environments/production.rbを下記のように設定します。
Rails.application.configure do ... if defined?(AssetSync) && AssetSync.enabled? config.action_controller.asset_host = "//#{AssetSync.config.azure_storage_account_name}.blob.core.windows.net/#{AssetSync.config.fog_directory}" end ... end
上記はAzureBlobに対して匿名のHTTPアクセスを許可させている場合の設定です。AzureCDNを使う場合は.blob.core.windows.net
の部分を .azureedge.net
としてください。(もちろん、Azure側は適切に設定してくださいね。)
なお、linkタグやimgタグのURLを全部Azureに向ける場合は上記の設定でOKですが、一部のJSやCSS、画像ファイルをRailsコントローラーやnginxなどのフロントWebサーバーに処理させたい場合には下記のようにProcオブジェクトで条件分岐させることができます。Procを評価した結果nilが返った場合は外部ホストを参照しないURLが生成されます。
Rails.application.configure do ... if defined?(AssetSync) && AssetSync.enabled? config.action_controller.asset_host = Proc.new { |src, request| if src =~ /^\/assets/ "//#{AssetSync.config.azure_storage_account_name}.blob.core.windows.net/#{AssetSync.config.fog_directory}" end } end ... end
おまけ
上記までで基本的にはAssetSyncでAzureBlobに静的ファイルを保存させることができます。
以下はハマった箇所についての補足です。
CSSファイルから外部ホスト参照している場合のキャッシュ回避
dt.menu-tree-open { background-image: url(<%= asset_path("menu_tree/minus.png") %>); }
上記のようにCSSファイル内で画像ファイルを指定している場合に前述の設定で外部ホストを参照するようにできますが、設定を変更して別のホストにしたり、一時的に外部ホストを使わないようにしたという場合にアセットプリコンパイルを実行してもキャッシュが効いてしまって再作成されずに古い設定のまま外部ホストを参照してしまいました。
キャッシュを削除してからプリコンパイルを実行すればもちろん解決するのですが、そのために全てのキャッシュを削除してプリコンパイルをやり直すとちょっと時間がかかりすぎてしまうのであまりやりたくないです。
そこで、画像ファイルを利用しているCSSファイルをxxx.css
からxxx.css.erb
に名称変更して下記の内容を加えます。
/* 画像ファイルパスのキャッシュ回避 */ <% depend_on Rails.root.join("config/asset_sync.yml") %> <% depend_on Rails.root.join("config/environments/production.rb") %> <% asset_host = Rails.app_class.config.action_controller.asset_host %> <% if asset_host.is_a?(Proc) %> dummy_tag { background-image: url(<%= asset_host.call(Rails.app_class.config.assets.prefix) %>) ; } <% else %> dummy_tag { background-image: url(<%= asset_host %>) ; } <% end %> dt.menu-tree-open { background-image: url(<%= asset_path("menu_tree/minus.png") %>); }
depend_on
で指定したファイルが更新されているとプリコンパイルで再作成する対象となります。また、dummy_tagというところに外部参照ホストを書き出すようにすることでプリコンパイル結果が変わるので無事にAssetSync関係の変更が反映されるようになります。
おわりに
AssetSyncでAzureBlobへ静的ファイルを自動的にアップロードするやり方を紹介しました。
もともとAssetSyncはAzureに対応していなかったのですが、プルリクを送ってみたら採用してもらえました。はじめてのプルリクでドキドキしていましたがピカチュウっぽい人が丁寧に対応してくれて助かりました。自分の変更が取り込まれて世界中の人に使ってもらえるっていいですね〜。