Apacheのmod_rewriteを使って、フェイルオーバーみたいなことができないかと思い、ちょっとやってみることにしました。
フェイルオーバーといったら大げさな感じですが、もうちょっと本格的にやるならmod_proxy_balancerを使ったほうがいいと思うので。。。
現在下記のような構成で運用しているサーバがありますが、静的ファイルを配信するサーバBが落ちている場合やオフになっている場合などは、サーバBから配信したいという感じです。
サーバA -> CGIの処理や比較的軽いHTMLを処理 サーバB -> 主に画像などの重たい静的ファイルを処理
かなり限定的な環境だと思うのですが、サーバAで一度リクエストを受け、画像ファイルだけをリダイレクトでサーバBに転送しています。
なので、サーバBが落ちていたりした場合、サーバBに転送せずにサーバAで返却したいというわけです。サーバAでも同じデータを持っている必要があります。
調べたところRewriteMapを使うことで外部プログラムを呼び出すことができるようです。今回はこれを使ってみます。
サーバが稼働しているか定期的にヘルスチェックをする必要がありますが、今回はサーバAにリクエストされた際に、外部プログラムを通して5秒間隔でサーバBにHTTPリクエストをおこなってチェックしてみます。
外部プログラムはRubyを使ってみます。外部プログラムの呼び出しは起動時に一回のみおこなわれ、STDIN、STDOUTを通して処理されるとのことで、オーバーヘッドはあまり気にする必要はなさそうです。
こんな感じで作ってみました。
## /usr/local/bin/healthcheck.rb #!/usr/bin/env ruby # Buffered I/Oを使わない $stdin.sync = 1 $stdout.sync = 1 require 'net/http' STATUS_DOWN = 'DOWN' STATUS_UP = 'UP' STATUS_NULL = 'NULL' CHECK_PERIOD = 5 # ヘルスチェックの間隔(秒) HTTP_TIMEOUT = 2 # HTTPコネクションと読み込みのタイムアウト(秒) lastcheck = 0 laststatus = STATUS_NULL while true buffer = $stdin.gets hostname, port, path = buffer.chomp.split(/:/, 3) nowcheck = Time.now.to_i if (lastcheck + CHECK_PERIOD) > nowcheck $stdout.puts laststatus else response = nil if hostname begin http = Net::HTTP.new(hostname, port ? port : 80) http.open_timeout = HTTP_TIMEOUT http.read_timeout = HTTP_TIMEOUT http.start {|p| response = p.head(path ? path : '/') } rescue # end end if response and response.code.to_i == 200 $stdout.puts laststatus = STATUS_UP else $stdout.puts laststatus = STATUS_DOWN end lastcheck = nowcheck end end
注意:上記のスクリプトにはバグがあります。
RubyのNet::HTTPでハマる – フタなしカンヅメ
Apacheの設定ファイルは下記のように書きました。
<IfModule mod_rewrite.c> RewriteEngine On # 必ずロックファイルを指定 RewriteLock /tmp/map.lock # RewriteMapで使う外部プログラムを指定 RewriteMap healthcheck-map prg:/usr/local/bin/healthcheck.rb </IfModule> <Directory /> RewriteEngine On # RewriteMapを呼び出す時は下記のように指定 # RewriteRuleでも呼び出せます RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ [NC] RewriteCond ${healthcheck-map:192.168.1.3:80:/} ^UP$ [NC] RewriteRule (.*) http://example.com/$2 [L,R] </Directory>
RewriteCond ${healthcheck-map:192.168.1.3:80:/} ^UP$ [NC] - healthcheck-map => 使用するRewriteMapの名前 - 192.168.1.3 => ヘルスチェックするホスト名またはIPアドレス - 80 => ポート番号 - / => チェック先のリクエストパス のように「:」で区切って呼び出します
一番ハマったのが、RewriteMapで外部プログラムを指定する部分。いろいろいじっても下記のようなエラーが・・・。
map lookup FAILED: map=healthcheck-map key=localhost:80:/
原因は、
参考サイト
mod_rewriteでサーバーの負荷が高いときだけリダイレクトする – inspfightmanの日記
Apache module mod_rewrite
mod_rewrite – RewriteMap – とみぞーノート
rubyスクリプトでRewriteMapを書く際の注意点 — Stack-Style
mod_rewrite:RewriteMapで柔軟なURL書き換え | ゆーすけぶろぐ