Engineering Note

プログラミングなどの技術的なメモ

Windows10の通知機能を利用したローカルサーバ監視アプリ

notification-center

今回はWindows10のトースト通知機能を使い、自宅で運用しているローカルサーバ(Raspberry Pi)内のサービスの死活監視を行うアプリを作成します。

内容としては、ローカルサーバ内のシェルスクリプトでサービスのステータス確認をし、異常があればloggerコマンドでリモート先(ローカルPC)にコネクションレス型のUDPで送信し、それをローカルPCで起動しているUDPサーバ(Pythonプログラム)を通してトースト通知します。

 

監視用シェルスクリプトの作成

まずローカルサーバ側で監視用シェルスクリプトを作成します。

監視対象のサービスは、ローカルで運用しているDNSキャッシュサーバにしてみます。

以下がスクリプトになります。

 

#!/bin/sh

status=`ps -ef | grep /usr/sbin/named | grep -v grep | wc -l`

if [ $status -eq 0 ]; then
  logger -p local0.warn -t DNS "DNS is not working!!"
fi

 

上記スクリプトを適当な場所(今回はホームディレクトリにscriptsフォルダを作成し、そこに"watch_server.sh"として格納)に保存します。

psコマンドにオプション"ef"を付与して、実行中のすべてのプロセスを完全フォーマットで表示した後に、grepコマンドでプログラムのパスである"/usr/sbin/named"を検索します。

grepコマンドの"v"オプションは、後に続く文字列を除外するためで、今実行したばかりのgrepコマンドを取り除いています。

最後にwcコマンドで行をカウントし、status変数に格納します。

status変数の値が"1"ならば、DNSは問題なく動作していますが、"0"の場合は何らかの原因によりサービスが停止しています。

この場合、loggerコマンドで任意のログメッセージを出力します。

local0はファシリティと呼ばれるもので、以下のrsyslogの設定で説明します。

ここではログレベルを"warn"にし,"t"オプションで"DNS"というキーワードをタグ付けします。

また、以下でシェルスクリプトに実行権限を与えます。

 

 > chmod u+x watch_server.sh

 

rsyslogの設定

上記シェルスクリプトで実行されるloggerコマンドでのlocalファシリティを追加します。

rsyslogではlocal0~local7まで、ログの出力元を自由に設定できます。

"/etc/rsyslog.d"ディレクトリに以下のlocalファシリティ用の設定ファイル(local0.conf)を作成します。

 

local0.*        @192.168.1.1:8888

 

上記ではワイルドカードを用いて、すべてのログレベルを指定しています。

続いて、「IPアドレス:ポート番号」を記述し、IPアドレスの先頭部分の"@"はUDPを指定するという意味で、TCPの場合は"@@"と記述します。

上記ファイルをrsyslogが読み込めるように、"/etc/rsyslog.conf"内の以下コメントを外します。

 

...
$IncludeConfig /etc/rsyslog.d/*.conf
...

 

設定が完了したらrsyslogを再起動します。

 

 > service rsyslog restart

 

cronの設定

上記で作成したシェルスクリプトをcronで自動実行させます。

今回は、5秒間隔でDNSキャッシュサーバのステータスを確認したいと思います。

cronは決まった時間間隔でスクリプトを実行することができますが、デフォルトでは秒単位での間隔指定ができないので、以下のように記述し、秒単位でも実行できるようにします。

記述後にcronをリスタートし、設定を読み込ませます。

 

 > crontab -e
 ...
 * * * * * for i in `seq 0 5 59`;do (sleep ${i}; ~/scripts/watch_server.sh) & done;
 ...
 > service cron restart

 

UDPサーバ(Pythonプログラム)の作成

それでは、ローカルPC内で起動するUDPサーバをPythonでプログラミングしていきます。

Windows10のトースト通知の実装はwin10toastモジュールを使用します。

基本的使い方を知りたい方は以下をご覧ください。

engineeringnote.hateblo.jp

以下が作成したコードになります。

 

from win10toast import ToastNotifier
from threading import Thread
from time import sleep
import socket, re

status = {}

def handle_client(data):
    serv, msg = re.findall(r'([a-zA-Z]+): (.*$)', data.decode('utf-8'))[0]
    if serv in status.keys():
        if status[serv]:
            return
    else:
        status[serv] = True
    toaster = ToastNotifier()
    toaster.show_toast(serv, msg, duration=5)
    sleep(15)
    status[serv] = False

if __name__ == '__main__':
    ip = "192.168.1.1"
    port = 8888

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((ip, port))
    while True:
        data, client  = sock.recvfrom(1024)
        t = Thread(target=handle_client, args=(data,))
        t.start()

 

9行目では、loggerコマンドでタグ付けされたサービス名とメッセージをそれぞれ、正規表現を使い格納しています。

また、15~18行目のトースト通知部分の実装では、ウィンドウの表示時間を5秒に設定し、あまりひっきりなしに通知のポップアップが出ても迷惑なので、追加で15秒の待機時間を置き、さらに通知の状態を持たせ、別スレッドから来た同サービスの通知を抑制しています。

このプログラムはデスクトップにscriptフォルダを作成し、こちらに保存しておきます。

 

バッチファイルの作成

上記で作成したUDPサーバ用のPythonプログラムを、ローカルPCの起動時に立ち上げるバッチファイルを作成します。 

 

python %USERPROFILE%\Desktop\script\watch_server.py

 

なお、仮想環境からPythonを実行する場合は、上記バッチファイルの先頭に「call "<仮想環境のファルダ名>\Scripts\activate.bat"」を追加してください。

上記バッチファイルを実行すれば作成したPythonプログラムが実行されますが、実行中はコマンドプロンプトが立ち上がってしまいます。

コマンドプロンプトを非表示にするには、VBScriptを作成し、こちらからバッチファイルを実行する必要があります。

以下が作成したVBScriptになります。

 

Set objWShell = CreateObject("Wscript.Shell")
objWShell.run "cmd /c %USERPROFILE%\Desktop\script\watch_server.bat", vbHide

 

最後に作成したVBScriptをスタートアップフォルダに保存します。

スタートアップフォルダのパスは以下になります。

 C:\Users\ [ユーザー名]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

なお、作成したバッチファイルは、Pythonプログラムと同じフォルダ(デスクトップのscriptフォルダ)に保存します。

 

動作確認

それでは、準備が整ったところでローカルPCを再起動します。

試しにDNSキャッシュサーバを停止すると、無事に以下のトースト通知がされました。

 

toast_message

 

最後に

今回は、簡単なサービスのステータス確認を行いましたが、サーバのCPUやディスク使用率などアイデア次第で様々な応用が利くと思います。

 

参考書籍

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界