GrowthForecastでサーバルームの室温監視

研究室のサーバールームの空調が弱くて,40コアくらいある計算サーバにフルで仕事させると室温が上がって,全サーバが落ちるみたいな事例が去年あったらしいので,室温のグラフ化とアラートメール送信をやれみたいなタスクが降ってきた.

そこで,サーバルームのうちのサーバ1台にUSB温度計を差して,定期的に温度を取得し,GrowthForecastに投げればいいと考えた. アラートメールのほうは温度を取得したのちに閾値を超えていたらsendmailでメール投げる感じ。

USB温度計として下記を使用する.Linuxから温度データを取得できることが重要.

Amazon.co.jp: サンコー USB温度計 AKIBA58: 家電・カメラ

必要なもの

  • Ubuntu 12.04
  • perl (GrowthForacastがPerlで書かれているので必要)
  • GrowthForecast (グラフ表示用のWebアプリケーション)
  • Temper (上記温度をGrowthForecastに登録してくれるスクリプト.アラートメール機能もある)

必要なパッケージとモジュールのインストール

$ sudo apt-get install build-essential libusb-0.1-4 libusb-dev build-dep sendmail git
$ curl -L http://cpanmin.us/ | sudo perl - App::cpanminus
$ sudo cpanm -n GrowthForecast HTTP::Tiny Email::Simple Email::Sender

GrowthFocastのインストール

以前の 研究室のサーバに流行りのGrowthForecastを導入してみた - ゆううきブログ を参照. perlbrew使ってたけど,perl-buildにした.

Temper

Temperをインストールする.TemperはUSBドライバAPIを叩いて上記USB温度計から温度情報を摂氏で取得して標準出力に出力してくれるもので,たぶん有志の人がつくってくれたやつ.

bitplane/temper · GitHub

今回はこれをforkして,出力される時間をJSTに直して,さらにGrorwthForecastのAPIを叩く処理とメール投げる処理を書いたスクリプトを入れておいた.

リポジトリの中身

  • temperコマンド: 温度情報を摂氏で取得して標準出力
  • temper_gfcast.pl: temperコマンドを実行して,GrowthForecastのAPIを叩いて温度を登録し,閾値を超えていたらアラートメールを投げる
  • temper_gfcast.conf: 閾値などの設定を行う
  • install.sh
$ git clone https://github.com/y-uuki/temper
$ cd temper
$ sudo ./install.sh

これで,temperコマンドとtemper_gfcast.plが/usr/local/binに入り,/etc/temper_gfcast/temper_gfcast.confが作成される.

まず, /etc/temper_gfcast/temper_gfcast.confを書き換える.

# Examle
#
# sender: root@example.com
# receiver: server-group@example.com
#
# graph_url: http://example.com/list/server-room/watch
#
# warning_threshold: 35
# critical_threshold: 45
#

sender: server-room@laboraoty
receiver: y_uuki@laboratory

graph_url: http://growthforecast.laboratory/list/server-room/temper

warning_threshold: 35
critical_threshold: 45

senderはメールの送信者,receiverはメールの受信者,graph_urlはGrowthForecast APIのendpointで, warnings_thresholdとcriticalthresholdにはメールアラートのための閾値を設定する.上記の場合,温度が35度以上ならばwarningメールを,45度以上ならばcriticalメールを送信する.

最後に,cronで一定時間ごとにtemper_gfcast.plを実行させておけばよい,

$ sudo crontab -e

cronに下記の一行を登録する. 5分ごとに温度監視することにした.

*/5 * * * * /opt/perl-5.14/bin/perl /usr/local/bin/temper_gfcast.pl >> /var/log/temper.log 2>&1

以上で,おわり.

temper_gfcast.pl

GrowthForecastのAPIはHTTP::Tinyで叩いて,アラートメールは Email::Simpleでメール内容を作成して,Email::Senderを介してsendmailで投げる感じ.

#!/usr/bin/env perl
use utf8;
use strict;
use warnings;

use Try::Tiny;
use HTTP::Tiny;
use Email::Sender::Simple 'sendmail';
use Email::Simple;

use constant {
    TEMPER_PATH => '/usr/local/bin/temper',
    CONF_PATH   => '/etc/temper_gfcast/temper_gfcast.conf',
    DEFAULT_WARN_THRESHOLD     => 35,
    DEFAULT_CRITICAL_THRESHOLD => 45,
};


main();
exit;

sub main {
    my $temp_value = current_temperature();

    # putput log
    my ($sec, $min, $hour, $day, $month, $year) = localtime(time);
    print sprintf("temperature:%d\ttime:%04d%02d%02d-%02d:%02d:%02d\n",
        $temp_value, $year + 1900, $month + 1, $day, $hour, $min, $sec);

    send_to_gfcast($temp_value);

    my $params = parse_conf(CONF_PATH);
    my $mail_from = $params->{sender} or die "Not found sender in conf file";
    my $mail_to   = $params->{receiver} or die "Not found receiver in conf file";
    my $graph_url = $params->{graph_url} or die "Not found graph_url in conf file";
    my $warn_threshold     = $params->{warning_threshold} || DEFAULT_WARN_THRESHOLD;
    my $critical_threshold = $params->{critical_threshold} || DEFAULT_CRITICAL_THRESHOLD;

    for ($warn_threshold, $critical_threshold) {
        die "threshold is invalid. $_ is not integer" if $_ !~ /\d+/;
    }

    return if $temp_value < $warn_threshold;
    # Warning or Critical process

    my $status = $critical_threshold <= $temp_value ? 'critical' : 'warning';

    my $email = create_alertmail($mail_from, $mail_to, $graph_url, $status, $temp_value);
    try {
        sendmail($email);
    } catch {
        die "Failed sending mail $_";
    };
}


sub current_temperature {
    my $output = readpipe(TEMPER_PATH);
    chomp $output;
    unless ($output =~ /,([\d|.]+)/) {
        die "Not Found temperature";
    }
    return int($1);
}


sub parse_conf {
    my $conf_path = shift;

    unless (-f $conf_path) {
        die "file:$conf_path is not found";
    }

    open(my $conf_fh, "<", $conf_path)
        or die "failed to open file:$conf_path";

    my $params = {};
    while (my $line = <$conf_fh>) {
       chomp $line;
       next if $line =~ /^\s*$/; # empty line
       next if $line =~ /^\s*#/; # for commentout

       my ($name, $value) = split(/:\s/, $line);
       $value =~ s/(.+?)#?/$1/;
       $params->{$name} = $value;
    }

    return $params;
}


sub send_to_gfcast {
    my $temp_value = shift;

    my $http = HTTP::Tiny->new;
    my $response = $http->post_form('http://localhost/api/server-room/watch/temperature', {
        number => int($temp_value),
        mode   => 'gauge',
        color  => '#333399'
    });

    if ($response->{status} =~ /^(4\d\d|5\d\d)/) {
       die "$response->{status}: $response->{reason}\n" . $response->{content};
    }
}


sub create_alertmail {
    my ($from, $to, $graph_url, $status, $temp_value) = @_;

    my $body = <<"EOS";
Server Room Temperature

Status: $status
Temperature: '$temp_value' degrees

Graph: $graph_url
EOS

    my $email = Email::Simple->create(
        header => [
            From    => "\"Temperature Alert\" <$from>",
            To      => "<$to>",
            Subject => "Server room tempature $status",
        ],
        body => $body,
        attributes => {
            content_type => 'text/html',
        },
    );
    return $email;
}

参考