如何使用Chef预编译资产?

13

OpsWorks在部署时未预编译资产。我在此帖子中找到了这个食谱,但我认为它不完整,可能是缺少某些内容,因为我收到一个关于未找到release_path的错误。

precompile.rb:

Chef::Log.info("Running deploy/before_migrate.rb...")

Chef::Log.info("Symlinking #{release_path}/public/assets to #{new_resource.deploy_to}/shared/assets")

link "#{release_path}/public/assets" do
  to "#{new_resource.deploy_to}/shared/assets"
end

rails_env = new_resource.environment["RAILS_ENV"]
Chef::Log.info("Precompiling assets for RAILS_ENV=#{rails_env}...")

execute "rake assets:precompile" do
  cwd release_path
  command "bundle exec rake assets:precompile"
  environment "RAILS_ENV" => rails_env
end

日志:

undefined local variable or method `release_path' for ....

有什么想法吗?我完全不了解厨师,正在试图在现场解决问题。


看起来你需要提供 release_path,即你的 Rails 应用程序应该驻留在指定主机上的路径。 - cmur2
这个在每次部署应用时都会改变。它是动态的,所以我不能硬编码它。 - manafire
4个回答

10

在OpsWorks原生支持Asset Pipeline之前,您可以执行以下操作。 在您的Rails应用程序中创建一个文件deploy/before_symlink.rb,内容如下:

run "cd #{release_path} && RAILS_ENV=production bundle exec rake assets:precompile"

如果您将Rails应用程序部署到不同的环境中,请更改RAILS_ENV。
如果您使用NGINX / Unicorn堆栈,则必须修改/assets资源。 只需将以下内容复制到名为unicorn/templates/default/nginx_unicorn_web_app.erb的文件中即可在您的cookbooks中使用。
upstream unicorn_<%= @application[:domains].first %> {
 server unix:<%= @application[:deploy_to]%>/shared/sockets/unicorn.sock fail_timeout=0;
}

server {
  listen 80;
  server_name <%= @application[:domains].join(" ") %> <%= node[:hostname] %>;
  access_log <%= node[:nginx][:log_dir] %>/<%= @application[:domains].first %>.access.log;

  keepalive_timeout 5;

  root <%= @application[:absolute_document_root] %>;

  <% if @application[:nginx] && @application[:nginx][:client_max_body_size] %>
    client_max_body_size <%= @application[:nginx][:client_max_body_size] %>;
  <% end %>

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    # If you don't find the filename in the static files
    # Then request it from the unicorn server
    if (!-f $request_filename) {
      proxy_pass http://unicorn_<%= @application[:domains].first %>;
      break;
    }
  }

  location /nginx_status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    deny all;
  }

  location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;

    add_header ETag "";
    break;
  }

  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root <%= @application[:absolute_document_root] %>;
  }
}

<% if @application[:ssl_support] %>
server {
  listen   443;
  server_name <%= @application[:domains].join(" ") %> <%= node[:hostname] %>;
  access_log <%= node[:nginx][:log_dir] %>/<%= @application[:domains].first %>-ssl.access.log;

  ssl on;
  ssl_certificate /etc/nginx/ssl/<%= @application[:domains].first %>.crt;
  ssl_certificate_key /etc/nginx/ssl/<%= @application[:domains].first %>.key;
  <% if @application[:ssl_certificate_ca] -%>
  ssl_client_certificate /etc/nginx/ssl/<%= @application[:domains].first %>.ca;
  <% end -%>

  keepalive_timeout 5;

  root <%= @application[:absolute_document_root] %>;

  <% if @application[:nginx] && @application[:nginx][:client_max_body_size] %>
    client_max_body_size <%= @application[:nginx][:client_max_body_size] %>;
  <% end %>

  location / {
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    # If you don't find the filename in the static files
    # Then request it from the unicorn server
    if (!-f $request_filename) {
      proxy_pass http://unicorn_<%= @application[:domains].first %>;
      break;
    }
  }

  location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;

    add_header ETag "";
    break;
  }

  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root <%= @application[:absolute_document_root] %>;
  }
}
<% end %>

如果您使用的是Apache2/Passenger堆栈,则必须修改/assets资源。只需将以下内容复制到名为passenger_apache2/templates/default/web_app.conf.erb的文件中即可。
<VirtualHost *:80>
  ServerName <%= @params[:server_name] %>
  <% if @params[:server_aliases] && !@params[:server_aliases].empty? -%>
  ServerAlias <% @params[:server_aliases].each do |a| %><%= "#{a}" %> <% end %>
  <% end -%>

  <% if @params[:mounted_at] -%>
  DocumentRoot /var/www
  <%= @params[:deploy][:passenger_handler] -%>BaseURI <%= @params[:mounted_at] %>
  <% else -%>
  DocumentRoot <%= @params[:docroot] %>
  <%= @params[:deploy][:passenger_handler] -%>BaseURI /
  <% end -%>
  <%= @params[:deploy][:passenger_handler] -%>Env <%= @params[:rails_env] %>

  <Directory <%= @params[:docroot] %>>
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
  </Directory>

  <Directory ~ "\.svn">
    Order allow,deny
    Deny from all
  </Directory>

  <Directory ~ "\.git">
    Order allow,deny
    Deny from all
  </Directory>

  <LocationMatch "^/assets/.*$">
    Header unset ETag
    FileETag None
    # RFC says only cache for 1 year
    ExpiresActive On
    ExpiresDefault "access plus 1 year"
  </LocationMatch>

  LogLevel info
  ErrorLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-error.log
  CustomLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-access.log combined
  CustomLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-ganglia.log ganglia

  FileETag none

  RewriteEngine On
  Include <%= @params[:rewrite_config] %>*
  RewriteLog <%= node[:apache][:log_dir] %>/<%= @application_name %>-rewrite.log
  RewriteLogLevel 0

  # Canonical host
  #RewriteCond %{HTTP_HOST}   !^<%= @params[:server_name] %> [NC]
  #RewriteCond %{HTTP_HOST}   !^$
  #RewriteRule ^/(.*)$        http://<%= @params[:server_name] %>/$1 [L,R=301]

  RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|jpeg|png)$
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteCond %{SCRIPT_FILENAME} !maintenance.html
  RewriteRule ^.*$ /system/maintenance.html [L]

  Include <%= @params[:local_config] %>*
</VirtualHost>

<% if node[:deploy][@application_name][:ssl_support] -%>
<VirtualHost *:443>
  ServerName <%= @params[:server_name] %>
  <% if @params[:server_aliases] && !@params[:server_aliases].empty? -%>
  ServerAlias <% @params[:server_aliases].each do |a| %><%= "#{a}" %> <% end %>
  <% end -%>

  SSLEngine on
  SSLProxyEngine on
  SSLCertificateFile <%= node[:apache][:dir] %>/ssl/<%= @params[:server_name] %>.crt
  SSLCertificateKeyFile <%= node[:apache][:dir] %>/ssl/<%= @params[:server_name] %>.key
  <% if @params[:ssl_certificate_ca] -%>
  SSLCACertificateFile <%= node[:apache][:dir] %>/ssl/<%= @params[:server_name] %>.ca
  <% end -%>
  SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0

  <% if @params[:mounted_at] -%>
  DocumentRoot /var/www
  <%= @params[:deploy][:passenger_handler] -%>BaseURI <%= @params[:mounted_at] %>
  <% else -%>
  DocumentRoot <%= @params[:docroot] %>
  <%= @params[:deploy][:passenger_handler] -%>BaseURI /
  <% end -%>
  <%= @params[:deploy][:passenger_handler] -%>Env <%= @params[:rails_env] %>

  <Directory <%= @params[:docroot] %>>
    Options FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

  <Directory ~ "\.svn">
    Order allow,deny
    Deny from all
  </Directory>

  <Directory ~ "\.git">
    Order allow,deny
    Deny from all
  </Directory>

  <LocationMatch "^/assets/.*$">
    Header unset ETag
    FileETag None
    # RFC says only cache for 1 year
    ExpiresActive On
    ExpiresDefault "access plus 1 year"
  </LocationMatch>

  LogLevel info
  ErrorLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-ssl-error.log
  CustomLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-ssl-access.log combined
  CustomLog <%= node[:apache][:log_dir] %>/<%= @params[:name] %>-ssl-ganglia.log ganglia

  FileETag none

  RewriteEngine On
  Include <%= @params[:rewrite_config] %>-ssl*
  RewriteLog <%= node[:apache][:log_dir] %>/<%= @application_name %>-ssl-rewrite.log
  RewriteLogLevel 0

  # Canonical host
  #RewriteCond %{HTTP_HOST}   !^<%= @params[:server_name] %> [NC]
  #RewriteCond %{HTTP_HOST}   !^$
  #RewriteRule ^/(.*)$        http://<%= @params[:server_name] %>/$1 [L,R=301]

  RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|jpeg|png)$
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteCond %{SCRIPT_FILENAME} !maintenance.html
  RewriteRule ^.*$ /system/maintenance.html [L]

  Include <%= @params[:local_config] %>-ssl*
</VirtualHost>
<% end -%>

如果您有问题,请随时提问。

最好的祝福, 丹尼尔

编辑:

或者您可以快速复制这些菜谱 https://github.com/neonlex/massive-octo-computing-machine ,我开发了它们。 但是OpsWorks将来应该会默认支持此功能。


非常感谢,丹尼尔。我设法找到了最终适合我的解决方案(答案在下面发布),现在它正在运行,我非常害怕尝试您的烹饪书,因为我担心会破坏它。我很想知道您答案中“独角兽步骤”的含义以及您对我想出的解决方案的任何想法。谢谢! - manafire
@neonlex,我使用了你更新的仓库,它运行得很好。我尝试将ruby shell命令替换为https://github.com/neonlex/massive-octo-computing-machine/blob/master/rails/libraries/rails_configuration.rb中的execute recipe dsl,但出现错误“undefined method 'execute'”。你有任何想法为什么它不起作用吗? - goutham
我不需要修改/assets或任何cookbooks。仅仅在before_symlink.rb中的代码对我起作用了。我使用nginx和unicorn,在使用Opsworks默认配方。 - Kirk
我真的不喜欢在我的源代码中添加特定的部署配置/责任... 在应用程序的源代码中创建deploy/before_symlink.rb,而不是纯粹使用Chef或OpsWorks来完成某些事情,感觉不太对。 - Don Cheadle

6
请注意,如果你使用OpsWorks新特性将环境变量传递给Rails应用程序,你需要在rake调用中包含这些变量(因为它们不会永久地被源代码部署用户的环境所使用)。
我在deploy/before_migrate.rb中执行以下操作(基于这篇文章这个教程):
Chef::Log.info("Precompiling assets for RAILS_ENV=" \
               "#{new_resource.environment['RAILS_ENV']}...")

execute 'rake assets:precompile' do
  cwd release_path
  command 'bundle exec rake assets:precompile'
  environment new_resource.environment
end

5
我很少了解OpsWorks和Chef,但这是我为使其工作所做的。首先,我必须创建一个Rails配方,在setup事件期间运行以创建资产的符号链接目录。这个配方位于一个OpsWorks可以访问的公共存储库中。 cookbooks/rails/recipes/symlink_assets.rb:
node[:deploy].each do |application, deploy|
  Chef::Log.info("Ensuring shared/assets directory for #{application} app...")

  directory "#{deploy[:deploy_to]}/shared/assets" do
    group deploy[:group]
    owner deploy[:user]
    mode 0775
    action :create
    recursive true
  end
end

接下来,在我的应用程序中,我需要创建 deploy/before_migrate.rb:

Chef::Log.info("Running deploy/before_migrate.rb...")

Chef::Log.info("Symlinking #{release_path}/public/assets to #{new_resource.deploy_to}/shared/assets")

link "#{release_path}/public/assets" do
  to "#{new_resource.deploy_to}/shared/assets"
end

rails_env = new_resource.environment["RAILS_ENV"]
Chef::Log.info("Precompiling assets for RAILS_ENV=#{rails_env}...")

execute "rake assets:precompile" do
  cwd release_path
  command "bundle exec rake assets:precompile"
  environment "RAILS_ENV" => rails_env
end

这个函数在部署过程中被调用并编译资产。


出于某种原因,即使我已经为共享资源目录指定了路径,它仍然尝试重新构建一些脚本。 - Brian Wigginton

3

在 AWS OpsWorks 中,我使用以下配方:

execute 'rake assets:precompile' do
  cwd "#{node[:deploy_to]}/current"
  user 'root'
  command 'bundle exec rake assets:precompile'
  environment 'RAILS_ENV' => node[:environment_variables][:RAILS_ENV]
end

我以root用户身份运行此命令,因为实例需要正确的权限才能写入release路径。以deploy用户身份运行命令会导致权限被拒绝的错误。


我被权限被拒绝的错误搞糊涂了,因为OpsWorks似乎将部署用户设置为/shared中所有内容的所有者。事实证明,该用户仍然使用root作为其文件的所有者(通过其文件掩码设置)创建其文件,因此它创建了一个日志文件,然后无法编辑它。 - zrisher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接