なぜそんなことをしたくなったのか
capistrano-railsのassets:precompileが走っている時にEC2がメモリ不足に陥り、インスタンスを再起動させた結果、deployが中途半端なところで終わってしまいました。
もし中途半端なコンパイル結果をもとにWebサーバーが動いて実行時エラーなんて起きたときには目も当てられないので、なんとかまっさらな状態に戻そうとしてあれこれしました。
バージョン情報
rails (5.2.4.2)
capistrano (3.11.0)
capistrano-rails (1.6.1)
sprockets (3.7.2)
sprockets-rails (3.2.1)
前提条件
current_pathはこのパスとする
- /var/www/app_name/current
shared_pathはこのパスとする
- /var/www/app_name/shared
先に結論
結論から言うと、やるべきことはこれです。
- /var/www/app_name/shared/public/assets の中身を消す
- /var/www/app_name/shared/tmp/cache/assets の中身を消す
- いつもどおりデプロイ
注意点があります。
capistranoにはdeploy:clobber_assets
のようなassetsをいかにも消しそうなコマンドが標準で用意されていますが、
これを使わずちゃんと手で消すということです。理由は後述します。
説明
capistranoのディレクトリ構成
まず、capistranoによってデプロイされたサーバーの/var/www/app_name
の中は、下記のような構造になっています。
├── current (releases内の最新のディレクトリにシンボリックリンクが貼られてる)
├── releases
│ ├── 20200601101105
│ ├── 20200601102714
│ └── 20200601105159
├── repo
├── revisions.log
└── shared
このcurrentの下、つまり、releases/20200601105159の下には、あなたのプロジェクトのリポジトリにあるような構成になっているでしょう。
そこにはapp/があり、config/があり、public/があるはずです。
このpublic/の下にはビルドされた静的ファイルが詰め込まれたassets/が入っている……ように見せかけて、
/var/www/app_name/current/public/assets
は/var/www/app_name/shared/assets
へのシンボリックリンクになっています。これはrails対応の公式gemであるcapistrano-rails側でlinked_dirs
の設定がされるためです。
そしてちゃんと設定をしていたのであれば、/var/www/app_name/current/tmp/cache
は同様に/var/www/app_name/shared/tmp/cache
へのシンボリックリンクになっているはずです。 capistrano-railsのREADMEに書かれている通りに設定していた場合はそうなっているはずです。
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache' ...以下省略
assets:precompileのキャッシュの仕組み
デフォルトのSprocketsは、development環境とproduction環境でtmp/cache/assetsにアセットをキャッシュします。
こう書かれている通り、tmp/cache/assetsにコンパイル結果のキャッシュがあるので、これを消します。
capistranoだと/var/www/app_name/shared/tmp/cache/assets
がこれにあたるので消します。
Sprocketsのデプロイ結果のキャッシュの仕組み
assets:precompileのキャッシュとは別に、Sprocketsはデプロイ結果のファイルも取っておいてあります。
Sprocketsのコードにこんなのが書かれています。
module Sprockets
module Exporters
# Writes a an asset file to disk
class FileExporter < Exporters::Base
def skip?(logger)
if ::File.exist?(target)
logger.debug "Skipping #{ target }, already exists"
true
else
logger.info "Writing #{ target }"
false
end
end
def call
write(target) do |file|
file.write(asset.source)
end
end
end
end
end
deploy:assets:precompileしているときに、標準出力でログがずらっと出てきているのを目にしていませんか? アレを実現しているのがおそらくは上のコードです。
logger.info "Writing
のところを見てください。
こんな感じ:
01 I, [2020-06-01T08:56:09.691790 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
01 I, [2020-06-01T08:56:09.692557 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
01 I, [2020-06-01T08:56:17.418572 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
01 I, [2020-06-01T08:56:17.418856 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
01 I, [2020-06-01T08:56:21.414096 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
01 I, [2020-06-01T08:56:21.414368 #10361] INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
このskip?
の判定に引っかかった場合。
つまり、この例だと/var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
のdigest込みのファイルが既に存在していた場合、
Sprocketsはファイルを書き出しません。
なので、capistranoだと/var/www/app_name/shared/assets
に同じファイルがあった場合、このskip?でスキップされてしまうということです。
消しておく必要がありますね。
おや? 便利なコマンドがありそうだが……?
capistranoはcap -T
で全てのコマンドを表示してくれます。
assets関係だとこんなコマンドが見つかります。
cap deploy:cleanup_assets # Cleanup expired assets
cap deploy:clobber_assets # Clobber assets
標準でdeploy:clobber_assets
というコマンドが用意されているではありませんか!
clobberとはぶん殴るという意味です。強制的に新しいassetsにしてくれそうだ! こりゃいいや! と思って使うと、実は思ったような挙動をしてくれません。
$ bundle exec cap review deploy:clobber_assets
00:00 deploy:clobber_assets
01 bundle exec rake assets:clobber
01 I, [2020-06-01T11:22:46.062204 #6756] INFO -- : Removed /var/www/app_name/releases/20200601094959/public/assets
01 Removed webpack output path directory /var/www/app_name/releases/20200601094959/public/packs
いかにもassetsをディレクトリごと消してくれていそうな雰囲気ですが、よく見るとRemoveしているのはただのシンボリックリンクで、肝心の中身は消えていません。マジか。
そして、そもそもコンパイル時のキャッシュであるtmp/cacheの方は消してくれていません。
このことについてはissueも上がっているようです。
なので、「結論」に書いた通り、手動でコマンドを実行して消すか、自分でコマンドを定義してやるのが良さそうです。