0x01 前言
经常有人问我家里服务器的功率是多少,我网站的访问量是多少,在经过一番斗争后我决定将这些数据通过status pages展示出来。
在前两周我配置好了一款名为Cachet开源的status pages服务(NGX Services Status Page),配置过程请参考以下文章:
这次我还是使用python脚本调用各个系统的API,然后将数据填充到Cachet中,最终以图表的形式展示出来:
0x02 准备
首先,需要安装上面的文章配置好Cachet,然后登入到控制台,到METRICS标签中添加图表:
先配置电功率的图表,在配置过程中需要注意“图表计算方法”这个选项,我的计划是每分钟从zabbix中取数据,然后写入Cachet,所以这里需要选择average(平均数);然后配置nginx的访问次数,而这里则需要选择sum(总和):
上图中的“Suffix”为单位,可根据实际情况进行填写。
填写完所有内容后即可单击“ADD”以完成图表的创建。
在开始编写脚本之前需要Cachet相关的API信息,所有的API可以在以下地址中找到:
因为需要在图表中插入数据,所以在这里我们需要用到以下API:
还需要在zabbix中取数据,所以需要在以下地址中寻找zabbix的API信息:
而我的nginx日志是在ELK中分析的,所以还需要找到elasticsearch相关的信息:
这部分的内容比较复杂,需要有一个系统的学习或初步的认识才能理解,所以这里不多做讲解。
0x03 脚本
首先需要定义三个服务的API地址:
zbx_api_url = 'http://zabbix.t.com/api_jsonrpc.php' es6_api_url = 'http://es6-node1.t.com:9200/' cachethq_url = 'https://status.ngx.hk/api/v1/'
0x03.1 时间戳
我这个脚本会每分钟执行一次,而我希望将这些数据插入Cachet中的整分钟点钟(这句话有点难理解),例如:
- 19:01:00
- 19:02:00
- 19:03:00
- 19:04:00
与此同时,Cachet的API仅接受时间戳格式的时间表达,所以,我需要预先处理下时间:
def get_datetime(): datetime_now = datetime.today().strftime("%Y-%m-%d %H:%M:00") datetime_now = datetime.strptime(datetime_now, '%Y-%m-%d %H:%M:%S') datetime_old = datetime_now - timedelta(minutes=1) timestamp_now = datetime_now.timestamp() timestamp_now = int(timestamp_now * 1000) timestamp_old = datetime_old.timestamp() timestamp_old = int(timestamp_old * 1000) output_dict = dict() output_dict['timestamp_now'] = str(timestamp_now) output_dict['timestamp_old'] = str(timestamp_old) return output_dict
先定义一个函数,获取当前的时间,但秒数我强制修改为“00”,然后再用strptime格式化。
因为需要在elasticsearch中搜索前一分钟内的访问量,所以还需要算出前一分钟的时间,用变量名datetime_old表示。
最后输出以下格式的字典:
{'timestamp_old': '1529933400000', 'timestamp_now': '1529933460000'}
0x03.2 zabbix
zabbix的API调用需要通过用户名和密码获取一个token,用完之后还需要登出以销毁该token,所以会有以下两个函数,分别实现登入和登出:
def zbx_login(zbx_username, zbx_passwd, zbx_id): payload = { "jsonrpc": "2.0", "method": "user.login", "params": { "user": zbx_username, "password": zbx_passwd }, "id": zbx_id, } headers = {'content-type': 'application/json'} req_run = requests.post(zbx_api_url, data=json.dumps(payload), headers=headers) req_content = json.loads(req_run.text) return req_content def zbx_logout(zbx_login_id, zbx_login_token): payload = { "jsonrpc": "2.0", "method": "user.logout", "params": [], "id": zbx_login_id, "auth": zbx_login_token } headers = {'content-type': 'application/json'} req_run = requests.post(zbx_api_url, data=json.dumps(payload), headers=headers) req_content = json.loads(req_run.text) return req_content
在登入函数中的payload有个id值,这里用时间戳填充,其实这是不严谨的,应该用UUID填充,这样才能保证该token与id对应的唯一性,但我的应用环境中要求不高,所以直接用时间戳填充。
登入和登出函数的输出内容如下:
#zbx_login {'jsonrpc': '2.0', 'id': '1529934000000', 'result': 'e925d67c6fc019447db9e63eb1d27a21'} #zbx_logout {'jsonrpc': '2.0', 'id': '1529934000000', 'result': True}
取得zabbix token之后就可以调用history.get这个API获取最新的一条数据了:
def get_zbx_item_value(zbx_token, zbx_item_id): payload = { "jsonrpc": "2.0", "method": "history.get", "params": { "output": "extend", "history": 0, "itemids": zbx_item_id, "sortfield": "clock", "sortorder": "DESC", "limit": 1 }, "auth": zbx_token, "id": 1 } headers = {'content-type': 'application/json'} req_run = requests.post(zbx_api_url, data=json.dumps(payload), headers=headers) req_content = json.loads(req_run.text) req_value = req_content['result'][0]['value'] return str(req_value)
因为在zabbix中,item的id是唯一的,所以只需要找到相应的item,就可以找到相应的id:
然后利用zabbix token与item id一起调用该函数,返还的结果如下:
553.8000
其实返还的实际内容是json格式的,只是经过数据提取后,直接返还了实际的数值:
{ 'id': 1, 'result': [ { 'ns': '598647172', 'clock': '1529934932', 'value': '593.8000', 'itemid': '31712' } ], 'jsonrpc': '2.0' }
0x03.3 elasticsearch
elasticsearch的数据统计比较简单,因为我只想知道一分钟内的访问量有多少,那么我只需要计算出这一分钟内elasticsearch接收到多少条日志就可以了,至于请求的内容是图片、js还是css我并不关心,所以payload的json如下:
{ "size": 0, "_source": { "excludes": [] }, "aggs": {}, "stored_fields": [ "@timestamp" ], "query": { "bool": { "must": [ { "match_all": {} }, { "range": { "@timestamp": { "gte": es_gte, "lte": es_lte, "format": "epoch_millis" } } } ] } } }
另外,在请求的header中必须将payload设置为json:
headers = {'content-type': 'application/json'}
完整的函数如下:
def get_number_of_visits(es_index_name, es_gte, es_lte): payload = { "size": 0, "_source": { "excludes": [] }, "aggs": {}, "stored_fields": [ "@timestamp" ], "query": { "bool": { "must": [ { "match_all": {} }, { "range": { "@timestamp": { "gte": es_gte, "lte": es_lte, "format": "epoch_millis" } } } ] } } } headers = {'content-type': 'application/json'} req_run = requests.post(es6_api_url + es_index_name + '/_search', data=json.dumps(payload), headers=headers) req_content = json.loads(req_run.text) req_value = req_content['hits']['total'] return str(req_value)
和zabbix获取数值的函数一样,最终输出的结果也是经过处理的:
118
而原始的返还内容如下:
{ 'took': 2, '_shards': { 'skipped': 0, 'total': 5, 'failed': 0, 'successful': 5 }, 'hits': { 'hits': [], 'total': 118, 'max_score': 0.0 }, 'timed_out': False }
0x03.4 Cachet
获取到所需要的数据后,接下来需要将数据填充到Cachet中,直接调用API即可:
def cachethq_metrics_add_point(api_token, metric_id, metric_value, metric_timestamp): req_url = cachethq_url + 'metrics/' + str(metric_id) + '/points' payload = { "value": metric_value, "timestamp": metric_timestamp } headers = {'X-Cachet-Token': api_token} req_run = requests.request('POST', req_url, data=payload, headers=headers) req_content = json.loads(req_run.text) return req_content
Cachet的API调用也需要验证身份,不过不需要使用用户名和密码,而是通过在header中添加“X-Cachet-Token”的token值即可,而这个token可以在控制台中点击头像,在个人信息配置页面中找到:
该函数中有metric_id这个变量,这是图表的id值,可以在控制台的图表页面中找到:
如果该函数能正常运行,那么会返还以下内容:
{ 'data': { 'created_at': '2018-06-25 22:14:00', 'metric_id': 1, 'id': 20475, 'counter': 1, 'value': 576.4, 'calculated_value': 576.4, 'updated_at': '2018-06-25 22:14:32' } }
0x04 收尾
完整的脚本可以在以下地址中找到:
将脚本放置到相应的路径后,再通过crond进行定时调用:
*/1 * * * * root /usr/bin/python3 /usr/local/services_data/shell/cachethq/main_temp.py
0x05 结语
经过将近一个月的测试与观察,一切运行正常。接下来计划增加各类模块,以便对接zabbix的监控,从而实现自动化调整各个服务的状态。