2008年8月11日星期一

Linux服务器系统监控框架与MSN、E-mail、手机短信报警的实现

 [文章作者:张宴 本文版本:v1.0 最后修改:2008.06.25 转载请注明原文链接:http://blog.s135.com/read.php/354.htm]

  最近,在我原有的“Linux服务器系统监控程序”基础上,完善了HTTP、TCP、MySQL主动监控与MSN、E-mail、手机短信报警。监控程序以shell和PHP程序编写,以下为主要框架与部分代码:

  一、系统监控接口程序(interface.php)具有的报警方式
  1、MSN实时报警
  ①、监控程序每次检测到故障存在、或者故障恢复,都会发送短消息到管理员的MSN。
  点击在新窗口中浏览此图片

  点击在新窗口中浏览此图片

  发送MSN短消息用了一个PHP类:sendMsg,使用该PHP类发消息,必须将发送、接收双方的MSN加为联系人,发送中文时,先用iconv将字符集转为UTF-8:
引用
$sendMsg->sendMessage(iconv("GBK", "UTF-8", $message), 'Times New Roman', '008000');




  2、手机短信报警
  ①、工作日早上10点之前,晚上6点之后,以及周六、周日,监控程序检测到故障,会调用手机短信接口,发送短信给管理员的手机。
  ②、如果监控程序多次检测到同一台服务器的同一类故障,只会在第一次检测到故障时发送一条“故障报警”短信。服务器故障恢复后,监控程序会再发送一条“故障恢复”短信。

  如果没有手机短信网关接口,可以试试中国移动通信的www.139.com邮箱,具有免费的邮件到达手机短信通知功能,可以将收到的邮件标题以短信的形式发送到手机上。



  3、电子邮件报警
  ①、如果监控程序多次检测到同一台服务器的同一类故障,只会在第一次检测到故障时发送一封“故障报警”邮件。服务器故障恢复后,监控程序会再发送一封“故障恢复”邮件。

  系统监控接口程序interface.php(核心部分,仅提供部分代码):
  1. //HTTP服务器监控
  2. if (htmlspecialchars($_POST["menu"]) == "http")
  3. {
  4. $date = htmlspecialchars($_POST["date"]);
  5. $ip = htmlspecialchars($_POST["ip"]);
  6. $port = htmlspecialchars($_POST["port"]);
  7. $status = htmlspecialchars($_POST["status"]);//状态,0表示无法访问,1表示正常,2表示无法访问但能ping通
  8. //...下一步处理(省略)...
  9. }
  10. //TCP服务器监控
  11. if (htmlspecialchars($_POST["menu"]) == "tcp")
  12. {
  13. $date = htmlspecialchars($_POST["date"]);
  14. $ip = htmlspecialchars($_POST["ip"]);
  15. $port = htmlspecialchars($_POST["port"]);
  16. $status = htmlspecialchars($_POST["status"]);//状态,0表示无法访问,1表示正常,2表示无法访问但能ping通
  17. //...下一步处理(省略)...
  18. }
  19. //MySQL服务器监控
  20. if (htmlspecialchars($_POST["menu"]) == "mysql")
  21. {
  22. $date = htmlspecialchars($_POST["date"]);
  23. $ip = htmlspecialchars($_POST["ip"]);
  24. $port = htmlspecialchars($_POST["port"]);
  25. $abstract = htmlspecialchars($_POST["abstract"]);//故障摘要(必须为全角)
  26. $info = htmlspecialchars($_POST["info"]);//故障详细描述
  27. $failback = htmlspecialchars($_POST["failback"]);//如果服务器存活,此处接收的值为active
  28. //...下一步处理(省略)...
  29. }
  30. ?>


  二、主动探测监控(“监控机”主动探测“被监控机”)
  1、HTTP服务器监控
  脚本:/data0/monitor/http.sh
引用
#!/bin/sh
LANG=C

#被监控服务器、端口列表
server_all_list=(\
192.168.1.1:80 \
192.168.1.2:80 \
192.168.1.3:80 \
)

date=$(date -d "today" +"%Y-%m-%d_%H:%M:%S")

#采用HTTP POST方式发送检测信息给接口程序interface.php,接口程序负责分析信息,决定是否发送报警MSN消息、手机短信、电子邮件。
send_msg_to_interface()
{
/usr/bin/curl -m 600 -d menu=http -d date=$date -d ip=$server_ip -d port=$server_port -d status=$status http://127.0.0.1:8888/interface.php
}

server_all_len=${#server_all_list[*]}
i=0
while [ $i -lt $server_all_len ]
do
server_ip=$(echo ${server_all_list[$i]} | awk -F ':' '{print $1}')
server_port=$(echo ${server_all_list[$i]} | awk -F ':' '{print $2}')
if curl -m 10 -G http://${server_all_list[$i]}/ > /dev/null 2>&1
then
#status: 0,http down 1,http ok 2,http down but ping ok
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!"
else
if curl -m 30 -G http://${server_all_list[$i]}/ > /dev/null 2>&1
then
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!"
else
if ping -c 1 $server_ip > /dev/null 2>&1
then
status=2
echo "服务器${server_ip},端口${server_port}无法访问,但是能够Ping通!"
else
status=0
echo "服务器${server_ip},端口${server_port}无法访问,并且无法Ping通!"
fi
fi
fi
send_msg_to_interface
let i++
done



  2、TCP服务器监控
  脚本:/data0/monitor/tcp.sh
引用
#!/bin/sh
LANG=C

#被监控服务器、端口列表
server_all_list=(\
192.168.1.4:11211 \
192.168.1.5:11211 \
192.168.1.6:25 \
192.168.1.7:25 \
)

date=$(date -d "today" +"%Y-%m-%d_%H:%M:%S")

#采用HTTP POST方式发送检测信息给接口程序interface.php,接口程序负责分析信息,决定是否发送报警MSN消息、手机短信、电子邮件。
send_msg_to_interface()
{
/usr/bin/curl -m 600 -d menu=tcp -d date=$date -d ip=$server_ip -d port=$server_port -d status=$status http://127.0.0.1:8888/interface.php
}

server_all_len=${#server_all_list[*]}
i=0
while [ $i -lt $server_all_len ]
do
server_ip=$(echo ${server_all_list[$i]} | awk -F ':' '{print $1}')
server_port=$(echo ${server_all_list[$i]} | awk -F ':' '{print $2}')
if nc -vv -z -w 3 $server_ip $server_port > /dev/null 2>&1
then
#status: 0,http down 1,http ok 2,http down but ping ok
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!"
else
if nc -vv -z -w 10 $server_ip $server_port > /dev/null 2>&1
then
status=1
echo "服务器${server_ip},端口${server_port}能够正常访问!"
else
if ping -c 1 $server_ip > /dev/null 2>&1
then
status=2
echo "服务器${server_ip},端口${server_port}无法访问,但是能够Ping通!"
else
status=0
echo "服务器${server_ip},端口${server_port}无法访问,并且无法Ping通!"
fi
fi
fi
send_msg_to_interface
let i++
done



  3、MySQL服务器监控
  ①、MySQL是否能够连接
  ②、MySQL是否发生表损坏等错误
  ③、MySQL活动连接数是否过多
  ④、MySQL从库是否同步正常
  ⑤、MySQL从库同步延迟时间是否过大
  脚本:/data0/monitor/mysql.php
  1. //$server_list[]="服务器地址:端口:帐号:密码";
  2. $server_list[]="192.168.1.11:3306:root:password";
  3. $server_list[]="192.168.1.12:3306:root:password";
  4. $server_list[]="192.168.1.13:3306:root:password";
  5. $database="mysql";
  6. $curl = new Curl_Class();
  7. foreach ($server_list as $server) {
  8. $status=1;//初始化,正常状态
  9. unset($data);
  10. $data["menu"] = "mysql";
  11. $data["info"] = "";
  12. list($data["ip"], $data["port"], $username, $password) = explode(":", $server);
  13. $connect = @mysql_connect($data["ip"].":".$data["port"], $username, $password);
  14. if(! $connect)
  15. {
  16. $status=0;
  17. $data["info"] = $data["info"] . "无法连接MySQL服务器\r\n";
  18. }
  19. $select = @mysql_select_db($database, $connect);
  20. $result = @mysql_query("show slave status");
  21. $rs_slave = @mysql_fetch_array($result);
  22. $result = @mysql_query("show global status like 'Threads_running'");
  23. $rs_threads = @mysql_fetch_array($result);
  24. if($rs_slave["Slave_SQL_Running"] == "No")
  25. {
  26. $status=0;//故障状态
  27. $data["abstract"] = "从库不同步";
  28. $data["info"] = $data["info"] . "Slave_SQL_Running = No\r\n";
  29. }
  30. if($rs_slave["Slave_IO_Running"] == "No")
  31. {
  32. $status=0;
  33. $data["abstract"] = "从库不同步";
  34. $data["info"] = $data["info"] . "Slave_IO_Running = No\r\n";
  35. }
  36. if($rs_slave["Last_Error"] != "")
  37. {
  38. $status=0;
  39. $data["abstract"] = "从库同步出错";
  40. $data["info"] = $data["info"] . "Last_Error = ".substr($rs_slave["Last_Error"], 0, 40)."\r\n";
  41. }
  42. if($rs_slave["Seconds_Behind_Master"] > 180)
  43. {
  44. $status=0;
  45. $data["abstract"] = "从库同步延迟时间高达".$rs_slave["Seconds_Behind_Master"]."秒";
  46. $data["info"] = $data["info"] . "Seconds_Behind_Master = ".$rs_slave["Seconds_Behind_Master"]."\r\n";
  47. }
  48. if($rs_threads["Value"] > 60)
  49. {
  50. $status=0;
  51. $data["abstract"] = "活动连接数多达".$rs_threads["Value"];
  52. $data["info"] = $data["info"] . "Threads_running = ".$rs_threads["Value"]."\r\n";
  53. }
  54. $data["date"] = date("Y-m-d_H:i:s");
  55. if($status == 0)
  56. {
  57. $post = @$curl->post("http://127.0.0.1:8888/interface.php", $data);
  58. echo "MySQL服务器“".$data["ip"].":".$data["port"]."”发生故障!\n";
  59. print_r($post);
  60. }
  61. else
  62. {
  63. $data["failback"] = "active";//服务器正常,发送通知信息
  64. $post = @$curl->post("http://127.0.0.1:8888/interface.php", $data);
  65. echo "MySQL服务器“".$data["ip"].":".$data["port"]."”运行正常!\n";
  66. print_r($post);
  67. }
  68. }
  69. /**
  70. *********************************************************************
  71. * Curl_Class :curl 类
  72. *********************************************************************/
  73. class Curl_Class
  74. {
  75. function Curl_Class()
  76. {
  77. return true;
  78. }
  79. function execute($method, $url, $fields = '', $userAgent = '', $httpHeaders = '',
  80. $username = '', $password = '')
  81. {
  82. $ch = Curl_Class::create();
  83. if (false === $ch)
  84. {
  85. return false;
  86. }
  87. if (is_string($url) && strlen($url))
  88. {
  89. $ret = curl_setopt($ch, CURLOPT_URL, $url);
  90. }
  91. else
  92. {
  93. return false;
  94. }
  95. //是否显示头部信息
  96. curl_setopt($ch, CURLOPT_HEADER, false);
  97. //
  98. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  99. if ($username != '')
  100. {
  101. curl_setopt($ch, CURLOPT_USERPWD, $username . ':' . $password);
  102. }
  103. $method = strtolower($method);
  104. if ('post' == $method)
  105. {
  106. curl_setopt($ch, CURLOPT_POST, true);
  107. if (is_array($fields))
  108. {
  109. $sets = array();
  110. foreach ($fields as $key => $val)
  111. {
  112. $sets[] = $key . '=' . urlencode($val);
  113. }
  114. $fields = implode('&', $sets);
  115. }
  116. curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
  117. }
  118. else
  119. if ('put' == $method)
  120. {
  121. curl_setopt($ch, CURLOPT_PUT, true);
  122. }
  123. //curl_setopt($ch, CURLOPT_PROGRESS, true);
  124. //curl_setopt($ch, CURLOPT_VERBOSE, true);
  125. //curl_setopt($ch, CURLOPT_MUTE, false);
  126. curl_setopt($ch, CURLOPT_TIMEOUT, 600);
  127. if (strlen($userAgent))
  128. {
  129. curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
  130. }
  131. if (is_array($httpHeaders))
  132. {
  133. curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
  134. }
  135. $ret = curl_exec($ch);
  136. if (curl_errno($ch))
  137. {
  138. curl_close($ch);
  139. return array(curl_error($ch), curl_errno($ch));
  140. }
  141. else
  142. {
  143. curl_close($ch);
  144. if (!is_string($ret) || !strlen($ret))
  145. {
  146. return false;
  147. }
  148. return $ret;
  149. }
  150. }
  151. function post($url, $fields, $userAgent = '', $httpHeaders = '', $username = '',
  152. $password = '')
  153. {
  154. $ret = Curl_Class::execute('POST', $url, $fields, $userAgent, $httpHeaders, $username,
  155. $password);
  156. if (false === $ret)
  157. {
  158. return false;
  159. }
  160. if (is_array($ret))
  161. {
  162. return false;
  163. }
  164. return $ret;
  165. }
  166. function get($url, $userAgent = '', $httpHeaders = '', $username = '', $password =
  167. '')
  168. {
  169. $ret = Curl_Class::execute('GET', $url, '', $userAgent, $httpHeaders, $username,
  170. $password);
  171. if (false === $ret)
  172. {
  173. return false;
  174. }
  175. if (is_array($ret))
  176. {
  177. return false;
  178. }
  179. return $ret;
  180. }
  181. function create()
  182. {
  183. $ch = null;
  184. if (!function_exists('curl_init'))
  185. {
  186. return false;
  187. }
  188. $ch = curl_init();
  189. if (!is_resource($ch))
  190. {
  191. return false;
  192. }
  193. return $ch;
  194. }
  195. }
  196. ?>


  4、主动监控守护进程
  脚本:/data0/monitor/monitor.sh
引用
#!/bin/sh
while true
do
/bin/sh /data0/monitor/http.sh > /dev/null 2>&1
/bin/sh /data0/monitor/tcp.sh > /dev/null 2>&1
/usr/local/php/bin/php /data0/monitor/mysql.php > /dev/null 2>&1
sleep 10
done


  启动主动监控守护进程:
/usr/bin/nohup /bin/sh /data0/monitor/monitor.sh 2>&1 > /dev/null &



  三、被动报告监控(“被监控机”采集数据发送给“监控机”)
  1、磁盘空间使用量监控
  2、磁盘Inode使用量监控
  3、Swap交换空间使用量监控
  4、系统负载监控
  5、Apache进程数监控

  被动监控这部分,在我的文章《写完“Linux服务器监控系统 ServMon V1.1” 》中已经实现,就不再详细写出。

没有评论: