背景
在grafana+ prometheus+php 监控系统实践文章当中已经实现了我们的第一个监控图表,现在我们有了一个新需求,需要对多个节点实现不同的监控,以及一个汇总的监控;
按照我们之前手动创建仪表盘的方法,每新增一个节点都需要手动去修改prometheus配置,并且需要去grafana系统当中创建一个仪表盘,在节点很少的时候这种方式也能满足,但当节点数量多起来的时候,就会增加很大一部分工作量,并且存在每次创建的图表规则不一致的风险,因此我们的需求是在新增节点之后让grafana自动创建一个仪表盘。
实现过程
操作步骤
- prometheus调用中间件
- 中间件收集各节点数据
- 验证中间件数据有效性
- 调试仪表盘API接口
- 编写节点变动事件处理
一、prometheus调用中间件
1.修改prometheus配置文件
修改配置文件的目的是把之前直接连接单个节点的地址更改为中间件地址,之前单节点的uri为’==/api/v1/rrd/metrics==’,现在则需要将其修改为中间件地址’==/api/v1/rrd/toolSpool==’,因此新的配置文件内容如下:
---
global:
scrape_interval: 5s
scrape_timeout: 3s
scrape_configs:
- job_name: 'mysql'
scrape_interval: 5s
static_configs:
- targets: ['192.168.43.34:9104']
labels:
instance: 192.168.43.34
- job_name: 'media'
scrape_interval: 3s
metrics_path: "/api/v1/rrd/toolSpool"
static_configs:
- targets: ['gslb.offcncloud.com:8080']
1.2 重新启动prometheus
当修改完配置文件之后,还需要让其配置文件生效,所以需要将prometheus重新启动。
docker重启
docker本身就提供重启命令,所以只需要输入如下命令就可以了,注意后面是容器的名称。
docker restart prometheus
mac下重启
mac下重启比较简单,首先终止之前的任务,然后使用启动命令
prometheus --config.file=/tmp/prometheus.yml
二、中间件收集各节点数据
2.1 目的
中间件的作用是将各个节点的数据进行汇总,然后一次性返回给prometheus,实现这个中间件的方式有很多种,在实现之前我们也查找了一些资料,比如有网友用 consul-template+consul方式来实现,可参考下方链接;不过我觉得这个配置好像也不简单,而这个中间件的功能还挺简单的,所以还是自己使用PHP写了一个中间件。
2.2 中间件需要的效果:
- 拉取所有节点数据,这是核心作用
- 给各节点加标示,将来用来区分是哪个节点的
- 汇总并输出,需要一个汇总的仪表盘
2.3 获取节点数据,并添加标示
要获得各个节点的数据,name首先获就得取到所有的节点列表,然后通过节点的IP地址来拼接URL,最终通过curl请求该地址来得到节点数据;
在获得数据后,我们还小需要给每一个节点返回的数据加上标示可以用{}包括起来,因为prometheus支持这种格式,伪代码如下:
<?php
public function getNodeListAttributesInfo(Request $req)
{
//1.获取节点列表
$nodelist = RrdToolModel::getNodeListAttributesInfo($req);
//2.遍历获取各节点数据
$str = '';
foreach ($nodelist as $name) {
$url = "http://{$name->ip}/api/v1/rrd/metrics";
//3. 获取数据
$tmp = file_get_contents($url);
//4. 在每个节点中插入host属性,到时候用来做筛选单个节点
$tmp = str_replace(' ', " {host=\"{$name->ip}\"} ", $tmp);
$str .= $tmp;
}
2.4 汇总并输出
我们的核心需求是需要看到所有节点的汇总状况,所以在获得各个节点的数据后还需要进行累加,prometheus中貌似并直接不支持,所以我们得在中间件总进线累加汇总。
//限制需要进行汇总统计,首先把字符串分割为数组
$arr = explode(PHP_EOL, $str);
$tmpArr = [];
//遍历数组
foreach ($arr as $val) {
//把每一行再次分割
$valArr = explode(" ", $val);
//5. 汇总统计
if (!empty($valArr[0]) && is_string($valArr[0]) && is_numeric($valArr[2])) {
$tmpArr[$valArr[0]] = isset($tmpArr[$valArr[0]]) ? ($tmpArr[$valArr[0]] + $valArr[2]) : $valArr[2];
}
}
//6. 汇总输出
foreach ($tmpArr as $key => $num) {
echo "{$key}_total $num" . PHP_EOL;
}
echo $str;
}
2.5 输出最后结果
当中间件处理完成之后,我们需要各个节点的数据,并有在数据中需要有节点的标示,另外还需要一个汇总的数据,因此中间件返回数据如下:
media_connectNum_total 0
media_network_total 0
media_on_push_total 2
media_connectNum {host="192.168.43.46:8080"} 0
media_network {host="192.168.43.46:8080"} 0
media_on_push {host="192.168.43.46:8080"} 1
media_connectNum {host="127.0.0.1:8080"} 0
media_network {host="127.0.0.1:8080"} 0
media_on_push {host="127.0.0.1:8080"} 1
三、验证中间件数据有效性
现在我们的prometheus已经启动,并且中间件也正常运行,那么此时prometheus和Grafana应该都有相应变化,我们可以根据这写变化来确定我们前面的处理是否成功。
3.1 prometheus数据验证
当我们的配置文件和中间件发生变化后,最先产生相应变化的应该是数据仓库,所以我们可以打开打开prometheus的web界面,URL地址(http://192.168.43.34:9090/graph)
在筛选中输入media_network,然后进行筛选,如果能看到多个返回的记录,则说明验证成功,如下图所示。
3.2 Grafana数据验证
当prometheus数据仓库的数据发生变化后,grafana的仪表盘也应该会发生变化,最明显的变化如下图所示,标签都变成了双份,比如之前的一份“拥堵拉流数量”变成了双份。
原因多个节点返回了多份数据,而我们使用Grafana绘图的时候筛选项只输入了其中的key部分,并没有筛选里面的属性,因此有多少个节点就会有出来多少个项,如果数量对上了,说明Grafana也验证成功了。
3.3 设置汇总图
现在我们把之前的仪表盘,重新编辑一下,把之前只筛选了key改成筛选key+上属性,设置方式如下图
设置好之后,我们看到的将是汇总的仪表盘,至此我们第一个的核心需求已经实现了
3.4 设置节点模板
在设置汇总图后,我们还将要实现第二个核心需求,自动化创建单节点的仪表盘,我们首先需要手动先创建一个单个节点的图,和第一篇文章的创建方法一致,在设置筛选项的时候,我们填写的内容要带上属性,属性的作用可以筛选节点,如下图:
四、调试仪表盘API接口
API官方文档URL:http://docs.grafana.org/http_api/dashboard/
4.1 创建API接口
我们的目标是当新增节点时grafana能够自动创建相应的仪表盘,因此需要使用到grafana的API接口,使用之前需要先创建一个密钥用来授权,创建的流程如下图:
添加一个api,在keyname中随便填写一个名字,然后role选择admin权限,点击添加按钮
当创建成功能看到grafana页面弹框提示,我们需要把他先复制下来放到一个位置,因为后面是看不见这个key的,如下命令:
使用终端进行访问测试,如果返回结果如下,则代表这个key可以使用
4.2 使用postman调试
现在不要急着取用PHP进行调试,可以先用Postman进行调试,我们需要调试的并不是刚才弹框上面的URL地址,而是创建一个仪表盘的地址,在官方文档中的请求信息如下:
POST /api/dashboards/db HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"dashboard": {
"id": null,
"uid": null,
"title": "Production Overview",
"tags": [ "templated" ],
"timezone": "browser",
"schemaVersion": 16,
"version": 0
},
"folderId": 0,
"overwrite": false
}
使用postman请求截图
{
"id": 23,
"slug": "production-overview",
"status": "success",
"uid": "ID2FFcciz",
"url": "/d/ID2FFcciz/production-overview",
"version": 1
}
当返回如上结果,则说明已经创建成功了
4.3 导出模板
现在我们需要导出之前创建的一个节点仪表盘,用来做模板,导出仪表盘的配置方法比较简单,
把上面的json数据保存到 grafana.json文件中,在保存json文件的时候需要注意,导出来的json配置并不能直接使用,因为prometheus创建仪表盘的json格式并不是这样的,我们需要对这份json内容稍微处理一下,在其前后分别加上一些字符,效果如下
{
"dashboard":
-------导出json的内容放中间--------
,
"overwrite": false
}
注意:,既然他是模板文件,里面肯定有些东西是变化的,比如说他的title,和host属性,因此我们得在模板里面做一个标示,比如title部分我们可以用###title### ,方便后面的文本替换,具体可参考我的配置文件
保存之后,也可以拿这个json的内容用postman进行验证,使用postman能够正常添加后,我们再使用PHP的curl去实现
4.4 编写PHP发起请求代码
现在已经确保我们的json数据没有问题,所以现在使用PHP的curl来创建仪表盘,伪代码如下:
/**
* 通过curl获取数据
* @param $url
* @param bool $isHearder
* @param bool $post
* @return mixed
*/
function http_request($url, $isHearder = null, $post = 'GET', $data = null, $timeout = 1)
{
//初始化curl
$ch = curl_init($url);
//设置URL地址
curl_setopt($ch, CURLOPT_URL, $url);
//设置header信息
if (!empty($isHearder)) {
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, $isHearder);
}
//如果是post,则把data的数据传递过去
if (($post == 'POST') && $data) {
#假如data为数组将其转换为json格式
if (is_array($data)) {
$data = json_encode($data);
}
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
//如果是删除方法,则是以delete请求
if ($post == 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
}
//设置超时时间,毫秒
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $timeout*1000);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//执行CURL时间
$result = curl_exec($ch);
//如果有异常,记录到日志当中
$curl_errno = curl_errno($ch);
if ($curl_errno > 0) {
LogModel::addlog('超时');
}
//关闭URL,返回数据
curl_close($ch);
return $result;
}
五、编写节点变动事件处理
当上面的操作都完成之后,我们前期的基本工作已经完成了,现在需要做得事情是创建节点的时候调用PHP来发起请求
5.1 新增节点触发
通过api来创建仪表盘的部分伪代码,prometheus的仪表盘中有一个uid的key,这个key可以由我们自己控制,必须是保证他的唯一性(如果把json模板中的uid项设置为null,prometheus会自动为你生成一个);
我们可以使用节点IP地址的hash值作为他的uid,这样我们将来在变更仪表盘的时候只要有ip就能得到uid,而无需再次存储一份,如下面的伪代码:
/**
*创建图表
* @param $str
* @param array $params
* @return \Illuminate\Http\JsonResponse
*/
public function replaceNodeInfo($ip)
{
//接收节点触发事件
$uid = md5($ip);
//设置head头,认证信息
$header = array(
"Content-Type:application/json",
'Authorization: Bearer eyJrIjoicnhTMklodFMzaDRsUXFoUFFiZ2tSRnQ3TnI4WEVqQlEiLCJuIjoidGFuZ3Fpbmdzb25nIiwiaWQiOjF9'
);
//读取json模板
$jsonstr = $this->readJsonData();
//替换模板中需要替换的位置
$jsonstr = str_replace('###node###', $ip, $jsonstr);
$jsonstr = str_replace('###uid###', $uid, $jsonstr);
//进行curl请求
http_request('http://192.168.43.34:3000/api/dashboards/db', $header, 'POST', $jsonstr);
}
5.2 检查效果
当使用PHP的curl请求后,我们可以在grafana的仪表盘管理界面看到使用PHP创建的图表,当出现下图的效果则代表成功:
5.3 删除节点触发
删除节点的时候,我们对应的仪表盘也没用了作用,因此我们也要删除对应的仪表盘,前面我们生产的uid是ip地址的hash值,因此我们删除的时候也可以取ip对应的hash值,通过这个uid来删除仪表盘,如下伪代码:
/**
* 删除node节点视图信息
* @param Request $req
*/
public function delNodeViewInfo(Request $req)
{
//接收参数
$params = $req->all();
$ip = $params['ip'];
$uid = md5($ip);
//设置认证信息
$header = array(
"Content-Type:application/json",
'Authorization: Bearer eyJrIjoicnhTMklodFMzaDRsUXFoUFFiZ2tSRnQ3TnI4WEVqQlEiLCJuIjoidGFuZ3Fpbmdzb25nIiwiaWQiOjF9'
);
//执行删除事件
http_request("http://192.168.43.34:3000/api/dashboards/uid/{$uid}", $header, 'DELETE');
}
作者:汤青松