Help us understand the problem. What is going on with this article?

capistrano + nginx + Rails 環境で簡単にメンテナンス画面を表示する方法

More than 3 years have passed since last update.

はじめに

サーバーのメンテナンス中には、503レスポンスと一緒にメンテナンス画面をレスポンスすることが多いと思います。
nginxのレイヤーで503レスポンスとメンテナンス画面を表示する仕組みと、それをcapistranoで操作する仕組みを作りました。

今まで、nginx + unicorn な環境でメンテナンスモードにするときには、いちいちnginxのnginx.confをいじって出していました。
綺麗ではないですし、オートスケール環境の時は全てのサーバーのnginx.confを修正するのも現実的ではないです。

ということで、capistranoで、

bundle exec cap production maintenance:on

を実行すると、メンテナンス画面が表示されて、

bundle exec cap production maintenance:off

を実行すると、元に戻る機能を作ってみました。

turnoutというgemを使うとメンテナンス画面を簡単に出すことはできますが、turnoutの場合は、メンテナンスモードであってもRailsへアクセスしてメンテナンス画面を表示します。
例えば、database.ymlの設定を変えているタイミングでRailsからデータベースにアクセスできないような状態でアクセスするとメンテナンス画面ではなく500エラーがレスポンスされてしまい、正しくメンテナンス画面を表示できません。
また、できればメンテナンス中はRailsが稼働するunicornサーバーへのアクセスの受け入れは避けたいです。

そのため、nginx のレイヤーで503レスポンスとメンテナンス画面を表示する仕組みを作りました。

前提

すでにcapistrano + nginx + unicornを利用したデプロイの仕組みができているものとして説明します。
「capistrano nginx unicorn」と検索するといい感じの記事がいくつかヒットするので参考にしてみてください。

設定

maintenance.html の作成

まず、maintenance.htmlを作成して、/public/maintenance.htmlに配置してください。
maintenance.htmlは、CSSなどをstyleタグで埋め込んだ1枚だけの簡単なファイルにしておくといいと思います。

deploy.rb の設定

メンテナンス中であることは/public/tmp/maintenance.htmlが存在するかどうかで判断します。
デフォルトのままだと、リリースのたびに/public/tmpディレクトリは、リセットされてしまいます。
/public/tmpをshared_pathに退避させるために、:linked_dirsに追加します。

config/deploy.rb
set :linked_dirs, fetch(:linked_dirs, []).push('public/tmp')
# 'public/tmp'以外にも :linked_dirs に追加するものがある場合は追加してください。

次に、メンテナンスモードのオン・オフを操作する、maintenance:onタスクとmaintenance:offタスクを作成します。
/lib/capistrano/tasksにrakeファイルを追加してもいいのですが、今回は簡単のため、deploy.rbに追加します。
以下を、config/deploy.rbの末尾に追加して下さい。

config/deploy.rb
namespace :maintenance do
  desc 'start maintenance'
  task :on do
    on roles(:web) do
      target_dir = "#{shared_path}/public/tmp"
      target_path = "#{target_dir}/maintenance.html"
      source_path = "#{release_path}/public/maintenance.html"
      execute :mkdir, '-p', target_dir
      execute :cp, '-f', source_path, target_path
    end
  end

  desc 'stop maintenance'
  task :off do
    on roles(:web) do
      target = "#{shared_path}/public/tmp/maintenance.html"
      execute :rm, target if test "[ -f #{target} ]"
    end
  end
end

.gitignore の設定

気になる人は、.gitignoreにpublic/tmpを追加してもいいと思います。

public/tmp/*

nginx.conf の設定

最後にnginx.confの設定をします。

nginx.conf
    upstream unicorn {
      server unix:/var/www/rails/shared/tmp/sockets/unicorn.sock;
    }

    server {
        listen       80;
        server_name  example.com;
        root         /var/www/rails/current/public;

        proxy_connect_timeout 60;
        proxy_read_timeout    60;
        proxy_send_timeout    60;

        set $maintenance false;

        if ( -f $document_root/tmp/maintenance.html ) {
            set $maintenance true;
        }

        if ( $args ~ "mode=hogehoge" ) {  
            set $maintenance false;
        }

        location @maintenance {
            try_files /tmp/maintenance.html /maintenance.html;
        }

        location /healthcheck {               # healthcheckをしている場合
            if ( $maintenance = true ) {
                return 200;
            }
        }

        location / {
            index index.html;

            if ( $maintenance = true ) {
                error_page 503 @maintenance;          # メンテナンスページの設定
                return 503;
            }

            try_files $uri @app;
        }

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

            proxy_pass http://unicorn;
        }
    }

これを設定して、

sudo service nginx configtest  # successful と表示されることを確認した上で
sudo service nginx reload

とするとnginx.confが反映されます。

/var/www/rails/current/public/tmp/maintenance.htmlが存在するときに$maintenanceをtrueにして、メンテナンス画面をレスポンスします。
また、実際のサイトを確認したい場合は、雑ですが、URLに?type=hogehogeを追加すると実際のサイトを確認できます。

AWSのELBなどでヘルスチェックをしている場合は、メンテナンスモード中は、200レスポンスを返すように設定してください。

最後に

設定は以上です。あとは、設定を含んだソースコードをデプロイした後に、

bundle exec cap production maintenance:on

を実行すると、メンテナンス画面が表示されて、

bundle exec cap production maintenance:off

を実行すると、元に戻ります。
ぜひ参考にしてみてください。

参考URL

kawasin73
ソフトウェアエンジニアです。東京大学4年Go/Ruby/Swift/Python/Javascript/Java/C
https://kawasin73.hatenablog.com/
dmmcom
総合エンタテイメントサイト「DMM.com」を運営。会員数は2,900万人を突破。動画配信、FX、英会話、ゲーム、太陽光発電、3Dプリンタなど40以上のサービスを展開。沖縄での水族館事業参入、ベルギーでのサッカークラブ経営など、様々な事業を手掛ける。また2018年より若手起業家の支援を強化、「DMM VENTURES」による出資や、M&Aなどを積極的に展開している。
https://dmm-corp.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away