皆様こんにちは。シャノンインフラ担当の藤井です。
初めましてですので、簡単に自己紹介を致します。
インフラ歴はIDCオペレータや監視要員、運用SE、OSSインフラ構築含めて12年ほどです。それまではベトナムのホーチミンでインターネットカフェを経営したりして日本には見向きもしませんでした。ところが海外に居ると、急激に極端に日本を意識し始め、今では地元の御輿や消防団、弓道などに参加し、日本的なものについて「分かる」ことではなく「身につける」をモットーに、精進しております。
株式会社シャノンには3月に入社したばかりです。社内の平均年齢が、今月30歳を突破したとのことです。私は数日前に不惑に至り、社内の平均年齢UPに貢献しております。
先輩方のレベルの高さにドキドキしながらも、何とか縁にしがみついて、シャノンという船に乗っています。ドキドキしたい人は是非ご応募を!
さて本題。
今回は mod_proxy+mod_rewrite+cgiによるメンテナンス画面表示の仕組みをご案内します。この機能は弊社サービスの一部に実装済みです。
■目的
Virtual_host別のメンテナンス画面を表示したい
弊社では多くのサイトをVirtual_hostで運用しております。サイト毎にメンテナンス表示を変更できるようにするのは顧客メリットに寄与するでしょう。
メンテナンス画面を出す方法はググるといろいろ出てきますが、Virtual_host別に出す方法はあまり見ないので書いてみます。
■要件
1,独自メンテナンス画面ファイルの設置、確認を顧客作業だけで完結する。
2,手作業の自動化
独自メンテナンス画面をお客様側で完結する理由は、複数のやりとりでの遅延回避、ミス防止、稼働工数軽減です。
今までは
1,独自メンテナンスファイル更新有無の確認 (営業)
2,更新があればファイル作成を依頼 (営業)
3,メンテナンスファイル作成 (顧客)
4,ファイルを頂いて、インフラに確認依頼 (営業)
5,試験機にファイルを適用
(インフラ)
6,画面をキャプチャーして営業に返す (インフラ)
7,キャプチャー画面で問題ないか確認 (営業、顧客)
8,画面表示に問題があれば3に戻る (顧客)
9,終了
という流れでしたが、今回の仕組みが出来ればこうなります。
1,独自メンテナンスファイル更新有無の確認 (顧客)
2,メンテナンスファイル作成 (顧客)
3,本番機にファイルを適用
(顧客)
4,画面表示に問題があれば3に戻る
(顧客)
5,終了
シンプル!
上記達成のために要件を具体化すると以下になります。
a)バーチャルホスト以下が全てメンテナンス画面になる
b)全てのメンテナンス画面はStatusCode503を返す
c)メンテナンス画面に埋め込まれた静的コンテンツの表示が可能←つまり503にしない
対象:(gif|png|jpg|jpeg|css|js|ico)
d)メンテナンス画面用に、開始年日時曜日、終了年日時曜日を変数で取得できるようにする
e)独自メンテナンス画面がなければ、共通メンテナンス画面を表示
f)通常運用中においても独自メンテナンス画面の確認が出来る。
g)メンテナンス切り替えは、シェルを一つ叩くだけ。戻すときも同様。
StatusCode503にする理由は多くの方がご案内の通り、状況に合わせた正しいステータスコードを設定しないと、タイミングによっては検索エンジンのクローラーにキャッシュされて、メンテナンス終了後も暫くメンテナンスページが表示され続けるという悲しいことになるのを回避するためです。
今回は紙面の都合上、a)~e)の途中まで説明したいと思います。
仕組みにうつります。
当初mod_rewriteだけで実現しようとしましたが、error_document503に投げた後の分岐が出来ないことが分かり、スクリプトで対応しました。
まずは、メンテナンス切り替え時の説明から致します。簡単ですが構成は以下のような感じ。
■構成図
今回の話と関係ない部分は端折っているので、腑に落ちない点もあるかと思いますが、ご容赦ください。
まずフロントサーバの設定から見ていきましょう。
■フロントサーバの設定
通常時からメンテナンスモードへの切り替えは、共通vhost.confの中に、maintenance.confをIncludeしたものに切り替えてgracefulすることでメンテナンス中にします。
弊社では、複数ドメインのvhostの共通部分はIncludeを利用して編集の手間を軽くしています。
1,/etc/httpd/conf.d/Maintenance.conf 内容:メンテナンス切り替え時にIncludeされるconfig
RewriteCond %{HTTP_HOST} !^.*:443$
RewriteRule .* - [E=X_SERVER_NAME:%{HTTP_HOST}]
RewriteCond %{HTTP_HOST} ^(.*):443$
RewriteRule .* - [E=X_SERVER_NAME:%1]
RequestHeader set X_SERVER_NAME %{X_SERVER_NAME}e env=X_SERVER_NAME
RewriteCond %{REQUEST_FILENAME} !^/maintenance/
RewriteRule ^(.*)$ /maintenance/$1
RewriteRule ^/maintenance/(.*) http://backend.shanon.co.jp/maintenance/$1 [P]
ここでやっているのは、単にバックエンドにプロキシするだけですが、一つ重要なことがあります。
フロントにリクエストされたドメインをヘッダで覚えてバックエンドに教えてやることです。
プロキシしてバックエンドに渡るときには、$HTTP_HOSTはフロントサーバに書き換えられているので、バックエンドに伝わりません。バックエンドのディレクトリ構造は、ドメイン名毎にコンテンツが分けられているので階層を指定するためにフロントへのリクエストURLが必要です。
1~6行目までの記述については、下記参照ください。
mod_rewriteでauガラケーのSSL対応しようとしたら見事にハマった件について
また、別の方法として下記を有効にすれば良いのですが、、、、
・ProxyPreserveHost On
このオプションが有効になっている場合、ProxyPass
で指定したホスト名の代わりに、受け付けたリクエストの Host: 行を プロキシ先のホストに送ります。
これを有効にすると、バックエンドにわたるHTTP_PORTが80に強制されてしまったので使いませんでした。原因の調査まで至ってないので、何か分かりましたら、後日お伝えしたいと思います。
別の対応策として、HTTPヘッダの拡張で指定することにしました。それが1行目の記述です。
他は全部バックエンドに投げますという意味です。
■バックエンドサーバの設定
バックエンドはメンテナンス表示の為だけにApacheが動いています。なので、追加Configはこのファイルだけです。
2,
/etc/httpd/conf.d/maintenanceB.conf
ServerAdmin hogehoge@shanon.co.jp
ServerName shanon-test.jp
DocumentRoot /var/www/html/
RewriteEngine On
RewriteLogLevel 3
RewriteLog /var/log/httpd/mod_rewrite.log
CustomLog logs/env_log "%t %h %{X_SERVER_NAME}e %{X_SERVER_NAME}i"
ErrorDocument 503 /cgi-bin/maintenance/script/maintenance.cgi
RewriteCond %{REQUEST_FILENAME} !^/cgi-bin/maintenance/script/maintenance.cgi
RewriteCond %{REQUEST_FILENAME} ^/maintenance/(.*)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !\.(cgi|css|gif|jpg|png|ico)$
RewriteCond %{REQUEST_URI} !=/maintenance.html
RewriteRule ^/(.*)$ "" [L,R=503]
10行目でErrorDocument 503は、独自・共通かを判断するCGIに飛ばす設定です。
12行目~18行目では、通常時のメンテナンス確認がうまく通るようにするためと、メンテナンス画面表示に必要なファイルが503にRewriteされないようにした上、その他のリクエストは503にRewriteする設定です。
次に10行目で503を飛ばしている3つめのスクリプトの説明をします。
3,/cgi-bin/maintenance/script/maintenance.cgi
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, sys
from datetime import date
from ConfigParser import SafeConfigParser
def main():
config = "../maintenance.conf"
MAINTENANCE = {}
MAINTENANCE = load_config(config,"MAINTENANCE")
datafile = ""
if os.environ.has_key('HTTP_HOST'):
datafile = MAINTENANCE['user_maintenance_path'].replace('__FQDN__',os.environ['HTTP_HOST'])
if os.environ.has_key('HTTP_X_SERVER_NAME'):
datafile = MAINTENANCE['user_maintenance_path'].replace('__FQDN__',os.environ['HTTP_X_SERVER_NAME'])
if not os.path.exists(datafile):
datafile = MAINTENANCE['default_maintenance_path']
htmldata = open(datafile).read()
enc = guess_charset(htmldata)
htmldata = replace_html(htmldata,enc,config)
print "Content-type: text/html;charset=%s\n\n" % (enc)
print "%s" % (htmldata)
def replace_html(_htmldata,_enc,_config):
MAINTENANCE_DATE = load_config(_config,"MAINTENANCE_DATE")
for formdata in MAINTENANCE_DATE:
form = '$FORM{%s}' % (formdata)
_htmldata = _htmldata.replace(form , MAINTENANCE_DATE[formdata])
weekdaymap = [u'月', u'火', u'水', u'木', u'金', u'土', u'日']
weekday = weekdaymap[date(int(MAINTENANCE_DATE['start_year']) , int(MAINTENANCE_DATE['start_month']) , int(MAINTENANCE_DATE['start_day'])).weekday()]
_htmldata = _htmldata.replace('$FORM{weekday}' , weekday.encode(_enc))
return _htmldata
def load_config(_config,confsection):
parser = SafeConfigParser()
configfile = os.path.join(os.path.dirname(__file__), _config)
try:
f_in = open(configfile, "r")
parser.readfp(f_in)
confdata = {}
for name, value in parser.items(confsection):
confdata[name] = '%s' % (value)
f_in.close()
return confdata
except IOError:
print "config load Error"
sys.exit(1)
def guess_charset(data):
f = lambda d, enc: d.decode(enc) and enc
try: return f(data, 'utf-8')
except: pass
try: return f(data, 'shift-jis')
except: pass
try: return f(data, 'euc-jp')
except: pass
try: return f(data, 'iso2022-jp')
except: pass
return 'utf-8'
if __name__ == '__main__':
main()
このCGIスクリプトは、
ファイルアップロードされたHTMLの文字コードを判別して、
content-typeのcharsetを変更しています。
また、下記設定ファイルで変数を変更できます。
3-a)
/cgi-bin/maintenance/maintenance.conf
[MAINTENANCE_DATE]
start_year = 2012
start_month = 6
start_day = 30
start_hour = 00
start_min = 00
start_ampm = AM
end_year = 2012
end_month = 6
end_day = 30
end_hour = 09
end_min = 00
end_ampm = AM
[MAINTENANCE]
default_maintenance_path = ./maintenance.html
user_maintenance_path = /var/www/html/upload/__FQDN__/system/maintenance/maintenance.html
1~13行目で、メンテナンス時刻の設定をします。この変数を共通メンテナンスページ、独自メンテナンスページに渡すことが出来ます。
16行目は共通メンテナンスページへのパスを指定します。
17行目は独自メンテナンスがあると見做すファイルの存在を指定します。今回の場合、./maintenance.htmlが無ければ、共通メンテナンスページに遷移します。
以上、ユーザ独自メンテナンス画面のご紹介でした。
これとほぼ同じ仕組みでSystemBusy画面も共通と独自を分けて表示できるようにする予定です。
1 コメント:
Write コメント言い忘れましたが、スクリプトは、fujya.shさんに泣きついて作ってもらいました。スクラッチで2時間かからないぐらい、、流石!
Reply