第9回 メンテナンスページへの切り替え

2008/06/03

今回は、Capistrano の deploy:web:disabledeploy:web:enable タスクについて。


cap -T で表示される説明によれば、それぞれのタスクの目的は次の通り:

  • deploy:web:disable -- 訪問者にメンテナンスページを見せる。
  • deploy:web:enable -- 再びアプリケーションをWebアクセス可能にする。

うーむ。便利そうであるが、これだけでは何がどうなるのか今ひとつピンと来ない。ソースコードを見てみよう(一部、省略)。

namespace :deploy do
  namespace :web do
    task :disable, :roles => :web, :except => { :no_release => true } do
      require 'erb'
      on_rollback { run "rm #{shared_path}/system/maintenance.html" }

      reason = ENV['REASON']
      deadline = ENV['UNTIL']

      template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.rhtml"))
      result = ERB.new(template).result(binding)

      put result, "#{shared_path}/system/maintenance.html", :mode => 0644
    end
    
    task :enable, :roles => :web, :except => { :no_release => true } do
      run "rm #{shared_path}/system/maintenance.html"
    end
  end
end

deploy:web:disable タスクがやっているのは、要するに、メンテナンスページとして表示したい HTML 文書を ERB で生成して、それを #{shared_path}/system/maintenance.html というファイルに書き込む、ということだ。

メンテナンスページのテンプレートとしては、maintenance.rhtml が用意されている。File.dirname(__FILE__) は、このソースコードのあるディレクトリだから、Capistrano のインストールディレクトリ(環境によって異なるが、/usr/lib/ruby/gems/1.8/gems/capistrano-2.3.0/ など)の下の lib/capistrano/recipes/templates/maintenance.rhtml ということになる。テンプレートを指定する変数は用意されていないので、独自のメンテナンスページを用意したければ(もちろん、用意したいよね)、このコードを参考に自分でタスクを書いて、config/deploy.rb に追加する。

そして、deploy:web:enable タスクは、そのファイルを消す。

通常、shared_path は変数 :deploy_to で指定したディレクトリの下にある shared ディレクトリである。つまり、/var/rails/ballad にデプロイするなら、/var/rails/ballad/shared/system/maintenance.html が問題のファイルになる。


さて、このファイルが存在すれば、それだけで訪問者にメンテナンスページを見せられるのであれば話は簡単だが、そうではない。

Apache の mod_rewrite のディレクティブと少し格闘する必要がある。

私はこの mod_rewrite という文字を見るとやや憂鬱になる。mod_rewrite はスイス製のアーミーナイフであり、黒魔術(voodoo)である(Apache module mod_rewrite)。何でも設定できて柔軟だが、その奇妙な構文が私を幻惑する。

やれやれ。


何はともあれ、次のように httpd.conf に書くと、deploy:web:disable タスクが使えるようになる。

<VirtualHost *:80>
  ServerName ballad.example.com
  DocumentRoot /var/rails/ballad/current/public
  ErrorLog /var/log/httpd/ballad-error_log
  CustomLog /var/log/httpd/ballad-access_log combined env=!nolog

  <Directory /var/rails/ballad/current/public>
     Order Deny,Allow
     Allow from All
  </Directory>

  RewriteEngine On
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteCond %{REQUEST_URI} !^/images/
  RewriteCond %{REQUEST_URI} !^/javascripts/
  RewriteCond %{REQUEST_URI} !^/stylesheets/
  RewriteRule ^.*$ /system/maintenance.html [L]

  RewriteRule ^/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]

  ProxyRequests Off
  ProxyPass / balancer://ballad_cluster/

  <Proxy balancer://ballad_cluster/>
    BalancerMember http://127.0.0.1:3000
    BalancerMember http://127.0.0.1:3001
    BalancerMember http://127.0.0.1:3002
    BalancerMember http://127.0.0.1:3003
  </Proxy>
</VirtualHost>


少し黒魔術の説明を試みよう。

まず、RewriteEngine On で mod_rewrite を有効にする。

次に、RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f で、

もし %{DOCUMENT_ROOT}/system/maintenance.html というファイルが存在すれば

という条件を表す。%{DOCUMENT_ROOT} には、上の方で宣言している値 /var/rails/ballad/current/public が埋め込まれる。

続く3行の RewriteCond は

/images または /javascripts または /stylesheets ディレクトリにあるファイルへのアクセスでなければ

という条件を表す。URIパスのパターンの前にある ! は否定を示す。

その次の RewriteRule ^.*$ /system/maintenance.html [L] は、

正規表現で ^.*$ に該当するパスを /system/maintenance.html に書き換えよ

という意味である。この正規表現はあらゆるパスに該当するので、要するにすべてのアクセスに対してメンテナンスページが表示されることになる。

この行の末尾についている [L] は「これ以上URLの書き換えをするな」という意味のフラグである。ちなみに、「L」は「Last rule」の略だそうだ。

その次の RewriteRule ^/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L] は、

/images または /javascripts または /stylesheets ディレクトリにあるファイルへのアクセスについては、そのまま返せ

ということである。この行にも [L] フラグが付いているので、これらのディレクトリにあるファイルへのアクセスは、常に(メンテナンス中かどうかにかかわらず)Apache 自体が処理することになる。これは、メンテナンスページにも画像等を表示できるようにするためであるが、Rails アプリケーションへの負荷を減らすという効果もある。

と、このように説明をしてみれば、何のことはないのであるが、mod_rewrite のドキュメントを読むのは骨が折れる。「苦労は買ってでもせよ」が座右の銘という方にはお勧めだ。

ProxyRequests Off 以下の記述は、Apache をロードバランサ(リバースプロキシ)として利用するためのものである。今回の話とは直接は関係ないが、ローカルホストの 3000 番から 3003 番ポートで動いている Rails アプリケーションにリクエストを中継している。maintenance.html が存在しない状態(つまり、普通の状態)であれば、こちらの仕組みが働いて、サイト訪問者に Rails アプリケーションが見える。


では、deploy:web:disable タスクを実行してみよう。

% cap deploy:web:disable
  * executing `deploy:web:disable'
    servers: ["alpha.oiax.jp"]
 ** sftp upload #<StringIO:0xb7855b6c> -> /var/rails/ballad/shared/system/maintenance.html
    [alpha.oiax.jp] /var/rails/ballad/shared/system/maintenance.html
    [alpha.oiax.jp] done
  * sftp upload complete

ブラウザで http://ballad.example.com/(実際には存在しません)を開くと、確かにメンテナンスページが表示される。

画面に表示されたメッセージには sftp という文字が見える。ということは、メンテナンスページはローカルホスト(あなたのパソコン)で作られてリモートホストに送り込まれるわけだ。

次に、deploy:web:enable タスク。

% cap deploy:web:enable
  * executing `deploy:web:enable'
  * executing "rm /var/rails/ballad/shared/system/maintenance.html"
    servers: ["alpha.oiax.jp"]
    [alpha.oiax.jp] executing command
    command finished

お、元に戻った!

めでたし、めでたし。