

An annoying Capistrano, Unicorn and Bundler issue
source link: https://evilmartians.com/chronicles/an-annoying-capistrano-unicorn-and-bundler-issue
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In short: always use an absolute path to Unicorn in Capistrano scripts; otherwise your application server will fail to restart after a number of deploys because of Capistrano release cleanups.
Last month, we came across an interesting problem: sometimes after a successful deploy of one of our Ruby on Rails 4 applications, a Unicorn server could not be restarted using zero downtime deployment signal. That affected all environments we run the application in — unfortunately, including production. As a result, we got annoying exceptions like ActionView::MissingTemplate
in the production environment. Only a hard restart of Unicorn would fix that kind of exceptions.
We had no idea what could be the source of the issue, so we decided to check this case out using our monitoring system (Zabbix) to catch such incidents immediately and act on it.
Here is a simple Rails code snippet we’ve used to detect if Unicorn was properly restarted:
# app/controllers/monitoring_controller.rb
class MonitoringController < ActionController::Metal
include ActionController::Head
def unicorn
if unicorn_revision.eql?(current_revision)
head 200
else
head 500
end
end
private
def unicorn_revision
File.read(Rails.root.join('REVISION')).chomp
end
def current_revision
File.read(Rails.root.join('../../current/REVISION')).chomp
end
end
# config/routes.rb
Rails.application.routes.draw do
# ...
get '/monitoring/unicorn' => 'monitoring#unicorn'
end
Note that the REVISION
file is written by Capistrano on every deploy, so we can simply compare the contents of the file with the current application revision we have in memory.
And here is a snippet of Zabbix configuration we have used:
UserParameter=unicorn.revision.check,/usr/bin/curl -H 'Host: example.com' -sL -w '%{http_code}' http://<unicorn ip address>/monitoring/unicorn
Next, we’ve created a Zabbix agent item (“Revision status check”) and used it for a trigger:
( {TRIGGER.VALUE}=0 and
{Unicorn revision template:unicorn.revision.check.count(#3,200,"ne")}=3 )
or
( {TRIGGER.VALUE}=1 and
{Unicorn revision template:unicorn.revision.check.count(#2,200,"ne")}=2 )
Now, how does this trigger work, exactly? It checks for a non-200 HTTP response and ensures that it persists during a period, to prevent false alerts during deploys and rebouncing. Google “Hysteresis in Zabbix” for more details, if you’re interested.
The first alert we’ve got led us to the following output in the Unicorn log file:
I, [2015-03-13T11:41:29.862602 #1285] INFO -- : executing ["/home/project/releases/20150312084505/vendor/bundle/ruby/2.1.0/bin/unicorn_rails", "-c", "config/unicorn.rb", "-E", "production", "-D", {12=>#<Kgio::UNIXServer:fd 12>}] (in /home/project/releases/20150313084000)
/home/project/current/vendor/bundle/ruby/2.1.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:475:in `exec': No such file or directory - /home/project/releases/20150312084505/vendor/bundle/ruby/2.1.0/bin/unicorn_rails (Errno::ENOENT)
And at that moment I’ve started to get a clear understanding of what’s going on.
Here is how zero-downtime Unicorn deploy works: it starts the master process and replaces it with a fork on the next deploy.
On the first deploy, bundle exec unicorn_rails command was mapped to /home/project/releases/1/vendor/bundle/ruby/2.1.0/bin/unicorn_rails
, which started properly. But later, let’s say on 100th deploy, the original Unicorn still pointed to the /home/project/releases/1
directory from the first release. By default, Capistrano 3 keeps only the last five releases and cleans up older releases (deploy:cleanup
task runs automatically by default), and obviously /home/project/releases/1
did not exist anymore.
Since /home/project/releases/1/vendor/bundle
is just a symlink to /home/project/shared/vendor/bundle
, we had to configure Bundler to map commands to the shared
directory — which exists forever.
$ bundle install --deployment
$ cat .bundle/config
#> BUNDLE_FROZEN: '1'
#> BUNDLE_PATH: "vendor/bundle"
Here is a problem: BUNDLE_PATH
is relative, and it maps to /home/project/releases/1/vendor/bundle
, which would be destroyed later after a few deploys.
And the fix:
$ bundle install --deployment --path /home/project/shared/vendor/bundle
$ cat .bundle/config
#> BUNDLE_FROZEN: '1'
#> BUNDLE_PATH: "/home/project/shared/vendor/bundle"
In Capistrano 3, this patch can be applied by setting the bundle_path
option:
set :bundle_path, -> { shared_path.join('vendor/bundle') }
Done.
In spite of my history of contributions to Capistrano and Bundler, it was one of the most interesting bugs I’ve found recently.
We’ve decided to keep monitoring Unicorn revisions to catch any failed restarts in future.
Recommend
-
19
Rails Asset Pipeline Failures and Capistrano Apr 14, 2020 One of the single worst parts of being a one man show on an engineering effort is that when you hit a serious snag, well, you're fscked. I'm at the dea...
-
4
Capistrano Failure - Asset Manifest Not Created Jan 4, 2017 I setup a new Rails application using Capistrano earlier today and hit a fair number of odder than normal Capistra...
-
10
AWS Tutorial 09 - Deploying Rails Apps With Capistrano Take 1 Sep 23, 2016 In this tutorial we're going to use the latest version of Capistrano to deploy your Rails app onto one or more EC2 instances. I'm dividing this...
-
9
A Template for Rails 5 Apps with MySQL, Capistrano, Devise Sep 9, 2016 Despite my recent focus on Elixir, I still make most of my money doing Rails. Here's a template that Dv a...
-
10
README.md
-
11
Rails 实战 - Capistrano Capistrano 是 Ruby 编写的服务器自动化和部署工具,不是只能部署 Ruby 编写的项目,任何语言编写的项目都可以。 Rails 项目中使用
-
5
Capistrano and the custom maintenance page Posted: 2011-09-19 - Last updated: 2019-06-05 Tagged
-
5
Lighting fast, zero-downtime deployments with git, capistrano, nginx and Unicorn Posted: 2011-09-14 - Last updated: 2019-06-05 ...
-
7
舊專案沒做的,就把他做起來吧!一個 Rails 專案,有三台機器,每次部屬分別要手動部屬,但其實這個可以做自動化三台機器分別是不同的權限使用但都吃同一個專案,並非 load balance 那種情況這邊我只用自己的電腦來做,所以這三台機器都分...
-
12
Deploy Your Ruby on Rails App Using CapistranoBrena Monteiro on Jul 13, 2022
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK