使用ELK日志等进行监控报警
- 2019-05-19 12:30:00
- CJL 原创
- 6886
在公司监控体系不完善的时候,我们怎么能实现快速发现业务故障,进行修复呢?
为了尽量减少复杂度,减少依赖,我们利用最基本的crontab elk php脚本等linux常见工具实现一个简单的监控体系,并通过集成钉钉机器人的方式进行业务通知。
一、整体思路
通过linux crontab进行计划任务的部署,每分钟执行一次控制脚本,进行监控脚本的触发。
通过ES引擎的查询获得api接口分析数据。
通过Redis、Mysql、MQ查询获得基础组件的状态。
通过模拟crontab语法进行内部检查项的检查次数控制。
通过钉钉机器人hook接口进行消息通知。
二、实现
1)linux crontab计划任务部署
参考文章:https://www.cnblogs.com/intval/p/5763929.html

2)ES查询接口
ES引擎提供了API接口可以使用丰富的语法查询到我们需要的数据,比如接口请求量、接口响应时间、错误日志数量等,通过ES的聚合功能我们还能获得更丰富的统计数据,比如接口响应时间增长率,各种指标的环比同比等,通过计算可以得到数据的动态变化过程,主动发现业务爆发趋势,提前做预警。
通过_search接口进行查询,接口支持GET和POST,我们通过POST body传递查询json串
ES查询语法参考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_empty_search.html
调试可以使用Kibana的Dev Tools。

a) 统计数量可以使用简单查询:https://www.elastic.co/guide/cn/elasticsearch/guide/current/query-dsl-intro.html
单条件:
{
"query": "YOUR_QUERY_HERE"
}复合条件:must相当于AND,should相当于OR,minimum_should_match是最小匹配数量
{
"bool": {
"must": { "match": { "email": "business opportunity" }},
"should": [
{ "match": { "starred": true }},
{ "bool": {
"must": { "match": { "folder": "inbox" }},
"must_not": { "match": { "spam": true }}
}}
],
"minimum_should_match": 1
}
}b) 范围查询 https://www.elastic.co/guide/cn/elasticsearch/guide/current/_ranges.html
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lt" : 40
}
}
}
}
}
}日期:支持相对值now-1h,2014-01-01 00:00:00||+1M。s秒,m分钟,h小时,d天,M月,y年
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}c) 统计分析 https://www.elastic.co/guide/cn/elasticsearch/guide/current/_aggregation_test_drive.html
聚合可以再query筛选的结果上进行,所以我们可以筛选出来数据后再进行具体的变化趋势分析
根据字段分组:
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}根据日期分组:date_histogram,interval是时间间隔
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "month",
"format": "yyyy-MM-dd"
}
}
}
}统计数据:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_adding_a_metric_to_the_mix.html
通过在统计中嵌套添加分析指标可以获得 分均值、求和等。avg平均值,sum求和,max最大值,min最小值,
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}通过统计值我们就可以计算出更复杂的统计指标,比如增长率、环比、同比等,通过这些计算可以轻松的获得变化趋势,预知业务风险。
3)查询Redis状态
redis提供了info命令提供系统信息,我们可以通过PHP的redis扩展连接服务器获得状态,也可以通过linux shell命令更简单的获得。
比如内存占用:
/usr/local/redis/bin/redis-cli -a ***** -h 10.0.0.1 info memory|grep used_memory:|awk -F ':' '{print $2}'其他指标可以变通去实现,redis-cli命令参考:https://www.runoob.com/redis/redis-commands.html
PHP里可以使用exec命令去执行shell命令获得命令结果。
4)查询Mysql状态
mysql可以通过show status进行状态查询,可以通过PDO连接,也可以通过shell命令获取
mysql -h 10.0.0.1 -u test -P 3306 -p4sk*****8 -e 'show status'|grep "Uptime"|awk '{print $2}'|head -n 15)查询RabbitMQ状态
RabbitMQ提供了一套httpApi供我们使用,可以通过curl直接获取队列状态,
API文档:https://cdn.rawgit.com/rabbitmq/rabbitmq-management/v3.7.14/priv/www/api/index.html
或者自己安装的RabbitMQ:http://10.0.0.1:15672/api/
使用接口:/api/queues/ vhost/ name
通过HTTP basic authentication进行身份认证,auth字符串计算方法:base64_encode(user:password) 。通过header传递:Authorization: Basic authstr
可以查询到队列的消息数量、消费者数量等信息
6)自定义内部crontab语法
通过调用crontabCheck方法对每个检查项的执行时间进行检查,检查通过才执行,这样我们可以对某个检查项进行单独的时间及频率控制
Util::crontabCheck("* 10-19 * * 1-5");
class Util
{
public static function crontabCheck($config)
{
list($m, $h, $D, $M, $W) = explode(' ', trim($config) . ' * * * * *');
$cm = date('i');
$ch = date('h');
$cD = date('d');
$cM = date('m');
$cW = date('w');
if (self::crontabItemCompare($m, $cm)
&& self::crontabItemCompare($h, $ch)
&& self::crontabItemCompare($D, $cD)
&& self::crontabItemCompare($M, $cM)
&& self::crontabItemCompare($W, $cW)) {
return true;
}
return false;
}
public static function crontabItemCompare($s, $c)
{
if ($s == '*') {
return true;
} else if (strpos($s, '/') !== false) {
$d = substr($s, strpos($s, '/') + 1);
if (((int)$c % (int)$d) == 0) {
return true;
}
} else if (strpos($s, '-') !== false) {
list($start, $end) = explode('-', $s);
if ((int)$c >= (int)$start && (int)$c <= (int)$end) {
return true;
}
} else if (strpos($s, ',') !== false) {
$vs = explode(',', $s);
foreach ($vs as $v) {
if ((int)$v == (int)$c) {
return true;
}
}
} else {
if ($s == $c) {
return true;
}
}
return false;
}
}7)钉钉发送提醒消息
参考官方文档:https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
在符合报警条件时通过curl发送消息,我们可以在消息中进行@操作,添加图片、添加链接。
三、部分源码
计划任务入口:
<?php
include_once 'autoload.php';
$checkList = array(
'PingCheck' => '*/10 * * * *',
);
$logFile = __DIR__ . '/log/run.log';
foreach($checkList as $class => $crontab) {
if (!Util::crontabCheck($crontab)) {
continue;
}
$check = new $class();
$check->danger();
$check->warning();
$check->ping();
file_put_contents($logFile, "执行 $class :" . date('Y-m-d H:i:s ') . json_encode($check->getCacheData(), JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
}ES查询:
<?php
class ESQuery
{
protected static $server = 'http://10.0.0.1:9200/';
public static function getData($indexs, $query)
{
$url = self::$server . $indexs . "/_search";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
$data = curl_exec($curl);
curl_close($curl);
return json_decode($data, true);
}
}<?php
class PingCheck extends BaseCheck implements MonitorInterface
{
protected static $index = 'logstash-access-*';
protected static $query = <<<EOF
{
"size": 0,
"query": {
"bool": {
"must": [
{
"range": {
"@timestamp": {
"gt":"now-5m"
}
}
}
]
}
}
}
EOF;
public function getData()
{
$result = ESQuery::getData(self::$index, self::$query);
return $result['hits']['total'];
}
public function warning()
{
}
public function danger()
{
}
public function ping()
{
if ($this->getCacheData() > 1) {
DingTalk::robotSendMessage(DingTalk::getMarkdownMessage('监控心跳 5min', self::class, '日志数量:' . $this->getCacheData()));
}
}
}RabbitMq API请求:
public function api($server, $path, $method = 'GET', $params = array())
{
$baseUrl = sprintf('http://%s:%d', $server['host'], $server['port']);
$url = $baseUrl . $path;
if ($method == 'GET' && !empty($params)) {
$url = $url . '?' . http_build_query($params);
}
$auth = implode(':', [$server['user'], $server['password']]);
$headers = array("Authorization: Basic " . base64_encode($auth));
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
if ($method == 'POST') {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
}
$data = curl_exec($curl);
curl_close($curl);
return json_decode($data, true);
}钉钉发送消息
<?php
/**
* https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
*/
class DingTalk
{
const ROBOTSENDAPI = "https://oapi.dingtalk.com/robot/send";
private static $accessTokenKey;
/**
* @return mixed
*/
public static function getAccessTokenKey()
{
return self::$accessTokenKey;
}
/**
* @param mixed $accessTokenKey
*/
public static function setAccessTokenKey($accessTokenKey)
{
self::$accessTokenKey = $accessTokenKey;
}
public static function robotSendMessage($data, $atMobiles = array())
{
if (!empty($atMobiles)) {
$data['at']['atMobiles'] = $atMobiles;
if ($data['msgtype'] == 'text') {
$data['text'] = self::appendAtText($data['text'], $atMobiles);
} else if ($data['msgtype'] == 'markdown') {
$data['markdown']['text'] = self::appendAtText($data['markdown']['text'], $atMobiles);
}
}
return self::requestByCurl(self::getAccessToken(self::getAccessTokenKey()), $data);
}
public static function appendAtText($text, $atMobiles)
{
foreach ($atMobiles as $mobile) {
$text .= " @$mobile";
}
return $text;
}
public static function robotSendNoticeMessage($title, $tag, $message, $atMobiles = array())
{
$data = self::getMarkdownMessage($title, $tag, $message, 'notice');
self::robotSendMessage($data, $atMobiles);
}
public static function robotSendDangerMessage($title, $tag, $message, $atMobiles = array())
{
if (isset(Config::$dangerAtMobile)) {
$atMobiles += Config::$dangerAtMobile;
}
$data = self::getMarkdownMessage($title, $tag, $message, 'danger');
self::robotSendMessage($data, $atMobiles);
}
public static function getAccessToken($key)
{
if (isset(Config::$dingtalkRobotAccessToken[$key])) {
return Config::$dingtalkRobotAccessToken[$key];
}
return "";
}
public static function getTextMessage($tag, $message)
{
$message = self::packageMessage($tag, $message);
$data = [];
$data['msgtype'] = 'text';
$data['text']['content'] = $message;
return $data;
}
public static function getMarkdownMessage($title, $tag, $message, $level = '')
{
$message = self::packageMessage($tag, $message);
$img = '';
if ($level == 'warning') {
// $img = "";
} else if ($level == 'danger') {
$img = "";
}
$data = [];
$data['msgtype'] = 'markdown';
$data['markdown']['title'] = $title;
$data['markdown']['text'] = "## $title ## \n $img \n\n > " . $message;
return $data;
}
private static function packageMessage($tag, $message)
{
if (!is_string($message)) {
$message = json_encode($message);
}
if (is_array($tag)) {
$tag = implode($tag, " ");
}
return "[ $tag ] " . date('Y-m-d H:i:s') . " \n\n > " . $message;
}
private static function requestByCurl($token, $data)
{
if (!is_string($data)) {
$data = json_encode($data);
}
$webUrl = self::ROBOTSENDAPI . "?access_token=$token";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $webUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json;charset=utf-8'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
}四、效果
