Tag: nginx

Nginx (and Puma behind) maintenance mode for Rack/Rails applications with Capistrano

Same for Apache + Passenger: Apache (Passenger) Maintenance mode for Ruby on Rails application with Capistrano

There is a time, when we need to switch our apps into maintenance mode. Maybe it is because of some data processing stuff, maybe because of backups, deployment or whatever good reason you might have. To be honest it doesn’t matter why. What does matter, is how we should handle working users of our apps. Even if you use a zero downtime deployment using Puma (great article on howto here), sometimes you just need to shutdown your app for few minutes.

Of course all the downtimes should take place when there is the smallest amount of users online. In most cases it might be a good idea to switch application off in the middle of the night (or on Sunday, etc.), but this won’t solve our primary problem: what should we show users that are already online?

The worst scenario ever would be showing them nothing (for example by shutting down whole application server). Users probably will think, that something bad happened. Much better idea is to show users a maintenance page with some sort of information like “Temporary down for maintenance”. It would be even better, if such a page would automatically display when needed.

In order to obtain this, we need to do two things:

  1. Configure our Nginx server block
  2. Add an additional Capistrano task

503 configuration for your Nginx server block (virtual host)

Add this at the end of your server block configuration (before the last curly bracket). It will test if a maintenance.txt file exists in a tmp directory, and if so, it will serve a 503 error page. It is worth pointing out, that all the assets will be served normally (we ignore them):

# Set a maintenance page
error_page 503 @maintenance;

location @maintenance {
  if (!-f $request_filename) {
    rewrite ^(.*)$ /503.html break;
  }
}

And in a location / section of the same file:

location / {
  # If we request a file that exists (assets/images/etc) serve them
  if (-f $request_filename) {
    break;
  }

  # If we request anything else - put 503 when needed
  if (-f $document_root/../tmp/maintenance.txt){
    return 503; 
  }

  # Here should go rest of this section
}

Capistrano hookup

To automate turning maintenance page on and off, I use a set of simple Capistrano tasks, enclosed in a Nginx namespace:

namespace :nginx do
  desc 'Switch current project into maintenance mode'
  task :lock do
    on primary :db do
      within release_path do
        execute :touch, 'tmp/maintenance.txt'
      end
    end
  end

  desc 'Turn off current project maintenance mode'
  task :unlock do
    on primary :db do
      within release_path do
        execute :rm, '-f tmp/maintenance.txt'
      end
    end
  end
end

Usage example (in my deploy.rb):

namespace :deploy do
  before :starting, 'nginx:lock'
  # Some other tasks...
  after :finished, 'nginx:unlock'
end

Good luck and as few maintenance downtime as possible!

Puma Jungle script fully working with RVM and Pumactl

Finally I've decided to switch some of my services from Apache+Passenger to Nginx+Puma. Passenger was very convenient when having more than one app per server. Although I used Passenger Standard edition and sometimes apps that should have max 1-2 workers would consume at least half of pool. I was not able to prioritize apps easily. Also it started to get pretty heavy and disadvantages finally exceeded benefits of using it.

Switching static and PHP-based content from Apache to Nginx was really simple. I've installed Nginx, started it on port 82 and 445 for HTTPS and to maintain uptime, I just proxy passed each app at a time from Apache to Nginx. That way I kept Apache temporarily as a proxy engine for all the "simple to move" content and as a Passenger wrapper for my Rails apps.

I've decided to use Puma as my default Rails server for apps on this particular machine. Everything worked great until I've tried to use Jungle script to manage all the apps at once (and add it to init.d). After few seconds of googling I found Johannes Opper post on how to configure Puma Jungle to work with RVM. It turned out, you just need to edit /usr/local/bin/run-puma file and add this line:

# Use bash_profile of your rvm/deploy user
source ~/.bash_profile

After that I was able to start all the apps at once:

/etc/init.d/puma start

Unfortunately, I was not able to stop/restart Puma instances with this script. It was giving me this message:

# when stopping
/etc/init.d/puma: line 99: pumactl command not found
# when restarting
/etc/init.d/puma: line 129: pumactl command not found

Since there's an RVM on the server, Puma was installed as one of gems for one Ruby version. The same goes for Pumactl. To be able to use Pumactl I had to change the /etc/init.d/puma script a bit. First I had to change it from sh to bash script:

#! /bin/bash
# instead of
#! /bin/sh

After that I had to add my deploy user bash profile:

source ~/.bash_profile

and change how pumactl is executed in few places:

do_stop_one method (line 99):

# replace this
pumactl --state $STATEFILE stop
# with this
user=`ls -l $PIDFILE | awk '{print $3}'`
su - $user -c "cd $dir && bundle exec pumactl --state $STATEFILE stop"

do_restart_one (line 129):

# replace this
pumactl --state $dir/tmp/puma/state restart
# with this
user=`ls -l $PIDFILE | awk '{print $3}'`
su - $user -c "cd $dir && bundle exec pumactl --state $dir/tmp/puma/state restart"

do_status_one (line 168):

# replace this
pumactl --state $dir/tmp/puma/state stats
# with this
user=`ls -l $PIDFILE | awk '{print $3}'`
su - $user -c "cd $dir && bundle exec pumactl --state $dir/tmp/puma/state stats"

and that's all. After that you should be able to manage all your Puma apps with Jungle.

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑