Telegram机器人开发初步——一个Grafana告警机器人

起因

话说自己用Prometheus+Grafana搭的监控系统用了有一段时间了,但是一直没有配置告警,因为觉得发邮件不够实时,而且现在邮件服务商的SMTP配置也很麻烦,又没有什么免费的短信接口(当然,把邮件发给运营商邮箱是一个办法,但是运营商的邮箱也基本不用)。

最近想想还是配一个吧,据说用钉钉的通知功能不错,然而结果折腾半天还是被垃圾钉钉给坑了:

要先创建一个钉钉群(我是用当面建群的方式创建了一个只有自己的群),然后添加智能群助手下的机器人,加一个自定义webhook机器人,输入名字……“由于智能机器人需要安全配置,请前往电脑端添加”。

只好前往电脑端,结果那边到了添加智能群助手这步就卡住了:页面是空白。搜了半天没找到问题原因,传说可能原因之一的网络问题实测并不存在,估计是钉钉客户端用的嵌入浏览器有问题。

没办法,只能改其它渠道了。

最好的当然是Telegram,但不幸的是它的API服务国内无法到达……只好自己写一个机器人来处理吧。

创建机器人

在TG里搜索@BotFather机器人,对它说:

/newbot

然后按提示依次输入你要创建的机器人显示名,比如:GrafanaAlert,再输入机器人用户名,这个必须唯一,比如:XXXXGrafanaAlertBot——必须以bot结尾,大小写不限制。

成功以后,会返回一个Token,把这个Token记下来。

现在你就可以跟这个机器人说话了,当然它不会有任何反应——注意,有过聊天才能激活机器人。

创建频道并取得ID

在TG里创建一个新的频道,比如:xxxx_grafana_alert。然后把机器人添加进来做管理员,分配一个发消息的权限即可。

其他需要关注这个告警的人只要订阅这个频道就可以了。

先在频道里说一句话(不说话没有聊天记录),然后调用机器人的取聊天记录接口(当然需要在外面可以访问接口的机器上调用):

curl https://api.telegram.org/botxxxx:yyyy/getUpdates

其中的xxxx:yyyy就是前面那个机器人Token。这个接口返回一堆数据中找到以下:

..."channel_post":{"message_id":3,"sender_chat":{"id":-nnnn,"title":"xxxx_grafana_alert","type":"channel"}...

其中-nnnn就是频道的ID。

测试机器人API

用最简单的方法让机器人在频道里发一条消息:

curl "https://api.telegram.org/botxxxx:yyyy/sendMessage?chat_id=-nnnn&text=hello"

成功。现在就可以在频道里看到机器人发的这条hello了。

Grafana自带的Telegram通知渠道实际上也是这样调用的,只是国内调不通这个接口而已。

写一个Webhook发消息

用bottle写一个API:

@app.post("/tgbot_xxx")
def post_tgbot_xxx():
    try:
        params = dict(chat_id=-nnnn, text=str(request.json))
        logger.warning(str(params))
        res = requests.post("https://api.telegram.org/botxxxx:yyyy/sendMessage", data=params)
        res.raise_for_status()
        return "Ok"
    except:
        logger.error("Send message fail: %s", str(request.json), exc_info=True)
        return "Error"

然后测试一下:

curl -XPOST -d '{"hello": "world"}' https://yourdomain/tgbot_xxx

现在可以在频道里看到机器人发的这个JSON。

创建告警渠道

打开Grafana的Alerting-Notification Channels,创建一个新的渠道,取一个名字,选择webhook类型,url就是上面那个https://yourdomain/tgbot_xxx

Send test测试一下,可以在频道里收到这个JSON:

{'dashboardId': 1, 'evalMatches': [{'value': 100, 'metric': 'High value', 'tags': None}, {'value': 200, 'metric': 'Higher Value', 'tags': None}], 'imageUrl': 'https://grafana.com/assets/img/blog/mixed_styles.png', 'message': 'Someone is testing the alert notification within grafana.', 'orgId': 0, 'panelId': 1, 'ruleId': 0, 'ruleName': 'Test notification', 'ruleUrl': 'http://localhost:3000/', 'state': 'alerting', 'tags': {}, 'title': '[Alerting] Test notification'}

这个显示略不直观,所以加个模板渲染一下吧。这里以mako为例:

from mako.template import Template

tmpl = Template("""${ data['title'] }
        ${ data.get('message', '') }
        % for r in data['evalMatches']:
          % if r.get('tags'):
          ${ r['tags'].get('job', 'metric') } | ${ r['tags'].get('instance', '-') } : ${ r['value'] }
          % else:
          ${ r.get('metric', 'metric') } : ${ r['value'] }
          % endif
        % endfor""")
# 上面那个API里的参数改为:
params = dict(chat_id=-nnnn, text=tmpl.render(data=request.json))

这样改了以后,频道里收到的测试消息就变成这样了:

[Alerting] Test notification
Someone is testing the alert notification within grafana.
  High value : 100
  Higher Value : 200

配置告警规则

在Grafana的Dashboard里找到要监控的指标,点编辑,在Queries里找到相应的query,复制一个,禁止显示,把其中的参数部分去掉。

在Alert里创建一个告警,指定一个When条件,比如avg(),选择上面创建的那个没有参数的query(告警不支持参数),指定阈值。选择send to到前面创建的webhook渠道,message里输入消息文本。

退出保存即可。

当告警条件触发时,就会推送一条消息到webhook,然后通过机器人发送到频道里。告警恢复时也会发送一条。

推送到[go4pro.org]