Bosun 提供了自己的 Dashboard 页面,但是我们基本上都是用 LOG 模式,即告警不显示在页面上,而是如果该告警没有消除,则一直会操作 action (即会持续发送告警)。当时这么做的原因是,Bosun 的 Dashboard 页面,其实一般的告警接受者很少上去,界面也很简陋。
因此,想了一个办法,将 Bosun 的告警推给 Nagios 来做显示,毕竟有 Nagstamon 这个桌面显示工具还是会方便好多。
Bosun API 接口
我的想法是直接给 Bosun 提交一个检查的规则配置,Bosun 检查完后将有 warning 或者 critial 的项返回来,然后 nagios 那边就好处理了。
官方文档是这么说的:
/api/rule
Test execution for rules. Can execute at various times and intervals, output templates, and send test emails. Example a request for details.
Example a request for details ...
瞬间泪目。这也够懒的。。。
不过经过简单的打开浏览器调试者工具,在 Rule Editor 页面执行下 Test,就可以知道该 API 的参数了。
uri: /api/rule?alert=${your_alert_rule_name}
payload: 整个配置文本内容
用 Python 来举个例子,是这样的:
import requests
url = 'http://127.0.0.1:8070/api/rule?alert=host_load'
payload = '''
tsdbHost = localhost:4242
tsdbVersion = 2.2
smtpHost = smtp.163.com:25
emailFrom = [email protected]
template email {
subject = {{.Last.Status}}: {{.Alert.Name}}
}
notification email {
email = [email protected]
}
alert host_load {
template = email
warn = avg(q("sum:linux.loadavg_1_min{host=*}", "10m", "")) >= 3
warnNotification = email
crit = avg(q("sum:linux.loadavg_1_min{host=*}", "10m", "")) >= 5
critNotification = email
}
'''
print requests.get(url, data=payload)
提交的配置里,template 填个简单的即可,反正我们并不需要用到 Bosun 本身的告警功能。最关键的是查询的语句,还有产生 warning 和 critical 的条件。
之前在写 Bosun 的监控 Dashboard 时候,已经写过这个接口了。这里贴下关键部分:
将不变的文本抽离出来一个常量
var BaseConfig = `
tsdbHost = localhost:4242
tsdbVersion = 2.2
smtpHost = smtp.163.com:25
emailFrom = [email protected]
template email {
subject = {{.Last.Status}}: {{.Alert.Name}}
}
notification email {
email = [email protected]
}
`
生成规则配置的函数
func MakeRuleConfig(a string, r Rule) (c string) {
c = BaseConfig + fmt.Sprintf(`
alert %s {
template = email`, a)
if r.WarnValue != "" {
c = c + fmt.Sprintf(`
warn = %s %s %s
warnNotification = email
`, r.Condition, r.Op, r.WarnValue)
}
if r.CritValue != "" {
c = c + fmt.Sprintf(`
crit = %s %s %s
critNotification = email
`, r.Condition, r.Op, r.CritValue)
}
c = c + "}"
return
}
Rule 结构如下:
type Rule struct {
Condition string
Op string
WarnValue string
CritValue string
}
Condition 就是查询语句,如上面的
avg(q("sum:linux.loadavg_1_min{host=*}", "10m", ""))
Op 是 比较符,如 >=
<=
等
WarnValue 和 CritValue 则是告警阀值。
执行 Test 的函数
func RuleTester(alert, glob, config string) (r []RuleRet, err error) {
r = make([]RuleRet, 0)
url := BosunUrl + "/api/rule?alert=" + alert
data := bytes.NewReader([]byte(config))
resp, err := http.Post(url, "application/json", data)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
js, err := simplejson.NewJson(body)
if err != nil {
return
}
sets, _ := js.Get("Sets").Array()
for _, a := range sets {
b := a.(map[string]interface{})
c := b["Results"].([]interface{})
for _, d := range c {
e := d.(map[string]interface{})
f := e["Result"].(map[string]interface{})
s := f["Status"].(string)
var k string
k = strings.Replace(e["Group"].(string), alert+"{", "", -1)
globs := strings.Split(glob, ",")
for _, g := range globs {
k = strings.Replace(k, g, "", -1)
}
k = strings.Replace(k, "}", "", -1)
k = strings.Replace(k, ",", "", -1)
r = append(r, RuleRet{k, s})
}
}
return
}
主要是用 simplejson 将结果解析出来,glob 参数是用来去掉一些不必要的字符,因为我这边只输出像 192.168.10.10:critial
这样的结果,返回的 RuleRet 结构体:
type RuleRet struct {
Key string
Status string
}
执行脚本
package main
import (
"fmt"
"flag"
"bosunapi/api"
)
var (
a = flag.String("a", "", "alert name")
w = flag.String("w", "", "warning value")
c = flag.String("c", "", "critial value")
)
func main() {
flag.Parse()
alert, warn, crit := *a, *w, *c
var r api.Rule
switch alert {
case "host_cpu_used_percent":
r = api.Rule{"host=", `avg(q("sum:rate{counter,,1}:os.cpu{host=*}", "10m", ""))`, `>=`, warn, crit}
case "host_mem_free_percent":
r = api.Rule{"host=", `avg(q("sum:os.mem.percent_free{host=*}", "10m", ""))`, `<=`, warn, crit}
case "root_disk_free_percent":
r = api.Rule{"disk=/,host=", `avg(q("sum:os.disk.fs.percent_free{disk=/,host=*}", "10m", ""))`, `<=`, warn, crit}
case "data_disk_free_percent":
r = api.Rule{"disk=/data,host=", `avg(q("sum:os.disk.fs.percent_free{disk=/data,host=*}", "10m", ""))`, `<=`, warn, crit}
case "host_load":
r = api.Rule{"host=", `avg(q("sum:linux.loadavg_1_min{host=*}", "10m", ""))`, `>=`, warn, crit}
}
config := api.MakeRuleConfig(alert, r)
fmt.Println(config)
Data, _ := api.RuleTester(alert, r.Glob, config)
output := ""
for _, v := range Data {
if v.Status != "normal" {
if output != "" {
output = fmt.Sprintf("%s, %s:%s", output, v.Key, v.Status)
} else {
output = fmt.Sprintf("%s:%s", v.Key, v.Status)
}
}
}
fmt.Printf(output)
}
主要是自己定义好需要监控的规则,检查结果向 STDOUT 输出即可。
Nagios脚本
libexec 目录下添加一个简单的脚本
#!/bin/bash
Main() {
info=$(/data/sh/monitor/bosun_server_rule -a host_load -w 8 -c 10)
if /bin/grep -q 'warning' <<< "${info}"; then
echo "${info}"
exit 1
elif /bin/grep -q 'critial' <<< "${info}"; then
echo "${info}"
exit 2
else
echo "ok"
exit 0
fi
}
Main
这里抽离出来用 Shell 脚本写,主要是有时候需要调整下告警阀值。。
至于 Nagios 服务端和客服端方面的配置,网上都有,就不提了。
其他
- Nagios 最好设置下执行的频率,看集群的负载情况而定,我这边是设置了 10 分钟执行一次;
- 上面的设置,一个告警项需要加一个 Nagios 脚本,略蛋疼。