Displaying posts tagged with

“サーバ管理”

muninの集計負荷をtmpfsで低減する(集計時間の短縮も)

リソース監視ツールのmuninは手軽に使えて素晴らしいのだが、監視対象のサーバが増えてくると集計サーバの負荷(主にディスクI/O)が馬鹿にならなくなってくる。そこでこちらのサイトを参考にして、データファイルとHTMLファイルをtmpfs上に移動してみた。

環境

  • CentOS 6
  • munin-1.4.5 (EPELリポジトリからインストール)
  • メモリ: 8GB

データファイルとHTMLファイルの場所は /etc/munin/munin.conf の dbdir と htmldir で変更できる…はずなのだが、プラグインの中には /var/lib/munin のパスを決め打ちにしているものがあるらしく、dbdir を変更するとうまく動かなかった。仕方がないのでHTMLファイルを /var/lib/munin 以下に移動して、tmpfs を /var/lib/munin にマウントすることにした。

まずHTMLファイルを移動する。以下の操作はroot権限で、muninの集計を停止してから行う。

muninの集計を止めるには、munin-cronの実行権限を外すか、/etc/cron.d/munin を編集してcronによる定期実行を止める。

# munin-cronの停止
chmod ugo-x /usr/bin/munin-cron
 
# 念のためmuninプロセスが動いていないか確認しておく
ps aux | grep munin

次にHTMLファイルをディレクトリごとコピー、そのパスにあわせてmuninとApacheの設定を変更する。

cp -a /var/www/html/munin/ /var/lib/munin/html
 
vim /etc/munin/munin.conf
# htmldir /var/lib/munin/html
 
vim /etc/httpd/conf.d/munin.conf
# 私の環境ではcgiディレクトリは /var/www/munin/cgi に移してあるので
# ScriptAlias /munin/cgi/ /var/www/munin/cgi/
# Alias /munin/ /var/lib/munin/html/

ここまで終えたら一旦muninの集計を再開し、正しく動くことを確認しておく。確認できたら再び集計を停止して、tmpfsの設定に移る。tmpfsに割り当てる容量だが、とりあえず参考サイトに従って1GBとした。二十数サーバを数年間監視した結果のデータ+HTMLファイルが300MB弱だったので、1GBあれば当分は大丈夫だろう。

vim /etc/fstab
#tmpfs  /var/lib/munin  tmpfs  rw,size=1024M   0 0

一旦データを退避してから /var/lib/munin にtmpfsをマウントし、退避しておいたデータを戻す。

mv /var/lib/munin /var/lib/munin.backup
mkdir /var/lib/munin
mount /var/lib/munin
cp -a /var/lib/munin.backup/* /var/lib/munin/

これでmuninが更新するファイルは全てメモリ上に置かれることになった。が、このままだとマシンが再起動されるとデータが消滅してしまうので、バックアップ策を講じる必要がある。参考サイトではcronを使って1時間ごとにバックアップを行っているが、それだと更新中の不完全なデータがバックアップされる危険性がある。そもそもmuninの集計はcronで5分ごとに実行されるのだから、次のような方針でバックアップ/リストアをすることにした。

  • 30分に一回、集計の「後に」データをバックアップする
  • 毎回、集計の「前に」データの有無をチェックして、なければデータをリストアする。このチェックは単純にファイルの有無を調べるだけなので、5分ごとに実行しても負担にはならない

このためにmunin-cronをラップする次のようなシェルスクリプトを書いた。

#!/bin/sh
 
rsync=/usr/bin/rsync
munin_cron=/usr/bin/munin-cron
munin_dir=/var/lib/munin
backup_dir=/home/munin/backup
lock_file=/var/tmp/munin-cron-backup.lock
 
die() {
        echo $1
        exit 1
}
 
lock() {
        test -e $lock_file && die "Backup/retore process is already runnning" 
        touch $lock_file
}
 
unlock() {
        rm -f $lock_file
}
 
backup() {
        lock || die "Failed to lock the backup process"
        $rsync -au $munin_dir/ $backup_dir/
        unlock
}
 
restore() {
        lock || die "Failed to lock the restore process"
        if [ ! "(" -e $munin_dir/datafile -a -d $munin_dir/html ")" ]; then
                $rsync -au $backup_dir/ $munin_dir/
        fi
        unlock
}
 
restore
 
test -x $munin_cron && $munin_cron
 
if [ "$1" = "backup" ]; then
        backup
fi

このスクリプトでは datafile というファイルと html ディレクトリの存在をもって、「データが存在する」と判断する。一応、同時実行を避けるための簡易的なロックも付けてみた。5分に一回しか実行されないと分かっているのだから、この程度でも多少は意味があるだろう。各種コマンドやバックアップ先などのパスは好みで変えて欲しい。私はこのシェルスクリプトやバックアップを /home/munin 以下にまとめることにした。

あとはmunin-cronの代わりに、/etc/cron.d/munin の中でこのスクリプトを実行するよう設定する。30分に一回は引数に backup を与えて実行することで、バックアップが作成される。

MAILTO=root

0,30 * * * * munin /home/munin/bin/munin-cron.sh backup
5,10,15,20,25,35,40,45,50,55 * * * * munin /home/munin/bin/munin-cron.sh

リストアは集計前に勝手に行われるので、再起動しても何もしなくていい。

最後に実施前後のCPU使用率のグラフを載せておく。確かにI/O待ちはガクンと減った(ちょっとだけ出ているのはバックアップ)。

CPU使用率の変化

それから集計に要する時間も多少は短縮されたようだ。もっともネットワーク経由でクライアントに接続する部分がボトルネックになっているようなので、あまり結果は安定していない。

munin集計時間の変化

nginxでメンテナンス中画面を表示する

どのURLにアクセスされてもステータスコード503を返して /home/www/maintenance.html を表示する設定。

server {
    server_name  your.hostname; 
    error_page 503 /maintenance.html; 
    location / { 
        return 503;
    }
    location = /maintenance.html {
        root /home/www;
    }
}

同等の設定をApache(>=2.2)で書くと次のようになる。

<VirtualHost *:80>
    ServerName your.hostname
    DocumentRoot /home/www
 
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !=/maintenance.html
    RewriteRule .* - [R=503]
 
    ErrorDocument 503 /maintenance.html
</VirtualHost>

InnoDBの意外な制約: Got error 139 from storage engine

環境: MySQL 5.0 (追記

某CMSにて、1つのテーブルにTEXT型のフィールドをたくさん(10前後)作ったところ、次のようなエラーが出てデータを保存できなくなった。

Got error 139 from storage engine

このエラーメッセージで検索すればいろいろと情報が出てくるが、こういうことらしい:

  • InnoDBの行サイズの上限はページサイズの約半分で、デフォルトでは約8000バイト
  • 可変長カラム(VARBINARY, VARCHAR, BLOB, TEXT)のデータは行の外部に保存されるが、先頭の768バイトだけは行の内部に保存される
  • よって例えば一つのテーブルに11個のTEXT型フィールドを作り、それぞれに768バイト以上のデータを入れようとすると、768*11=8448 > 8000 なので保存できない

ページサイズは8〜64KBまで設定できるが、変更するためにはMySQL本体をコンパイルし直した上でテーブルスペースとログファイルを再作成する必要があるらしく、運用途中での変更は難しい。よってどうしてもInnoDBでなければならない場合は、テーブルを分割するなどして対処するしかない。

件のCMSの場合は特にInnoDBでなくても良かったので、テーブルをMyISAMに変換することで回避できた。

ALTER TABLE TABLE_NAME ENGINE=MyISAM;

MyISAMの場合、行サイズの上限は64KBで、かつTEXTやBLOBの保存に要するのは9〜12バイトなので、同じ問題はまず起こらない。最近は特に何も考えずにInnoDBを使っていたので、ヒヤリとさせられた。

参考にしたサイト:

追記

SH2さんからのコメントで、MySQL 5.1+InnoDB Plugin/MySQL 5.5以降で新たなファイルフォーマット(Barracuda)を使用すれば状況は改善される、と教えていただきました。どうやらTEXT/BLOB一つあたり20バイトだけになるようです。詳細は以下のリンクなどを参照してください(私もまだ読んでいる途中ですが)。

CentOS 5にNginxをインストールする

CentOS 5.5にNginxをセットアップした記録。

EPELリポジトリにrpmパッケージもあるようだが、かなりバージョンが古かったので(0.6.39)ソースからインストールすることにした。現時点での最新版(stable)は0.8.53。

まず下準備。nginxユーザの追加とビルドに必要なパッケージのインストール。

sudo useradd -s /sbin/nologin -d /usr/local/nginx -M nginx
sudo yum install gcc openssl-devel pcre-devel zlib-devel

続いてソースをダウンロードしてコンパイルする。Nginxはコンパイル時にしか拡張モジュールの組み込みができないので、ビルドの過程はシェルスクリプトの形で残しておいた方が良い。コンパイルオプションについてはWikiを参照のこと。一番下のExample 5(RedHat向け)を参考に調整を行った。

#!/bin/sh
 
NGINX=nginx-0.8.53
 
# インストール先は /usr/local/nginx になる
cd $NGINX \
&& make clean \
&& ./configure \
  --conf-path=/etc/nginx/nginx.conf \
  --error-log-path=/var/log/nginx/error.log \
  --pid-path=/var/run/nginx/nginx.pid  \
  --lock-path=/var/lock/nginx.lock \
  --user=nginx \
  --group=nginx \
  --with-http_stub_status_module \
  --with-http_ssl_module \
  --with-http_gzip_static_module \
  --with-http_realip_module \
  --http-log-path=/var/log/nginx/access.log \
  --http-client-body-temp-path=/var/tmp/nginx/client/ \
  --http-proxy-temp-path=/var/tmp/nginx/proxy/ \
  --http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ \
&& make

正常にビルドが完了したらインストール。足りないディレクトリも作っておく。

sudo make install
sudo mkdir /var/tmp/nginx/{proxy,client,fcgi}

この段階で正常に起動するかどうかを確認しておく。rootで ${prefix}/sbin/nginx を実行後、http://hostname/ にアクセスして、「Welcome to nginx!」と表示されたらOK。

sudo /usr/local/nginx/sbin/nginx
 
# 確認できたら終了しておく
sudo /usr/local/nginx/sbin/nginx -s quit

この後、設定ファイル(上のコンパイルオプションの場合は /etc/nginx/nginx.conf)を編集してサーバの設定を行うことになるが、そこは省略。

サービス起動ファイルを作る。CentOS(RedHat系Linux)ならひな形がWikiに用意されているので、必要な部分を書き換えた後 /etc/init.d/nginx に保存して、 chkconfig コマンドで設定する。

# 実行ファイルのパスなど変更しておく
vi /etc/init.d/nginx
 
chmod 755 /etc/init.d/nginx
chkconfig --add nginx
chkconfig nginx on
 
# 今すぐ起動するなら
service nginx start

続いてログのローテーションの設定。/etc/logrotate.d/nginx を以下の内容で作成する。パスやパラメータは各自で調整のこと。

/var/log/nginx/*log {
    missingok
    notifempty
    delaycompress
    sharedscripts
    postrotate
        /usr/local/nginx/sbin/nginx -s reopen
    endscript
}

設定ファイルのチェックも行っておく。

sudo logrotate -d /etc/logrotate.conf

設定ファイルの内容について後で書くかも。

MySQL MEMORYテーブルのサイズをmuninで監視する

DB内のMEMORYテーブルのサイズを、テーブルごとにグラフ化するための munin プラグインを作ってみました。

mysql_memory_tables_

インストール

munin-node 本体のプラグインディレクトリ(/usr/share/munin/plugins など)にコピーしてください。

設定

ワイルドカードプラグインなので、mysql_memory_tables_{チェック対象となるDB名} という名前でシンボリックリンクを張ってください。

ln -s /usr/share/munin/plugins/mysql_memory_tables_ /etc/munin/plugins/mysql_memory_tables_db1

データを取得するには対象DBの INFORMATION_SCHEMA にアクセスする権限が必要です。プラグイン設定ファイル(/etc/munin/plugin-conf.d/munin-node など)で mysql コマンドに渡すユーザ名・パスワードを指定してください。

[mysql_memory_tables_*]
env.mysqlopts -u cicindela --password=hogehoge

動作確認

munin-run コマンドでテストができます。

# 値の表示
munin-run mysql_memory_tables_db1
 
# グラフ設定情報の表示
munin-run mysql_memory_tables_db1 config

動作が確認できたら munin-node を再起動してください。

WARNING: mismatch_cnt is not 0 on /dev/md0

Software RAID1を組んでいるサーバで、CentOS 5.3 -> 5.4にアップグレードしたところ、週末ごとに次のような警告メールが届くようになった。

/etc/cron.weekly/99-raid-check:

WARNING: mismatch_cnt is not 0 on /dev/md0

/proc/mdstat 等には特に異常などは見当たらないし、構成の異なる複数のサーバで同じ現象が起こったので、ハード的な問題ではないだろうと判断し、忙しかったこともあってそのままにしていた。しかしやはり気持ち悪いので、きちんと調べてみた。

メール中にある通り、この警告は /etc/cron.weekly/99-raid-check というスクリプトから送信されている。このスクリプトを提供しているのは mdadm パッケージで、5.4で更新されている。更新内容はレッドハットのサイトで RHBA-2009:1382-1 として提供されており、99-raid-check について説明しているのは、次の2箇所である。

* LinuxのソフトウェアRAIDスタックでは、データスクラビングがサポートされています(RAIDアレイのディスクを読み取って不良セクタを検索し、不良セクタが見つかると、他のディスクやパリティの情報を使用して、不良セクタを正常なデータで書き直します)。しかし、mdadmパッケージはこの機能を利用していませんでした。今回のパッケージは、cronジョブが/etc/cron.weeklyに追加されており、ディスクをチェックして不良セクタがないかどうかを確認し、見つかった場合は修復します。(BZ#233972)

* 今回のアップデートでは、mdadmに冗長アレイでの「データスクラビング」を可能にする新しい機能が追加されています。データスクラビングでは、冗長アレイ内のドライブ上で不良セクタが検索され、見つかった場合は他のドライブのデータを使用して修正されます。それによって、読み取りエラーを返すセクタは再構築されます。注:この新しいパッケージでは、データスクラビングはデフォルトでオンになっています。この機能は週1回実行され、実行中にある程度のパフォーマンス低下を引き起こす場合があります。パフォーマンス上の理由でこの機能を無効にしたい場合、アレイに対して実行されるチェックのタイプを管理したい場合、あるいは、どのアレイをチェックするかを制御したい場合は、/etc/sysconfig/raid-checkファイルを編集します。オプションを設定する方法の詳細は、そのファイルにコメントとして含まれています。(BZ#513200)

「データスクライピング(Data Scrubbing)」とは何か、なぜ重要なのかについては、ここで示されているBugzilla #233972およびその先のリンクに説明がある(一箇所リンクが切れているのはRAID/Software – GEntoo Linux Wikiだと思われる)。

データスクライピングを実行するには、root権限で次のコマンドを実行する。これによって不良セクタが検出され、自動的に修復される。99-raid-check が実行するのも、まさにこのコマンドである(少なくともデフォルトの設定においては)。

echo check > /sys/block/mdX/md/sync_action

ではこのコマンドと、問題の mismatch_cnt とはどう関係するのか。/etc/sysconfig/raid-check のコメントを読むと次のように書いてある。

If it finds good sectors that contain bad data (meaning that the data in a sector does not agree with what the data from another disk indicates the data should be, for example the parity block + the other data blocks would cause us to think that this data block is incorrect), then it does nothing but increments the counter in the file /sys/block/$dev/md/mismatch_count.

つまりセクタ自体は正常だが、中に入ってるデータがディスク間で異なる場合に mismatch_cnt が増加するということらしい。

ならば mismatch_cnt が 0 でないということは、ただちにRAIDが正常に機能していないことを意味するのだろうか?いろいろなMLの投稿などを読む限り、少なくとも swap パーティションを含むRAIDデバイスにおいては、全く正常であっても各ディスク間の内容が異なり得る…すなわち mismatch_cnt が0でないことがあり得るようだ。では swap を含まないパーティションの場合は?私の(また他のCentOS利用者の)ように、/boot のみを含む md0 の場合は?……これがよく分からない。この投稿を読む限り、ある種の状況においてはそうしたことも起こり得る、とのことである。

もしそうした状況に該当するなら。mismatch_cnt が 0 でなくとも、異なっているのは実際にはファイルシステムからアクセス不能な部分であって、システムの運用には影響しないのであれば。修復する方法はある。check の代わりに repair を使うことである。

#このコマンドを実行する前に以下の説明をよく読んでください!!
echo repair > /sys/block/mdX/md/sync_action

ただし /etc/sysconfig/raid-check には、次のような恐ろしいコメントが書いてある(強調は私による)。

The repair option does the same thing, but when it encounters a mismatch in the data, it automatically updates the data to be consistent. However, since we really don’t know whether it’s the parity or the data block that’s correct (or which data block in the case of raid1), it’s luck of the draw whether or not the user gets the right data instead of the bad data.

というわけで、以下は「それでも自己責任でこの方法に賭けるぜ!」という方のみ読んでください。

まず各デバイスの mismatch_cnt の値をチェックしておく。

cat /sys/block/md0/md/mismatch_cnt

そして repair を実行する。

sudo sh -c 'echo repair > /sys/block/md0/md/sync_action'

進行状況は /proc/mdstat で確認できる。

cat /proc/mdstat

repair を実行しただけでは mismatch_cnt は更新されないので、もう一度 check を実行する。進行状況はやはり /proc/mdstat で確認できる。

sudo sh -c 'echo check > /sys/block/md0/md/sync_action'

終了したら mismatch_cnt をチェックして、0 になっているか確認する。

cat /sys/block/md0/md/mismatch_cnt

……

参考にしたサイトははてブの mismatch_cnt タグにまとめてあるので、この記事に書いてあることを鵜呑みにせず、自分で調べ自分で判断するようお願いします。

XREA+からエックスサーバーに移転

XREAが重過ぎて使い物にならなくなったので、エックスサーバーに移転した。ここを選んだ理由は

  • PHP5, MySQL5が使える
  • 比較的値段が安い(X10プランで月額1050〜1260円)
  • ネット上の口コミでは速度に対する評価が高い
  • “ハズレ”を引いたときの救済措置簡単サーバ移動機能がある
  • SSHが使えないので、派手なことをするヤツは少ないだろうという希望的観測

今のところ速度には満足。サーバの管理画面も使いやすい(若干パワーユーザ向けだが)。WordPressも問題なく移転できた。

WordPressの移転にはいろいろな方法があるようなので、私の方法も紹介しておく。最も原始的かつ確実な方法で行った。

エックスサーバーでの下準備

  1. 管理画面「MySQL設定」で新しいMySQLデータベースとMySQLユーザを作る
  2. 「ドメイン設定」で独自ドメインを登録する
  3. できればここで /etc/hosts ファイルを書き換えてきちんとアクセスできることを確認しておく。WebサーバのIPアドレスは「DNSレコード設定」で確認できる

旧データのバックアップと修正

  1. XREAの管理画面「データベース」で「保存」ボタンを押してバックアップを作成する
  2. 作成されたmysql.dumpおよびサイトの全ファイルをローカルにコピーする
  3. wp-config.php を開き、データベース設定をエックスサーバーのものに書き換える

エックスサーバーにアップロード

  1. 管理画面からphpMyAdminにログインして、mysql.dumpを実行
  2. ドキュメントルートに全ファイルをコピー
  3. 例によって /etc/hosts を書き換え、WordPressが正常に動くことを確認

あとはDNSを変更すればOK。

mymemcheckをCentOSで動かす

サーバ/インフラを支える技術』に載っていた mymemcheck を試してみようと思って CentOS 5 上で実行してみたところ、いくつかの Perl モジュールが足りなくて動かなかった。例によって rpmforge で検索してみたら、やはり見つかった。

sudo yum --enablerepo=rpmforge install perl-Readonly perl-UNIVERSAL-require

相変わらず便利過ぎるぜ rpmforge。

mod_dosdetector の使い方

サーバ/インフラを支える技術』で知ったDoS攻撃判定モジュール mod_dosdetector の使い方。mod_dosdetecotr のバージョンは 0.2、環境は Apache 2.2.3 @ CentOS 5。

改造版も公開しています。

インストール

wget http://ncu.dl.sourceforge.net/sourceforge/moddosdetector/mod_dosdetector-0.2.tar.gz
tar xvzf mod_dosdetector-0.2.tar.gz
cd mod_dosdetector-0.2
make
sudo make install

httpd.conf 設定例

# 実際にはこの一行は make install で追加される
LoadModule dosdetector_module modules/mod_dosdetector.so
 
DoSDetection     on
DoSPeriod        5
DoSThreshold     10
DoSHardThreshold 25
DoSBanPeriod     30
DoSTableSize     100
DoSIgnoreContentType  image|javascript|css

DoS判定の肝となる数値は DoSPeriod, DoSThreshold, DoSHardThreshold, DoSBanPeriod の4つで、上の例ではだいたい次のような意味になる:

  1. 同一IPアドレスから5秒間に10回以上のアクセスがあった場合「DoS攻撃の疑いあり」と見なし、その後30秒間のアクセスに対しては環境変数 SuspectDoS をセットする(値は”1″)
  2. さらに5秒間のアクセス回数が25回に達した場合、「激しいDoS攻撃の疑い」と見なし、環境変数 SuspectHardDoS もセットする(値は”1″)
  3. 初めに「DoS攻撃の疑いあり」と判定してから30秒が経過したら、次のアクセスでもう一度判定をやり直す。直近5秒のアクセスが10回を下回っていれば、疑いは晴れる

アクセス数の計測はファイルの種類・有無を問わずに行われるため、デフォルトではページ内の画像やCSSなども計測対象となってしまう。それらを除外したい場合は、DoSIgnoreContentType ディレクティブで除外したいファイルの MIME type を正規表現で指定しておく(ファイル名(URL)ではないので注意)。各種画像ファイル・JavaScript・CSSファイルを除外したい場合は上例のように設定しておけば良い。

ただし多くの環境では拡張子 ico に対する MIME type の指定は行われていないはずなので、そのままだと favicon.ico が text/plain 等と見なされて除外されない(.ico に限らず、未設定の場合は httpd.conf の DefaultType になる)。次のように AddType を追加しておけば image と判定される。

AddType  image/vnd.microsoft.icon  .ico

ちなみにこれがアイコンファイルの正式な MIME type である。

DoS攻撃に対する防御設定

mod_dosdetector が行うのはあくまでも攻撃の”検出”だけなので、防御する方法はまた別に設定する必要がある。多くの場合は mod_rewrite と併用することになるだろう。例えば「激しいDoS攻撃の疑い」なアクセスに対してステータスコード503 + 静的なHTMLを返す場合は次のように設定する(Apache 2.2 only)。

RewriteEngine On
RewriteCond %{ENV:SuspectHardDoS} =1
RewriteRule .*  - [R=503,L]
 
ErrorDocument 503 /server_is_busy.html

しかし現実には、攻撃の判定とその後の防御は、匙加減が難しい。どの程度のアクセスを攻撃と見なすかはサイトによって千差万別だろうし、あまりに頻繁・過剰な反応をしてしまうとユーザが離れてしまう。

そこで個人的には、まず厳し目の設定を行って、ログを取ってみることをおすすめする。「DoS攻撃の疑いあり」なアクセスを普通のログと分けて記録するには次のように設定する。

LogFormat "%{SuspectHardDoS}e %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" dos_suspect
CustomLog logs/dos_suspect_log dos_suspect env=SuspectDoS

ログの行頭に環境変数 SuspectHardDoS が記録されるため、どのような経過をたどって「疑いあり」から「激しい攻撃の疑い」へと移行したかも分かる。

後はこの記録を見つつ、通常のアクセスが引っかからないような適当な設定を探ることになる。もしサーバリソースの都合上、どうしても通常のアクセスが引っかてしまう可能性があるのなら、その時は防御方法をより穏当なものに設定することもできる(このあたりの柔軟性は mod_dosdetector の設計の妙だと思う)。

その他

上で触れなかったディレクティブについて少々:

DoSDetection
DoS判定機能の有効・無効を設定。on で有効
DoSTableSize
クライアントの追跡記録の最大保存数。この数値を大きくすればより多くのクライアントを同時に追跡できるが、メモリ使用量と負荷が上昇する
DoSShmemName
クライアントの追跡記録を保存しておくための共有メモリの名前。普通は設定する必要は無いはず

最後に

素晴らしいソフトウェアをオープンソースとして公開してくださった田中慎司さん(そして株式会社はてな)に感謝!

Apache 2.2ではmod_rewriteのRフラグで任意のステータスコードを返せる

昔Apache 2.0系で試したときは無理だったのに、2.2系ではできるようになっていたのか。

mod_rewriteで503 – Do You PHP はてな

上エントリーで引用されているドキュメントは古くて、現在は次のような記述に置き換わっている。

While this is typically used for redirects, any valid status code can be given here. If the status code is outside the redirect range (300-399), then the Substitution string is dropped and rewriting is stopped as if the L flag was used.

mod_rewrite – Apache HTTP Server

『このフラグは通常リダイレクトのために用いられるが、任意の妥当なステータスコードを指定することができる。もしリダイレクト以外のステータスコード(すなわち300〜399以外)が指定された場合、置換文字列は無視され、さらにLフラグが指定されたときと同じようにURLの書き換えが停止される。』

つまりリダイレクトもURLの書き換えも行われないので、デフォルトのエラー画面以外を表示するには ErrorDocument ディレクティブで指定しておく必要がある。

RewriteEngine On
RewriteCond %{some_condition} =1
RewriteRule .*  - [R=503]
 
ErrorDocument 503 /server_is_busy.html

ソースに付属している文書(CHANGES)によれば、この仕様に変わったのはバージョン2.1.1。Bugzillaで以下のような議論も見つけた。

In the <300, >=400 cases, one can think of this as [R{esponse}=nnn} as opposed to [R{edirect}], and it really harms nothing to overload it.

Bug 45478 – R flag in RewriteRule does not honor 400 and 500 range error code

Redirect の ‘R’ ではなく Response の ‘R’ と見なしてくれ、と。なるほど…。