停電時の自動シャットダウン の履歴(No.2)
更新目的†
USP の必要性†
WindowsXP で動いているファイルサーバーマシンがあり、 その筐体内にRAIDを組んだHDDが複数台積まれています。
RAIDはHDDの物理的な故障からデータを守るために組んでいる 物で、ある一台が急に動かなくなると言うような故障が生じても データが失われる事はないのですが、一方で論理的な故障には めっぽう弱いという特徴があります。
例えば停電やブレーカーが落ちるなどの原因で、データの書き込み 途中にPCの電源が落ちると、その他の部分も含めて、HDD内の データがまるごと失われてしまうような危険性があります。
そこで、無停電電源装置(UPS)を購入してデータを守る事に しました。
安全な電源断†
UPSは中にバッテリーを持っています。
通電時にこのバッテリーを充電しておき、停電時にはバッテリーから PCへ電力を供給することで、いきなりPCの電源が落ちるという 不幸を避けることができます。
このUPSのバッテリーが空になるまでの時間を使って PCを安全にシャットダウンしたいというのが今回の目的です。
状況†
- ユタカ電機製作所のUPS:UPSmini500 YEUP-051MA
- 最大電力 300W で 5 分間の動作が可能
- 90W なら1時間くらいまでOK
- RS-232c ポートを利用して停電/通電および低バッテリーを通知する機能を持つ
- Linux マシンとWindowsXP マシンが一台ずつ繋がっている
- 基本的にどちらもサーバー用途で常時通電
- Linux マシン:Atom を使った省電力サーバー 〜 60W (シリアルポートを持つ)
- WindowsXP マシン:ファイルサーバー 〜 110W (シリアルポートを持たない)
- LAN用のハブもUPSに繋がっている 〜 10W ?
+--------+ | UPS | - - + +--------+ : 似非 RS-232c ケーブル | 電 | : | 源 | : | ケ | +---------+ | ー +-----| Linux | | ブ +---------+ | ル : LAN | : +----------[hub] | : | +---------+ +----------| WinXP | +---------+
期待する動作†
Linux マシンでUPS状態を常時監視
停電したらすぐに
- WinXP マシンをシャットダウン
さらに、バッテリー残量が10%を切ったら
- Linux マシンもシャットダウン
電源が復帰したら
- WinXP・Linux とも電源ON
UPS と Linux マシンの間の接続について†
UPS に付属のケーブルと使い、UPS の制御端子とLinuxマシンのシリアルポートを繋ぎました。
UPS の制御端子は、通常の RS-232c シリアルポートと同じ形をしているのですが、 信号の形式は全く違っていて、実際にはパラレルのDIOポートとして動作しています。
制御ソフトの説明ページ:http://www.yutakadenki.jp/support/downloadfile/linux/f_linux.htm
UPS装置のマニュアル:http://www.yutakadenki.jp/support/downloadfile/miniII_manu.pdf
PCのシリアルポートは、本来名前の通りシリアル通信用です。 しかし9ピンあるうちのいくつかは、本来とは異なる使い方ですが、 DIOポート(単純に電圧が0Vか5Vかを読み取る、あるいは0Vや5Vを出力する) として利用する事ができます。
制御ソフトの説明ページでは、PC側の
- 8ピンに停電信号(UPS→PC:停電を検出すると5Vになる)
- 1ピンにバッテリ残量低下信号(UPS→PC:バッテリ残量が少なくなると5Vになる)
- 4ピンにUPS停止制御信号(PC→UPS:5Vを出力するとUPSがシャットダウンする)
- 7ピンにCOM
- 5ピンにGND
を繋ぐように指示されています。
ところが、UPSに付属のケーブルを調べたところ、このうち4,7,5ピンは 接続されておらず、3ピンがGNDに接続されていました。
- 8ピンに停電信号(UPS→PC:停電を検出すると5Vになる)
- 1ピンにバッテリ残量低下信号(UPS→PC:バッテリ残量が少なくなると5Vになる)
- 3ピンにGND
ということで、現状では停電およびバッテリ残量低下は検出できますが、 PCとUPSとが接続されているかどうかを検出する事はできず、 また、UPSをシャットダウンする事もできません。
なぜこういう仕様のケーブルが付属していたのか、大いに疑問です。
Linux マシンでUPS状態を常時監視する†
ユタカ電機でソフトウェアを配布している ので、これをちょっとだけ変更して使います。
変更点をまとめた差分:usvd-2.0.1-okng-a.diff
変更の理由は以下で解説します。
以下はその手順:
LANG:console(linenumber) $ mkdir yec $ cd yec $ wget http://www.yutakadenki.jp/support/downloadfile/linux/yec.tgz $ tar fxz yec.tgz $ ls usvd-2.0.1 yec.tgz $ cd usvd-2.0.1/ $ wget -O usvd-2.0.1-okng-a.diff "http://dora.bk.tsukuba.ac.jp/~takeuchi/index.php?plugin=attach&refer=%B4%C9%CD%FD%BC%D4%2F%C4%E4%C5%C5%BB%FE%A4%CE%BC%AB%C6%B0%A5%B7%A5%E3%A5%C3%A5%C8%A5%C0%A5%A6%A5%F3&openfile=usvd-2.0.1-okng-a.diff" $ ls COPYING README etc usv_off usvd-2.0.1-okng.diff usvd.lsm Makefile doc readme.ecn usv_off.c usvd.c $ patch -p1 usvd.c usvd-2.0.1-okng-a.diff missing header for context diff at line 3 of patch (Stripping trailing CRs from patch.) patching file usvd.c $ which g++ /usr/bin/g++ $ make gcc -Wall -c -o usvd.o usvd.c gcc usvd.o -o usvd gcc -Wall -c -o usv_off.o usv_off.c gcc usv_off.o -o usv_off $ ls COPYING README etc usv_off usv_off.o usvd-2.0.1-okng-a.diff usvd.lsm Makefile doc readme.ecn usv_off.c usvd usvd.c usvd.o $ chmod 700 usvd usv_off # chown root:root usvd usv_off # mv usvd usv_off /usr/local/sbin/ $ cd etc $ jed lineok 中身は次の1行のみ #!/bin/sh $ jed lineng 中身は次の1行のみ #!/bin/sh $ jed daemon_start 中身は次の2行 #!/bin/sh ln -s /dev/ttyS0 /dev/usv $ jed daemon_stop 中身は次の2行 #!/bin/sh rm /dev/usv $ chmod 700 battlow lineback linefail lineng lineok noconnect # chown root:root * # mkdir /etc/usvd # cp * /etc/usvd # jed /etc/init.d/usvd 下記 usvd デーモンの起動スクリプトに記載の内容を書く # chmod 700 /etc/init.d/usvd # jed /etc/rc.local 次の1行を追加 /etc/init.d/usvd start # /etc/init.d/usvd start
Starting ups watch dog: usdv/usr/local/sbin/usvd: USV daemon version 2.0.1 starting...
failed!
ちなみに、Linux のディストリビューションは Debian (lenny) です。
- 6 行目までで、ソースコードをダウンロード&解凍
- 14 行目までで、変更を組み込み(必要なければ飛ばして良い)
- 24 行目までで、コンパイル
- 27 行目までで、実行ファイルを /usr/local/sbin にインストール
- 46 行目までで、設定ファイルを /etc/usvd にインストール
- 49 行目までで、usvd デーモンの起動ファイルを作成
- 52 行目までで、usvd デーモンが自動実行されるように設定
- 55 行目までで、usvd デーモンを起動
デーモン起動時に faild が出るのですが、動作はしています。 usvd は子プロセスをバックグラウンドに作って、 自分自身はすぐに終了してしまうので、 起動スクリプトが判断を誤っている???のかもしれません。
ここまでをそのまま行うと、
- 停電後5分経ったらLinuxマシンをシャットダウン
- その間にバッテリー残量が10%を切ったらLinuxマシンを即時シャットダウン
- 5分経つ前に停電が復帰したら通常動作に戻る
の動作が行われます。
この動作は目的の物と異なるので、以下で目的の動作に合わせていく事になります。
usvd デーモンの起動スクリプト†
LANG:console(linenumber) #!/bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/local/sbin/usvd test -x $DAEMON || exit 0 . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting ups watch dog" "usdv" /etc/usvd/daemon_start start-stop-daemon --start --quiet --oknodo --exec "$DAEMON" -- $OPTIONS log_end_msg $? ;; stop) log_daemon_msg "Stopping ups watch dog" "usdv" start-stop-daemon --stop --quiet --oknodo --retry 2 --exec "$DAEMON" /etc/usvd/daemon_stop log_end_msg $? ;; force-reload|restart) $0 stop $0 start ;; *) echo "Usage: /etc/init.d/usdv {start|stop|restart|force-reload}" exit 1 ;; esac exit 0
9行目で usvd の起動時スクリプトを走らせている点に注意が必要です。
本来なら usvd から起動すべきかもしれません。
usvd の動作について†
usvd は 1)停電/通電 2) バッテリー残量低下 を10秒おきに監視して、
- 通電状態から停電状態になったら /etc/usvd/linefail
- 停電状態でバッテリー残量低下を検出したら /etc/usvd/battlow
- 停電状態から通電状態になったら /etc/usvd/lineback
のそれぞれのスクリプトを起動します。
監視の際は、/dev/usv というデバイスに UPS の制御信号が繋がって いることを前提としています。制御信号を com1 に繋いでいるので あれば、
ln -s /dev/ttyS0 /dev/usv
として、デバイスを作成する事ができます。
上の通り、起動スクリプトとソースの変更を行っていれば、この他に
- デーモン起動時に /etc/usvd/daemon_start
- デーモン停止時に /etc/usvd/daemon_stop
- 通電状態の間1分おきに /etc/usvd/lineok
- 停電状態の間1分おきに /etc/usvd/lineng
を起動します。
そこで、これらのファイルに望みの動作が行えるよう コマンドを記述しておくことになります。
期待する動作(再考)†
行いたいのは、
- 停電時、WinXP マシンをシャットダウンする
- さらにその後、バッテリー残量が少なくなったら Linux マシンをシャットダウンする
- 電源が復帰したら両方とも電源をONにする
よく考えなければならないのは3番目で、一度切ってしまった電源を どのように入れるか、というのが問題です。
上記1と2との間、WinXP マシンがOFFになって、 Linux マシンがONの状態で電源が復帰した場合には、 Linux マシンが UPS から電源復帰の信号を受け取って、 WinXP マシンに起動の合図を送る事ができます。
これに対して2の後、どちらのPCもOFFになってしまった後、 電源が復帰した場合に、どうやってPCの電源を入れるかは、 ちょっとややこしいのですが、何とかして Linux マシンの 電源を入れた後は、やはり Linux マシンから WinXP マシンの 電源を入れることになります。
Linux マシンから WinXP の電源のON・OFFをする方法は 後述しますが、電源ONの命令は、WinXP の電源が入っている 間は無視されます。また、電源OFF(シャットダウン)の 命令は、WinXP がビジーだと失敗してしまいます。
したがって、
- 電源 ON の命令を送ったつもりが、WinXP はシャットダウン中だったので、 命令は無視され、しかもその直後に WinXP マシンはOFFになった
- 電源 OFF の命令を送ろうとしたが、WinXP が起動中で命令を 受け取れなかったため、OFF にすることができなかった
などのケースが考えられます。
そこで、通電中は電源 ON の命令を、停電中は電源 OFF の命令を 送り続ける(1分間隔で)事にしました。それぞれ、電源 ON 中に 電源 ON 命令を送っても無視されるだけですし、電源 OFF 中に 電源 OFF 命令を送っても失敗するだけなので、問題はないと 判断しました。
usvd デーモンの動作は次のようになります。
- デーモン起動時 (/etc/usvd/daemon_start)
- usvd 用にデバイス /dev/usv を作成する
- WinXP マシンを起動 (Linux マシンの起動時を想定)
- デーモン停止時 (/etc/usvd/daemon_stop)
- usvd 用にデバイス /dev/usv を消去する
- 通電状態の間1分おきに (/etc/usvd/lineok)
- WinXP マシンを起動
- 停電状態の間1分おきに (/etc/usvd/lineng)
- WinXP マシンをシャットダウン
- 通電状態から停電状態になったら (/etc/usvd/linefail)
- WinXP マシンをシャットダウン
- 停電状態でバッテリー残量低下を検出したら (/etc/usvd/battlow)
- WinXP マシンをシャットダウン
- 自分自身をシャットダウン
- 停電状態から通電状態になったら (/etc/usvd/lineback)
- WinXP マシンを起動
電源復帰後に Linux マシンの電源を入れるには†
バッテリー残量が低下して、Linux マシンもシャットダウンしてしまった後に 電源が復帰した場合、どうやって Linux マシンの電源を入れるかは、 難しい問題です。
BIOS による起動?†
このPCは、電源復帰時に自動起動するようBIOSで設定することができます。
一見これが解になりそうなのですが・・・
この機能は一旦バッテリー残量が完全にゼロになって、 PCに電圧が供給されなくなった後、 電源が復帰した場合に限り有効ということになります。
バッテリーが完全になくなる前に電源が復帰した場合には、 PCからこれを検出する事ができませんので、 電源は切れたままになってしまいます。
一応、バッテリーが完全にゼロになる可能性を考えて、 BIOSのこの機能はONにしておきます。
遠隔地から LAN 経由で起動命令を送る†
後に詳述する Wake on LAN という機能を使うと、 インターネット経由で遠隔地からPCの電源をONに することができます。
サーバーの様子を見て、落ちていたら起動命令を送る、 と言うのでも良いのですが、起動中に起動命令が送られてきても 無視されるだけなので、起動中か停止中かに関わらず、 例えば30分に1回、起動命令を送る事にすれば、 それで構わない事になります。
バッテリー残量が少ない状況では†
バッテリー残量が少ないことを検出したためシャットダウンしたとして、 運悪くこのタイミングで起動命令が送られてきてしまうと、 実際には停電中にも関わらず電源が入ってしまうと言う状況が 生じてしまいます。
しかし実際には停電中はルータが落ちているため、 外部からのパケットがLAN内に転送される事はありません。
したがって、LAN外から起動命令を送っている限り、 こういう問題は生じない事になります。
LAN 経由でPCの電源を投入する†
Wake on LAN と呼ばれる機能を使います。
PCに刺さったLANカードに、その MAC アドレスを含む特殊なパケットを 送りつけてやる事で(ポート番号は任意)、PCの電源を入れる事ができます。
このようなパケットを送信するプログラムはあちこちに落ちているそうですが、 今回は http://www.hakusan.tsg.ne.jp/tjkawa/lib/cap/poff.jsp で 配布されていたプログラムをちょっと変更して使わせていただきました。
ソースファイル:wol.cpp
変更点は、送信先のポートを指定できるようにしたことです。
LANG:console(linenumber) $ mkdir wol $ cd wol $ wget -O wol.cpp "http://dora.bk.tsukuba.ac.jp/~takeuchi/index.php?plugin=attach&refer=%B4%C9%CD%FD%BC%D4%2F%C4%E4%C5%C5%BB%FE%A4%CE%BC%AB%C6%B0%A5%B7%A5%E3%A5%C3%A5%C8%A5%C0%A5%A6%A5%F3&openfile=wol.cpp" $ ls wol.cpp $ g++ -o wol wol.cpp $ ls wol wol.cpp $ ./wol %% wol IP-bcast-addr MACaddr [port] # chown root:root wol # mv wol /usr/local/bin/
wol の後に、送りつける先の IP アドレス、起動したいPCの MAC アドレス、 必要ならば送りつける先のポート番号、を指定します。
電源がOFF状態のPCは、自身のIPアドレスが何であるかを知らないので、 実際には、PCの所属するLAN内をこのパケットが通れば、IPアドレスは 何でも良い事になります。
したがってLAN内から送るのであれば、192.168.255.255 などを送るのが 良いと思います。
ルータの外から送るのであれば、ルータにポート転送の設定をした上で、 ルータのIPアドレスと、転送設定したポート番号を指定してパケットを 送信します。
MAC アドレスは、Windows であればコマンドプロンプトから ipconfig として、 Linux であれば ifconfig として、表示させる事のできる 00:11:22:33:aa:bb のような、数字とアルファベットが2つずつ6組、 コロンで区切られた形の文字列になります。
したがって、Linux マシンから同じ LAN 内 (192.168.0.0) に存在する WinXP マシンを起動するには、WinXP マシンの MAC アドレスを ##:##:##:##:##:## として、
wol 192.168.255.255 ##:##:##:##:##:##
とすればよく、遠隔地から Linux マシンを起動するには、ルータのIPアドレスを 123.123.123.123 とし、ポート番号 999 を Linux マシンに転送する設定にし、 Linux マシンの MAC アドレスを ##:##:##:##:##:## として、
wol 123.123.123.123 ##:##:##:##:##:## 999
などとすれば良い事になります。
遠隔地からのコマンドは30分ごと(毎時00分と30分)に送り続けるため、 常に電源の入っているサーバーに以下のような設定をしました。
LANG:console $ crontab -e # m h dom mon dow command 0,30 * * * * ~/wol 123.123.123.123 ##:##:##:##:##:## 999
Linux マシンから WinXP マシンをシャットダウンする†
これ、やろうとすると結構難しいようです。
net コマンドや rpcclient コマンドを使えばできるというような 記述があちこちにあるのですが、やってみるとうまく行きません。
仕方がないので、WinXP 上で cygwin の ssh サーバーを走らせる ことにしました。
sshd のインストール†
cygwin のインストールについては、このあたり http://musashi.sourceforge.jp/cygwin/cygwin.html が詳しいです。
インストールするパッケージは、デフォルトのままで大丈夫ですが、 他に使い道がないのであれば、 Base の Default のパッケージと、Admin にある cygrunsrv、Network にある openssh だけでも 構わないと思います。
cygwin での sshd のインストールは、http://osksn2.hep.sci.osaka-u.ac.jp/~naga/miscellaneous/winssh/winssha-4.html が詳しいです。
基本的には、
- すべてのユーザーにちゃんとしたパスワードが設定されている事を確かめる
- sshd が動いていると、パスワードが破られたときにPCを乗っ取られます
- cygwin シェルから ssh-host-config -y を実行する(サービスとして登録され、次回からは自動実行されます)
- cygrunsrv -S sshd でサーバーを起動する
で、起動するはずです。
エラーで起動しない場合には、cygrnsrv -Q sshd とすると、エラーメッセージを見る事ができるそうです。
良くある問題として、PC内に複数の cygwin1.dll があると、 PATH の設定によってはエラーが出て起動しない事があるようです。 私の場合、WinAVR を導入していたためにこの問題が生じました。 今は WinAVR をアンインストールして使っています。
サービスが起動したらそのまま cygwin のコンソールから ssh localhost として ログインを試してみて下さい。ログインできればOKです。
次に Linux マシンから同様に ssh <user_id>@<ip_address> として、ログインしてみます。 これでうまく行かない場合には WinXP マシンのファイヤーウォールを疑う事になります。
鍵ファイルの作成†
Linux 側で鍵ファイルを作って WinXP 側に入れておくと、 パスワードを手で入力することなく ssh でログインできるようになります。
自動実行で ssh を走らせるには、この作業が必須になります。
Linux 側で、鍵ファイル(秘密鍵 is_rsa, 公開鍵 is_rsa.pub)を作り、 公開鍵を WinXP サーバーに送ります。
LANG:console $ ssh-keygen -f id_rsa Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): (何も入れずENTER) Enter same passphrase again: (何も入れずENTER) Your identification has been saved in id_rsa. Your public key has been saved in id_rsa.pub. $ ls is_rsa is_rsa.pub $ scp is_rsa.pub winxp_user@winxp_address:is_rsa.pub winxp_user@winxp_address's password: (パスワードを入力)
WinXP 側で、is_rsa.pub ファイルを winxp_user の ~/.ssh/authorized_keys に追加します。
LANG:console $ mkdir ~/.ssh $ cat is_rsa.pub >> ~/.ssh/authorized_keys
これで準備は整いました。
Linux 側から、
LANG:console $ ssh -i id_rsa winxp_user@winxp_address
とすることで、パスワードの入力を行うことなくログインできる事を確認します。
id_rsa はログインパスワードと同じく非常に大切なファイルなので、 取り扱いには十分注意する必要があります。
sshd の設定変更†
必要に応じて sshd を、パスワードでログインできないように設定し直します。
これは、パスワードはトライアンドエラーで破られる危険性があるからです。
上で作ったような鍵ファイルはパスワードに比べて桁違いに複雑なので、 これが破られる可能性はほぼ皆無。安全に使う事ができる事になります。
WinXP 上で /etc/sshd を編集することになります。
詳しくは、例えば http://kinshachi.ddo.jp/blog/comp/archives/000290.html などが とても参考になります。
シャットダウン†
ssh は、
LANG:console $ ssh -i id_rsa winxp_user@winxp_address [WinXP上で実行したいコマンド]
の形で使う事で、ログインするのではなく、直接リモートでコマンドを実行する ことができます。
今の場合、id_rsa は /etc/usvd に置いておくのが良いと思います。
WinXP 上で、コマンドラインからPCをシャットダウンするには、
LANG:console $ shutdown /s /t 0 /f
とすればよいので、Linux 上からシャットダウンするには、
LANG:console $ ssh -i /etc/usvd/id_rsa winxp_user@winxp_address shutdown /s /t 0 /f
とすれば良い事になります。
Linux マシンのシャットダウン†
すぐにシャットダウンするには
LANG:console # shutdown -h now
15分後にシャットダウンするには
LANG:console # shutdown -h 15
??分後にシャットダウン、をキャンセルするには
LANG:console # shutdown -c
とする。
最終的な usvd の設定†
ここまでで、
- WinXP の起動: wol 192.168.255.255 ##:##:##:##:##:##
- WinXP のシャットダウン: ssh -i /etc/usvd/id_rsa winxp_user@winxp_address shutdown /s /t 0 /f
- Linux のシャットダウン: shutdown -h now
とすれば良い事が分かった。
後はこれを /etc/usvd/ の battlow lineback linefail lineng lineok noconnect の適当なところに入れればよい。
/etc/usvd/daemon_start
LANG:Shell(linenumber) #!/bin/sh # デバイスの作成 /bin/ln -s /dev/ttyS0 /dev/usv
/etc/usvd/daemon_stop
LANG:Shell(linenumber) #!/bin/sh # デバイスの削除 /bin/rm /dev/usv
/etc/usvd/linefail
LANG:Shell(linenumber) #!/bin/sh # ログイン中のユーザーへのメッセージ /usr/sbin/wall << EOF UPS: power is failing... please log out as soon as you can EOF # メールで通知 /usr/sbin/sendmail root << EOF Subject: [server-name] power is failing EOF # WinXP のシャットダウン /usr/bin/ssh -i /etc/usvd/id_rsa winxp_user@winxp_address shutdown /s /t 0 /f &
/etc/usvd/battlow
LANG:Shell(linenumber) #!/bin/sh # ログイン中のユーザーへのメッセージ /usr/bin/wall << EOF UPS: battery is low EOF # メールで通知 /usr/sbin/sendmail root << EOF Subject: [server-name] battery is low EOF # WinXP のシャットダウン /usr/bin/ssh -i /etc/usvd/id_rsa winxp_user@winxp_address shutdown /s /t 0 /f # Linux のシャットダウン /sbin/shutdown -h now
/etc/usvd/lineback
LANG:Shell(linenumber) #!/bin/sh # ログイン中のユーザーへのメッセージ /usr/sbin/wall << EOF UPS: power is back... you can continue EOF # メールで通知 /usr/sbin/sendmail root << EOF Subject: [server-name] power is back EOF # WinXP の起動 wol 192.168.255.255 ##:##:##:##:##:## &
/etc/usvd/lineng
LANG:Shell(linenumber) #!/bin/sh # WinXP のシャットダウン /usr/bin/ssh -i /etc/usvd/id_rsa winxp_user@winxp_address shutdown /s /t 0 /f &
/etc/usvd/lineok
LANG:Shell(linenumber) #!/bin/sh # WinXP の起動 wol 192.168.255.255 ##:##:##:##:##:## &