一般的Web網站來說,都包括很多服務和應用,我們沒法即時知道系統運行是否正常,特別是晚上的時候,如果伺服器宕機或應用掛掉了,都會影響業務和
使用者訪問,這時候一套對系統監控的錯設就必須得當。目前有很多軟體應的監控通知和警示服務,有收費的也有免費的,大家都可以選擇。
我們就嘗試自己來實現一個服務監控和警示通知的程式,這樣能夠使用很小的代價,同樣讓我們的服務高可用性和高可靠性。
【監控原理】
遠程服務
對於遠程機器來說,我們可以有一台監控伺服器,或者隨便找一台比較不容宕機的伺服器來作為監控伺服器,那麼就能夠監控其他的服務機上的服務了,
遠程監控是比較大家需要的方式了。一般遠程監控就監控伺服器和連接埠是否開放,比如說,我們的 Web 服務 Apache 一般都會開放 80
連接埠,那麼我們就可以通過訪問這台伺服器的 80 連接埠來確定 Apache 是否在正常工作,如果無法串連上,那麼說明該服務就停止了。
本地服務
對於本機來說,監控進程和記錄檔都是可行的,一般來說,長期頻繁工作的服務,比如 Apache 都會在每次訪問後把訪問資訊記錄到
access
訪問記錄檔裡,如果這個檔案長時間沒有更新,就可以懷疑該服務已經停止了(當然了,不排除,這段時間內都沒有人訪問的情況)。另外對於進程來說,本機是
很容易可以查看到進程情況的,對於 MySQL 等伺服器來說,守護進程都是長期開放的,如果發現當前系統中沒有了 MySQL
守護進程,那麼也可以確認 MySQL 服務已經停止了。
警示通知
服務停止了,自然需要通知系統維護人員,那麼一般就是通過郵件或者簡訊的方式,簡訊是最好的了,但是頻繁簡訊同樣讓維護人員很鬱悶,這個叫做簡訊炸彈
(Message Bomb),所以郵件也許是個簡單實在的方式,本地再配置上 Outlook/Foxmail
定期接收和通知方式,也比較快捷,但是晚上回家後,一般都無法收到郵件了,所以合理的方式是白天郵件通知,晚上和周末簡訊通知 的警示方式更合理。
【代碼實現】
具體代碼實現可以使用各種代碼了,C/C++、Ruby、Python、PHP ,只要能夠訪問檔案、Socket
,能夠定期執行的語言都可以,我們下面的代碼採用 Perl 來構建,因為 Perl 是很好的系統管理指令碼語言,任何 Unix/Linux
都預設安裝了 Perl 引擎,能夠很方便的在任何機器上面運行,同時 Perl 的靈活性強,而且有強大的 CPAN
包庫,所以編寫代碼很方便,在系統管理中也是值得推薦大家使用的,當然了,很多系統管理工作使用 shell 指令碼也許更方便。
下面的代碼實現對遠程監控、本地記錄檔監控、本地進程監控都進行了實現,但是只使用了遠程連接埠監控的方式,因為這樣就能夠監控多台機器和服務
了,如果只是單台機器或者只是想監控本地進程,可以很簡單的修改主函數來實現。同時通知方式主要是採用郵件通知的方式,並且函數實現了SMTP協議進行郵
件發送(因為我發現Perl內建的 Net::SMTP
在進行型驗證的時候,並不是很靠譜),當然了,在警示通知方面,完全可以改寫成傳送簡訊或者按照時間來分別調用簡訊和郵件的方式。
代碼中主要監控了包括 Apache、MySQL、Memcache、Search(假如你有的話)等服務,可以在這個基礎上進行增刪不同的伺服器監控,只需要增加一個常量配置和修改 main 函數代碼。
說明:以下Perl代碼在 RHEL 4 + Perl v5.8.6 環境下測試通過
#!/usr/bin/perl
use IO::Socket;
use IO::File;
use MIME::Base64;
##############################
# Constant define (configure)
##############################
# mail config
use constant MAIL_ADDR => ('to'=>'webmaster@example.com', 'from'=>'webmaster@example.com');
use constant SMTP_INFO => ('host'=>'smtp.example.com', 'user'=>'webmaster', 'password'=>'pass',
'debug'=>1, 'bufsize'=>1024);
# common config
use constant MD5SUM_FILE => '/tmp/__monitor_md5sum_hash';
use constant APACHE_LOG_PATH => '/usr/local/apache2/logs/access';
# apache
use constant APACHE_PORT => 80;
use constant APACHE_SERVERS => ('web1.example.com', 'web2.example.com');
# mysql
use constant MYSQL_PORT => 3306;
use constant MYSQL_SERVERS => ('db1.example.com', 'db2.example.com');
# memcache
use constant MEMCACHE_PORT => 11211;
use constant MEMCACHE_SERVERS => ('cache1.example.com', 'cache2.example.com');
# search
use constant SEARCH_PORT => 8000;
use constant SEARCH_SERVERS => ('search1.example.com');
##############################
# Server port is alive check
##############################
sub check_server_alive {
my($server, $port) = @_;
$sock = IO::Socket::INET->new(PeerAddr=>$server, PeerPort=>$port, Proto=>'tcp', Timeout=>3);
if (!$sock){
return 0;
}
$sock->close();
return 1;
}
##############################
# Check process is exist
##############################
sub check_process_exist {
my $proc_name = shift;
$line = `/bin/ps auxw | /bin/grep $proc_name | /bin/grep -v grep | /usr/bin/wc -l`;
$line =~ s/^s+|s+$//g;
if ($line == 0){
return 0;
}
return 1;
}
##############################
# Check file md5 fingerprint
##############################
sub check_file_md5sum {
my $io, $line;
$filename = shift;
@arr = split(/s/, `/usr/bin/md5sum $filename`);
$filehash = shift(@arr);
$io = IO::File->new();
$io->open(MD5SUM_FILE, O_RDWR);
if (!($line = $io->getLine())){
$io->syswrite($filehash);
$io->close;
return true;
}
if ($line != $filehash){
$io->truncate(0);
$io->syswrite($filehash);
$io->close;
return true;
}
return true;
}
##############################
# SMTP execute command
##############################
sub smtp_cmd {
my ($sock, $cmd, $blocking) = @_;
my %smtpinfo = SMTP_INFO;
my $buf, $bufsize = $smtpinfo{'bufsize'}, $debug=$smtpinfo{'debug'};
$sock->syswrite($cmd);
if ($debug == 1){
print ">>> $cmd ";
}
if ($blocking == 1){
$sock->sysread($buf, $bufsize);
if ($debug){
print "<<< $buf";
}
}
}
##############################
# Send notice mail
##############################
sub send_mail {
my ($subject, $content) = @_;
my $sock;
my %mailaddr = MAIL_ADDR;
my %smtpinfo = SMTP_INFO;
my $debug = $smtpinfo{'debug'};
# Count date time
($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst) = localtime(time());
$datetime = sprintf("%s-%s-%s %s:%s:%s", "20".substr($year,1,2),
length($mon)==1?"0$mon":$mon, length($day)==1?"0$day":$day,
length($hour)==1?"0$hour":$hour, length($min)==1?"0$min":$min,
length($sec)==1?"0$sec":$sec);
$subject .= "[$datetime]";
# Connect to SMTP server
$sock = IO::Socket::INET->new(PeerAddr=>$smtpinfo{'host'}, PeerPort=>25, Proto=>'tcp', Timeout=>10);
$sock->blocking(1);
# Send smtp command
if ($debug == 1){
print "<<< ". $sock->sysread($buf, $smtpinfo{'bufsize'});
}
smtp_cmd($sock, "HELO locahost ", 1);
smtp_cmd($sock, "AUTH LOGIN ", 1);
smtp_cmd($sock, encode_base64($smtpinfo{'user'}), 1);
smtp_cmd($sock, encode_base64($smtpinfo{'password'}), 1);
smtp_cmd($sock, "MAIL FROM: <". $mailaddr{'from'} ."> ", 1);
smtp_cmd($sock, "RCPT TO: <". $mailaddr{'to'} ."> ", 1);
smtp_cmd($sock, "DATA ", 1);
smtp_cmd($sock, "From: ". $smtpinfo{'from'} ." ", 0);
smtp_cmd($sock, "To: ". $smtpinfo{'to'} ." ", 0);
smtp_cmd($sock, "Subject: $subject ", 0);
smtp_cmd($sock, "$content ", 0);
smtp_cmd($sock, " . ", 1);
smtp_cmd($sock, "QUIT ", 0);
$sock->close();
return 1;
}
##############################
# Check server alive main
##############################
sub monitor_main {
# check apache
foreach $item (APACHE_SERVERS) {
if (!check_server_alive($item, APACHE_PORT)) {
send_mail("$item apache server is down", "$item apache server is down. please timely restoration");
}
}
# check mysql
foreach $item (MYSQL_SERVERS) {
if (!check_server_alive($item, MYSQL_PORT)) {
send_mail("$item mysql server is down", "$item mysql server is down. please timely restoration");
}
}
# check memcache
foreach $item (MEMCACHE_SERVERS) {
if (!check_server_alive($item, MEMCACHE_PORT)) {
send_mail("$item memcache server is down", "$item memcache server is down. please timely restoration");
}
}
# check search
foreach $item (SEARCH_SERVERS) {
if (!check_server_alive($item, SEARCH_PORT)) {
send_mail("$item search server is down", "$item search server is down. please timely restoration");
}
}
}
##############################
# Main running
##############################
monitor_main();