Why do I need to precompile Rails assets on app servers and not web servers only?

生来就可爱ヽ(ⅴ<●) 提交于 2021-01-27 07:08:05

问题


I'm deploying a simple Rails app to this small ubuntu precise64 server setup :
* 1 web server running nginx
* 2 app servers running unicorn
* 1 db server running postgresql

My servers are provisionned with Puppet and I'm pushing the app with capistrano.

My Capfile

load 'deploy'
# Uncomment if you are using Rails' asset pipeline
load 'deploy/assets'
load 'config/deploy' # remove this line to skip loading any of the default tasks

The deploy.rb file is super simple

# Execute "bundle install" after deploy, but only when really needed
require "bundler/capistrano"

# Name of the application in scm (GIT)
set :application, "devops-test-app"
set :repository, "https://github.com/geoffroymontel/devops-test-app.git"
set :scm, :git

set :deploy_to, "/var/www/#{application}"

# server there the web server is running (nginx)
role :web, "172.16.0.2"

# server there the db is running
# This is where Rails migrations will run
role :db, "172.16.0.3", :primary => true

# servers there the app servers are running (unicorn)
role :app, "172.16.0.4", "172.16.0.5"

set :rails_env, :production

# user on the server
set :user, "deployer"
set :use_sudo, false

namespace :deploy do 
  task :start, :roles => :app, :except => { :no_release => true } do
    run "service unicorn_#{application} start"
  end

  task :stop, :roles => :app, :except => { :no_release => true } do
    run "service unicorn_#{application} stop"
  end

  task :restart, :roles => :app, :except => { :no_release => true } do
    run "service unicorn_#{application} restart"
  end

  task :copy_in_database_yml do
    run "cp #{shared_path}/config/database.yml #{latest_release}/config/"
  end

  # Precompile assets
  # I have to precompile the assets on the app servers too, and I don't really know why...
  # namespace :assets do
  #   task :precompile, :roles => [:web, :app], :except => { :no_release => true } do
  #     run %Q{cd #{latest_release} && #{rake} RAILS_ENV=#{rails_env} #{asset_env} assets:precompile}
  #   end
  # end
end

before "deploy:assets:precompile", "deploy:copy_in_database_yml"

If I don't precompile assets on the app servers, the application fails.

$ cap ROLES="app" COMMAND="cat /var/www/devops-test-app/current/log/production.log" invoke
  * 2013-05-01 21:43:10 executing `invoke'
  * executing "cat /var/www/devops-test-app/current/log/production.log"
    servers: ["172.16.0.4", "172.16.0.5"]
    [172.16.0.5] executing command
 ** [out :: 172.16.0.5] Connecting to database specified by database.yml
 ** [out :: 172.16.0.5] Connecting to database specified by database.yml
 ** [out :: 172.16.0.5] Started GET "/posts" for 172.16.0.2 at 2013-05-01 19:42:10 +0000
 ** [out :: 172.16.0.5] Processing by PostsController#index as HTML
 ** [out :: 172.16.0.5] Rendered posts/index.html.erb within layouts/application (25.7ms)
 ** [out :: 172.16.0.5] Completed 500 Internal Server Error in 122ms
 ** [out :: 172.16.0.5] 
 ** [out :: 172.16.0.5] ActionView::Template::Error (application.css isn't precompiled):
 ** [out :: 172.16.0.5] 2: <html>
 ** [out :: 172.16.0.5] 3: <head>
 ** [out :: 172.16.0.5] 4:   <title>DevopsTestApp</title>
 ** [out :: 172.16.0.5] 5:   <%= stylesheet_link_tag    "application", :media => "all" %>
 ** [out :: 172.16.0.5] 6:   <%= javascript_include_tag "application" %>
 ** [out :: 172.16.0.5] 7:   <%= csrf_meta_tags %>
 ** [out :: 172.16.0.5] 8: </head>
 ** [out :: 172.16.0.5] app/views/layouts/application.html.erb:5:in `_app_views_layouts_application_html_erb__677166568443748084_17536100'
 ** [out :: 172.16.0.5] app/controllers/posts_controller.rb:7:in `index'

If I uncomment the commented lines in deploy.rb, everything is fine.

But why ?? I thought you would need to compile the assets only on the web server, not on the app server.

Please help me understand why :)

Thanks, best regards

Geoffroy


回答1:


Because, among other things, Rails generates a manifest of the precompiled assets including hashes of the files in the filenames, and then uses those names when you include the assets in a page. When you say image_url('foo.jpg'), Rails will end up generating foo-b48cf0140bea12734db05ebcdb012f1d265bed84.jpg in the source code.

Rails needs to know what compiled names to use for these assets, so it has to have the manifest, so the precompilation needs to be done on the app server.

Take a look at public/assets/manifest.yml - that's the file that Rails needs in order to serve precompiled assets properly.




回答2:


This is a known bug in Capistrano.

1) Comment

load 'deploy/assets'

on your Capfile.

2) Add the following lines at the top of your deploy.rb file :

set :assets_role, [:web, :app]
load 'deploy/assets'

That's it !




回答3:


Be sure you configured Nginx to serve static files without hitting the application server. It's likely this is not configured, thus the assets exist but Nginx is not serving them and is falling back to the application.



来源:https://stackoverflow.com/questions/16344022/why-do-i-need-to-precompile-rails-assets-on-app-servers-and-not-web-servers-only

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!