サーバー停止時のお知らせ画面を自動表示

はじめに

サーバー停止時にお知らせを表示させる仕組みを構築した。
YAMAHAルーターのLUA機能を利用して定期的なping実行でホストの稼働監視をさせて,一定期間応答が無い場合にお知らせ画面に誘導する。ping応答が回復したら通常画面に戻る。
ping監視のひな形はヤマハホームページに載っていたので難なく構築できた。
ホストを監視する (yamaha.com)

当初はルーターのカスタムGUI機能を利用して外部からルーターのお知らせ画面にアクセスする方法を検討したが,ベーシック認証のポップアップ画面を回避できず,またルーターの管理画面へアクセスされる懸念もあり断念して,LAN内の別サーバーにお知らせ画面を置くこととした。

当初案

ルーターにお知らせ画面を配置する際の検討結果
定期的にPING監視して,一定回数応答がなかったらカスタムGUIの無名ユーザ画面にアクセスさせるためrt.commandでWEBポートのIPマスカレードCONFIG設定を自ルーターのIPアドレスに変更する。カスタムGUIの無名ユーザ画面は「サーバー停止のお知らせ」を表示する。
PING応答が復活したらIPマスカレードCONFIG設定を戻す。

ルーターにお知らせ画面を置く際の問題点
外部から80ポートで直接アクセスできない仕様となっており,別ポートを経由してアクセスすることになる。
ルーターのhttp待ち受けポートを標準の80から変更する(例 80>>>8888)
rt.command(“httpd listen 8888”) にして外部80をnat で8888 に変更
rt.command(“nat descriptor masquerade static 1 2 192.168.0.1 tcp www=8888”)

無名ユーザでパスワード指定なしの場合はベーシック認証画面は空白でエンターすればお知らせ画面が表示される。無名では設定guiに入れない。
ただしベーシック認証画面で正しいユーザーとパスワードを入力すれば管理画面に入れるのでセキュリティー上好ましくない。ルーターにお知らせ画面を置くのは難しい。

無名ユーザの場合はconfig設定でユーザ名を省略して,「directory=」のフォルダーにのみアクセス可能
    # httpd custom-gui user directory=/gui/anonymous

代替案

ルーターにお知らせ画面を置くのをあきらめて,LAN内の未使用サーバーに置くこととする。前世代で使用していたサーバーTX100S3を活用してフォルダー構造が同じなので,ドキュメントルート直下のメンテナンスフォルダーにhtmlで作成した画面を置く。(index.html)
代替サーバーのデフォルトゲートウェイは本番サーバーと同じに設定。フォルダー構造も本番サーバーと同じに設定する。
【参考】フォルダー構造のみコピーするコマンド
dドライブのフォルダー「www」以下のサブフォルダーをfドライブにコピー

xcopy /e /i /t d:\www f:\www

本番サーバー停止時に代替サーバーをWOL起動させて,ルーターのnat設定を代替サーバーに変更する。本番サーバー停止から代替サーバーに切り替わる際に数分のタイムラグがあり,その際にエラー画面が表示されるが自動切換えなので仕方ない。事前に代替サーバーを起動しておいて手動でconfig設定すればエラー画面は表示されない。
本番サーバー復旧時は逆の動作で切り替わる。代替サーバーは稼働したままとなるが,本番サーバーのスタートアップでbatファイルから代替サーバー停止のコマンド「PsShutdown」を投入することで停止できる。
【参考】パソコンの起動・停止監視ツール – na-blog (na-3.com)

リダイレクト設定

代替サーバーのドキュメントルートに.htaccessファイルを置いてルートディレクトリ以下をお知らせ画面ページへリダイレクトさせる。
配下のディレクトリに.htaccessファイルがあるとそちらが優先されるので,リネーム等で無効にしておく。

# メンテナンス画面へリダイレクト  (ドキュメントルートに配置)
ErrorDocument 503 /maintenance/index.html

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} !=/maintenance/index.html
RewriteRule ^.*$ - [R=503,L]
</IfModule>

## メンテ終了予定時刻を指定
#<IfModule mod_headers.c>
#    Header set Retry-After "Sat, 27 Apr 2024 6:00:00 GMT"
#</IfModule>

実際の設定
配置したドキュメントルート以下の全てのフォルダー・ファイルを「https://hoge.com/maintenance/index.html」のお知らせ画面ファイルへ一時的にリダイレクトする。しかし長期間停止していたのでssl証明書が期限切れでhttp接続限定となり警告が表示される。お知らせ画面の表示だけなのでこれでもよいのだが,事前に期限切れ前に定期的に起動してssl証明書を取得するか,またはwol起動時にしばらく放置で取得するかどちらかが必要。

お知らせ画面例

代替サーバーのドキュメントルート直下のリダイレクト先フオルダー(maintenance)に置く。
(例:D:\www\public_html\maintenance\ 又は D:\www\a-blog\maintenance\)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<title>サーバーメンテナンスのお知らせ</title>

<h2>サーバーメンテナンスのお知らせ</h2>

<p>ただいまサーバーのメンテナンスを実施しています。
<br>
<br>
停止に伴い、下記の通りホームページの閲覧および全サービスを一時休止いたします。
<br>
誠に申し訳ございませんが、ご了承くださいますようお願い申し上げます。
<br>
<br>
■ホームページの閲覧およびサービスの休止
<br>
<br>
 ホームページ   ブログサイト (<a href="https://hoge.com/hoge-blog/">https://hoge.com/hoge-blog/</a>)
<br>
<br>
 その他      hoge.comドメインの全ページ
<br>
<br>
 メールサーバー  @hoge.com
<br>
<br>
※現在,復旧に向けて全力で対応しております。今しばらくお待ちください。_(._.)_
</p>
</head>
</html>

 

ルーターconfigの変更

事前変更・追加箇所

フィルター定義の変更は若干セキュリティーを緩める方向だが,luaでの書き換えを少なくするため実施。
サーバー稼働監視のluaをルーター起動と共に起動させるため,スケジュール設定追加。

#フィルター定義のサーバーipアドレス設定をネットワークに変更例 他にも同様変更あり
ip filter 1031 pass * 192.168.0.100 tcpflag=0x0002/0x0017 * www
ip filter dynamic 201 * 192.168.0.100 www
              ↓
ip filter 1031 pass * 192.168.0.0/24 tcpflag=0x0002/0x0017 * www
ip filter dynamic 201 * 192.168.0.0/24 www


#スケジュール設定
#サーバー停止のお知らせ監視              <<<<<<<<<<<<  追加
schedule at 3 startup * lua /lua/server-check.lua

 

サーバー停止時の自動変更箇所

ルーターconfigの変更はLUAスクリプト(server-check.lua)で自動書き換えする。

本番サーバー停止時
nat descriptor masquerade static 1 2 (代替サーバーipアドレス) tcp www
nat descriptor masquerade static 1 6 (代替サーバーipアドレス) tcp https

本番サーバー復旧時
nat descriptor masquerade static 1 2 (本番サーバーipアドレス) tcp www
nat descriptor masquerade static 1 6 (本番サーバーipアドレス) tcp https

ルアスクリプト

ルーターで常時動作させるルアスクリプト。(server-check.lua)
ヤマハホームページのひな形を利用して,追加変更(ハイライト部)はわずかで完成した。
Windows Updateで再起動する際に更新時間が長い場合は一旦,代替サーバーに切り替わることがある。通常の再起動を何度か行って監視間隔・回数を40秒・5回にした。機種によりその値は調整が必要。

--[[
  ●ping 応答監視スクリプト
  指定したアドレスに宛に ping を実行してその応答を監視し、応答がなかった場合
  に管理者にメールを送信とホームページ閲覧者にサーバー停止のお知らせを表示して
  知らせるスクリプトです。

  指定した回数連続して ping に対する応答がなかった場合には、管理者にメールを
  送信して知らせます。その後、指定した回数連続して応答があった場合には、応答
  が回復したと判断します。設定値 down_mail を true に設定している場合には、応
  答が回復した際にもメールを送信します。

  <説明>
  ・このファイルを RTFS か外部メモリに保存してください。
  ・本項目の config の設定では schedule at コマンドでルーター起動時に Lua スク
    リプトが実行されるように設定しています。
  ・スクリプトを停止するときは terminate lua コマンドを実行してください。
  ・再度、Lua スクリプトを実行する場合は lua コマンドで実行してください。
  ・★マークの付いた設定値は変更が可能です。
 ・☆マークの付いた設定値はヤマハ標準からの変更箇所です。

  <ノート>
 ・メールの送信失敗時に出力する SYSLOG レベルを指定可能です。
  SYSLOG のレベルを指定するには、log_level を設定してください。
  debug レベル、notice レベルの SYSLOG を出力するためには、それぞれ以下の設定
  が必要です。
   debug レベル ・・・ syslog debug on
   notice レベル・・・ syslog notice on
 ・本スクリプトファイルを編集する場合、文字コードは必ず Shift-JIS を使用してく
  ださい。

]]


--------------------------##  設定値  ##--------------------------------

-- 監視間隔(1 - 864000 秒)
idle_time = 40   -- ★ 40秒毎に本番サーバーを監視

-- ping への応答がない、または応答が回復したと判断する連続回数(1, 2 ..)
count = 5        -- ★ 40秒毎に5回監視して200秒(3分20秒)間停止・起動していたら代替・本番サーバーへ切替

-- 応答が回復したときにもメールを送るかどうか(送る: true / 送らない: false)
down_mail = true    -- ★

-- メールの送信に失敗したときに出力する SYSLOG のレベル(info, debug, notice)
log_level = "info"    -- ★

-- ping を実行する宛先 IP アドレス
dst = "192.168.0.100"     -- ★   監視サーバーipアドレス

-- ☆ 停止お知らせ画面の代替えサーバーIP アドレス MACアドレス
alt = "192.168.0.200"               -- ☆ 代替サーバーipアドレス
targetmac3 = "00:11:22:33:44:55"    -- ☆ 代替サーバーmacアドレスlan3 dmz
targetmac1 = "00:22:33:44:55:66"    -- ☆ 代替サーバーmacアドレスlan1

targetip = ""              -- ☆ NATipアドレス

-- メールの設定      -- ☆
mail_tbl = {
     smtp_address        = "hoge.net",        -- 自分のメールサーバー
     smtp_port           = "587",             --  "25" | "587"
     smtp_auth_name      = "rt@hoge.net",     -- 自分のメールサーバーアカウント
     smtp_auth_password  = "password",        -- 自分のメールサーバーパスワード
     pop_before_smtp     = true,       -- true | false
     pop_address         = "hoge.net",         -- 自分のメールサーバー
     pop_protocol        = "pop3",   --
     pop_auth_name       = "rt@hoge.net",      -- POP認証用ユーザー名
     pop_auth_password   = "password",          -- POP認証用パスワード
     from                = "RTX1210@hoge.com",  -- 送信元アドレス
     to                  = "rt@hoge.net"        -- 宛先アドレス
--     subject             = "",              -- 件名 (処理中に代入) 
--     text                = ""               -- 内容 (処理中に代入) 
}

----------------------##  設定値ここまで  ##----------------------------

------------------------------------------------------------
-- ping を実行し、到達したかどうかを返す関数              --
------------------------------------------------------------
function ping_reach(adr)
	local rtn, str, loss
	local reach = false
	local cmd = "ping " .. adr
	local ptn = "(%d+)%.%d+%%"

	rtn, str = rt.command(cmd)
	if (rtn) and (str) then
		loss = str:match(ptn)
		if (loss) then
			loss = tonumber(loss)
			if (loss == 0) then
        			reach = true
      			end
    		end
  	end

	return rtn, reach, str
end

--------------------------------------------------------------
-- 連続何回 ping に応答がないかを示すカウンターの処理関数   --
--------------------------------------------------------------
function count_proc(t, reach, th)
	local rtn = 0

	if (not reach) then
		if (not t.flag) then
			t.ng = t.ng + 1
			if (t.ng == th) then
				rtn = 1
				t.flag = true
			end
		else
			if (t.ok > 0) then
				t.ok = 0
			end
		end
	else
		if (t.flag) then
			t.ok = t.ok + 1
			if (t.ok == th) then
				rtn = -1
				t.flag = false
				t.ng = 0
				t.ok = 0
			end
		else
			if (t.ng > 0) then
				t.ng = 0
			end
		end
	end

	return rtn
end

------------------------------------------------------------
-- メール本文を作成する関数                               --
------------------------------------------------------------
function make_pingmsg(tbl, reach, adr, cnt, sec, down)
	local rtn
	local str = ""

	rtn = count_proc(tbl, reach, cnt)
	if (rtn < 0) then
		if (down) then
			str = "pingの応答が回復しました。\r\n"
			str = str .. string.format("  送信先: %s\r\n  監視間隔: %d(秒)\r\n\r\n",adr, sec)
			targetip = dst       -- ☆ 本番サーバーipアドレスに戻す
		end
	elseif (rtn > 0) then
		str = "pingの応答がありません。\r\n"
		str = str .. string.format("  送信先: %s\r\n  応答がなかった回数: %d回\r\n  監視間隔: %d(秒)\r\n\r\n",adr, cnt, sec)
		targetip = alt          -- ☆ 代替サーバーipアドレスに設定
	end

	return str
end

------------------------------------------------------------
-- 現在の日時を取得する関数                               --
------------------------------------------------------------
function time_stamp()
	local t

	t = os.date("*t")
	return string.format("%d/%02d/%02d %02d:%02d:%02d", t.year, t.month, t.day, t.hour, t.min, t.sec)
end

------------------------------------------------------------
-- 代替サーバーの起動                             -- ☆ 
------------------------------------------------------------
function altserver_start()

rt.command("wol send lan3 " .. targetmac3)
rt.sleep(1)
rt.command("wol send lan1 " .. targetmac1)

end

------------------------------------------------------------
-- サーバーのルート変更config                           -- ☆ 
------------------------------------------------------------
function server_route()

rt.command("nat descriptor masquerade static 1 2 " .. targetip .. " tcp www")
rt.command("nat descriptor masquerade static 1 6 " .. targetip .. " tcp https")

end

------------------------------------------------------------
-- メインルーチン                                         --
------------------------------------------------------------
local rtn, reach, str
local reach_tbl = {ng = 0, ok = 0, flag = false}

while (true) do
	mail_tbl.text = ""

	rtn, reach, str = ping_reach(dst)
	if (rtn) then
		mail_tbl.text = mail_tbl.text .. make_pingmsg(reach_tbl, reach, dst,count, idle_time, down_mail)
	else
		mail_tbl.text = string.format("%s (ping送信先: %s\r\n\r\n)", str, dst)
	end

	if (mail_tbl.text:len() > 0) then
		mail_tbl.subject = string.format("watch ping : %s (%s)", dst, time_stamp())
		rtn = rt.mail(mail_tbl)
		server_route()             -- ☆ ルーターのconfig変更
		if targetip == alt then    -- ☆ 代替サーバー起動
			altserver_start()      -- ☆ 代替サーバー起動
		end                        -- ☆ 代替サーバー起動
		if (not rtn) then
			rt.syslog(log_level, "failed to send mail. (server-check.lua)")
		end
	end

	rt.sleep(idle_time)
end

 

代替サーバー停止

本番サーバー復旧時に代替サーバーを停止するため,本番サーバーのスタートアップに下記batファイルを登録してPsShutdownコマンドを投入する。
代替サーバーの起動有無に関係なく実行する。

rem 5分後にipアドレスで指定したpcを停止  server-stop.bat
timeout /t 300
PsShutdown64 -s \\ipアドレス  [ -u user -p psswd]

Windows 8.1以降・Windows Server 2012 およびそれ以降で対応している。
当環境ではWindows Server 2008R2で動作した。
Windows 7 Proでもポート開放「Netlogonサービス(NP受信)ポート:445」で動作するが,下記のエラーを表示する。その後再起動・停止処理に入る。PHPからのコマンド投入は動作しない。

おわりに

当初案のルーターにお知らせ画面を置く構想は捨てきれていないが,試していない方法があるのかも。今後の課題として取っておくことにする。長期停止の際に代替サーバーに代えてマイクロPC(ベアボーンPC)がジャンク品の中にあったのでそれを利用する方法もありかも。電気代が気になるので今後検討するか。
ちなみにサーバーをメンテナンスモードにする際には,pingに応答しないようにすればよいのでDMZ回線のLAN2ネットワークアダプターを無効にするか又はLAN2ケーブルを抜けばよい。後者のほうが簡単。メンテナンスはLAN1回線又はiRMC回線から行える。