家の機器を死活監視したい

10/13

Try to monitor alive or not by NodeJS

目次

経緯

家のネットワーク機器が増えてきたので、簡単な監視をしたい。

やりたいこと

家のありとあらゆるIPを持つ機械を監視したい。
できればNodeJSで。

構築

てことで、やってみる。

graph TD
    a((start))-->|monitoring| c{is dead?}
    c-->|dead| d{has timeout counter exceeded threshold? }
    d-->|exceeded| g{has message already sent?}
    d-->|not yet| j[counter up]
    j-->a
    c-->|alive| f[counter reset]
    f-->h{has message already sent?}
    g-->|yep| a
    g-->|nop| i[send message]
    h-->|yep| i
    h-->|nop| a
    i-->a

こんな感じで、違反のしきい値をもたせておいて、違反した回数がしきい値を超えればメッセージを送信する。
途中で生きていることを確認できたら、またカウンタをリセットしてまた再開する。
しきい値オーバーのとき正常に戻ったとき、それぞれ1回ずつ通知することとして通知先はLINEにしてみた。
結構シンプルだったのと手軽にできるのがよかったので、本当はGoogleChatとかでも良かったのかもしれない。

$ cd /srv
$ sudo mkdir serverstat && sudo chown ME:ME serverstat  # ME: your name
$ cd serverstat
$ npm init -y
$ sed -i -e 's/"main":.*/"main": "monitor.js"/' package.json
$ npm i fs axios form-data ping --save
$ 

今回はaxiosというHTTPクライアントを使ってLINE Notification APIを叩いている。

//filename: monitor.js
/**
 *
 * simple alive monitoring
 *
 */
const fs = require('fs');
const axios = require('axios');
const formdata = require('form-data');
const ping = require('ping');
const cred = require('./credentials');
const monitor = require('./servers');

/**
 * required variables for credentials
 *   file: credentials.js
 *   imagedir: the directory where required image files exist
 *   token: Token for LINE Notification API
 *   url: Access URI for LINE Notification API
 */

/**
 * for monitored nodes
 *   file: servers.js
 *
 *   servers = [
 *      {
 *         host:'192.168.1.1', //host address [editable]
 *         threshold: 1,       //counter triggered if 'count' exceeded the value [editable]
 *         count: 0,           //icmp req-timed-out counter
 *         status: false,      //current status (true:alive|false:dead)
 *         needtosend: false,  //boolean trigger for sending message
 *         hasdead: false      //whether the host has already dead (true:already dead|false:still alive)
 *      },
 *      {
 *      ...
 *      }, ...
 *   ]
 */
let servers=monitor.servers;
let timer1=monitor.interval; //interval of ping execution
let imageArray=['alive.jpg','dead.jpg'];  //0:alive, 1:dead

let sendMessage=async (msg, imagefile)=>{
    let message='テストメッセージ';
    let imageFile=imageArray[0];
    if(msg) message=msg;
    if(imagefile) imageFile=imagefile;
    let form=new formdata();
    form.append('imageFile', fs.createReadStream(cred.imagedir + '/' + imageFile));
    form.append('message', message);
    let headers = {
        'Authorization': 'Bearer ' + cred.token,
        ...form.getHeaders(),
    }

    try {
        const resp = await axios.post(cred.url, form, {headers});
        console.log(resp.data);
    } catch (error) {
        console.error(error);
    }
}

let serverStat=()=>{
    servers.forEach((server)=>{
        //validation
        ping.sys.probe(server.host, async (isAlive)=>{
            let msg='';
            let imageindex=0;
            //dead or alive
            if(!isAlive){
                //is dead
                server.count++;
                server.status=false;
                if(!server.hasdead&&server.count>=server.threshold){
                    msg='host:[' + server.host + '] is dead as a reason of request timeout'
                    server.needtosend=true;
                    server.hasdead=true;
                    imageindex++;
                }else{
                    msg='';
                }
            }else{
                //is alive
                server.status=true;
                if(server.hasdead&&server.count>=server.threshold){
                    msg='host:[' + server.host + '] is alive';
                    server.needtosend=true;
                    server.hasdead=false;
                    imageindex=imageindex*0;
                }
                server.count=server.count*0;
            }

            //sending message
            console.log(server);
            if(server.needtosend&&msg!==''){
                await sendMessage(msg, imageArray[imageindex]);
                server.needtosend=false;
            }
        });
    });
}

let main=()=>{
    let tid=setInterval(serverStat, timer1);
}

main();

こんな感じにしてみた。
このファイルと同じディレクトリにcredentials.jsを作って以下を参考に更新する。

//filename: credentials.js
exports.token='XXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; //LINE notification API
exports.url='https://notify-api.line.me/api/notify';  //LIVE Notification API URI
exports.imagedir='/tmp';

LINE側の設定は適宜行っていただきたい。

あとは監視する機器の設定

//filename: servers.js
//modify below
let monitored_servers = [
     {
        host:'192.168.1.1', //host address [editable]
        threshold: 1,       //counter triggered if 'count' exceeded the value [editable]
     },
     {
        host:'192.168.1.2',
        threshold: 1,
     },
     {
        host:'8.8.4.4',
        threshold: 1,
    }
];
let timer=5000; //interval for alive monitoring
//modify above

let props={ count: 0, status: false, needtosend: false, hasdead: false };
let servers=[];

monitored_servers.forEach(async (srv, i)=>{
    servers[i]={
            host: srv.host,
            threshold: srv.threshold,
            ...props };
});

exports.servers=servers;
exports.interval=timer;

serversとtimerを適宜変更してほしい。
serversはhostとthresholdのみ変更できる。
timerはsetIntervalにそのまま渡しているのでmsecで設定する。

それではテストしてみる。

$ pwd
/srv/serverstat
$ node .
{ host: '192.168.1.1',
  threshold: 1,
  count: 0,
  status: true,
  needtosend: false,
  hasdead: false }
{ host: '192.168.1.2',
  count: 0,
  threshold: 1,
  status: true,
  needtosend: false,
  hasdead: false }
.
.
.
$ 

このように出ていたら、起動には問題はなさそう。
実際に監視しているノードを再起動なりをしてみて、ちゃんとreq timed outしたときにLINEに通知が来るか試してみる。
で、あとは起動。

$ forever start monitor.js
$ forever logs 3 # 3: id
.
.
.
.
.
data:    monitor.js:12221 - { host: '192.168.1.1',
data:    monitor.js:12221 -   threshold: 1,
data:    monitor.js:12221 -   count: 0,
data:    monitor.js:12221 -   status: true,
data:    monitor.js:12221 -   needtosend: false,
data:    monitor.js:12221 -   hasdead: false }
$ 

大丈夫そうだ。

最後にファイル一覧。

$ pwd
/srv/serverstat
$ ls -F
alive.jpg  credentials.js  dead.jpg  monitor.js  node_modules/  package-lock.json  package.json  servers.js
$ 

User Experience

画面の見た目はこんな感じ。

通知イメージ


コメント: