场景分析
我们需要对三个集群进行监控,分别是
- 开发-测试集群 : blackbox-dev
- 预发布集群 : blackbox-pre
- 生产集群 : blackbox-pro
Blackbox Exporter 配置
首先需要使用 BlackBox Exporter 实现一个探测 Module ,这个 Module 可以包含 HTTP、HTTPS、SSH 等方案,我们对目标进行监控的时候,可以直接使用 Prometheus 传入 Targets 以调用 Module
运行 Blackbox Exporter 时,需要用户提供探针的配置信息,这些配置信息可能是一些自定义的 HTTP 头信息,也可能是探测时需要的一些 TSL 配置,也可能是探针本身的验证行为,在 Blackbox Exporter 每一个探针配置称为一个 module,并且以 YAML 配置文件的形式提供给 Blackbox Exporter
如果我们想调用集群中的某个服务,必须先经过一个 API 网关,这个 API 网关兼备了访问控制的功能,每个用户需要在 Request 请求头中添加一个 Bearer Token —— 这取决于网关的具体配置,我们的探针也不例外,下面是 BlackBox Exporter 的配置示例
blackbox.yml
modules:
http_2xx:
prober: http
timeout: 5s
http:
preferred_ip_protocol: ip4
headers:
Host: "probe.xxx.com"
Cache-Control: no-cache
bearer_token: '<token>'
tcp_connect:
prober: tcp
timeout: 5s
tcp:
preferred_ip_protocol: ip4
icmp:
prober: icmp
timeout: 5s
icmp:
preferred_ip_protocol: ip4
http_2xx_home:
prober: http
timeout: 5s
http:
method: GET
fail_if_body_not_matches_regexp:
- "OK"
fail_if_not_ssl: false
preferred_ip_protocol: ip4
将服务启动后,我们访问 IP:9115 就能看见 WebUI 了
index
Prometheus
Prometheus 作为 Grafana 的数据源,也作为我们的信息处理与报警中心,主要的配置都在这里,因为我们只是对目前现有的方案做一个 BlackBox Exporter 配置的插入,所以只需要列出来关键配置就行,下面是 targets-dev.yml
,即测试服的 API 地址的配置示例,我们一共需要配置三个文件,文件除了不同环境的 API 地址,全部都相同
- targets:
- URL1
- URL2
targets 的目录结构
config/targets/
├── dev-targets.yml
├── pre-targets.yml
└── pro-targets.yml
配置完成探测目标之后,需要配置 Prometheus 本身了,我们调用 BlackBox Exporter 的方法其实是以 GET 的方式实现的,就像这样
http://<IP>:9115/probe?target=<URL>&module=http_2xx
因此我们的配置就变成了下面这样
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- /etc/prometheus/rules/*.rules
scrape_configs:
- job_name: "blackbox-dev"
metrics_path: /probe
scheme: http
params:
module: [http_2xx]
file_sd_configs:
- files:
- targets/dev-targets.yml
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox_exporter:9115
- job_name: "blackbox-pre"
metrics_path: /probe
scheme: http
params:
module: [http_2xx]
file_sd_configs:
- files:
- targets/pre-targets.yml
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox_exporter:9115
- job_name: "blackbox-pro"
metrics_path: /probe
scheme: http
params:
module: [http_2xx]
file_sd_configs:
- files:
- targets/pro-targets.yml
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox_exporter:9115
...
至此我们已经能实现对目标的探测和数据的搜集存储
Grafana 可视化
这个 Dashboard 的的示意图
AlertManger
我们在 AlertManager 这边直接将 Webhook URL 指向我们的转发器 dd2wx,因为 AlertManager 并不支持发送到企业微信群机器人,干脆自己实现一遍转发
global:
resolve_timeout: 5m
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'web.hook'
receivers:
- name: 'web.hook'
webhook_configs:
- url: <dd2wxURL>
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
接下来是警报规则文件
groups:
- name: service-availability
rules:
- alert: dev-service-unavailability
expr: avg_over_time(probe_success{job="blackbox-dev"}[3m]) * 100 < 95
for: 5s
annotations:
summary: 测试服出现了服务不可用情况
- alert: pre-service-unavailability
expr: avg_over_time(probe_success{job="blackbox-pre"}[3m]) * 100 < 95
for: 5s
annotations:
summary: 预发布出现了服务不可用情况
- alert: pro-service-unavailability
expr: avg_over_time(probe_success{job="blackbox-pro"}[3m]) * 100 < 95
for: 5s
annotations:
summary: 生产环境出现了服务不可用情况
这个文件需要放在 Prometheus 的 rules 目录下面,文件内容为:当出现可用性小于 95% 的情况达 5s 的时候触发警报
Prometheus 完整的配置结构
$ tree prometheus/config/ -a
prometheus/config/
├── prometheus.yml
├── prometheus.yml.bak
├── rules
│ └── blackbox.rules
└── targets
├── dev-targets.yml
├── pre-targets.yml
└── pro-targets.yml
2 directories, 6 files
DD2WX
这个是我自己写的一个转发器,将发过来的警报 Json 转化成 md 说明发送给企业微信机器人
模版 : alert_temp.tmpl
------------------------------
警报来了,共{{ .Count }}个实例
------------------------------
{{ range .AllInstance }}
状态: {{ .Status }}
告警名称: {{ .LabelsName }}
服务名称: {{ .Name }}
告警实例: {{ .Instance }}
告警时间: {{ .StartsAt }}
解决时间: {{ .EndsAt }}
说明: {{ .Description }}
{{ end }}
------------------------------
Json Models : Json.go
package main
import "time"
type promeAlerts struct {
Receiver string `json:"receiver"`
Status string `json:"status"`
Alerts []struct {
Status string `json:"status"`
Labels struct {
Alertname string `json:"alertname"`
Env string `json:"env"`
Instance string `json:"instance"`
Job string `json:"job"`
KubernetesName string `json:"kubernetes_name"`
KubernetesNamespace string `json:"kubernetes_namespace"`
KubernetesNode string `json:"kubernetes_node"`
Name string `json:"name"`
} `json:"labels"`
Annotations struct {
Summary string `json:"summary"`
} `json:"annotations"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
GeneratorURL string `json:"generatorURL"`
Fingerprint string `json:"fingerprint"`
} `json:"alerts"`
GroupLabels struct {
Alertname string `json:"alertname"`
} `json:"groupLabels"`
CommonLabels struct {
Alertname string `json:"alertname"`
} `json:"commonLabels"`
CommonAnnotations struct {
Summary string `json:"summary"`
} `json:"commonAnnotations"`
ExternalURL string `json:"externalURL"`
Version string `json:"version"`
GroupKey string `json:"groupKey"`
}
type wxAlert struct {
Msgtype string `json:"msgtype"`
Text struct {
Content string `json:"content"`
} `json:"text"`
}
main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"html/template"
"log"
"net/http"
"os"
"time"
)
var wxWebhook string
func init() {
wxWebhook = os.Getenv("WECHAT_BOOT_URL")
}
type Message struct {
Count int
AllInstance []noTep
}
type noTep struct {
LabelsName,
Name,
Instance,
StartsAt,
EndsAt,
Status,
Description string
}
func main() {
if err := startServe(); err != nil {
log.Fatal(err)
}
}
func startServe() error {
e := echo.New()
e.HideBanner = true
e.Use(middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) {
log.Println(string(reqBody))
}))
e.POST("/connect", recv)
e.GET("/connect", recv)
return e.Start(":8080")
}
func recv(c echo.Context) error {
u := &promeAlerts{}
err := c.Bind(u)
if err != nil {
log.Println(err)
return c.String(http.StatusOK, err.Error())
}
var msg wxAlert
err = fillAlert(*u, &msg)
if err != nil {
return c.String(http.StatusOK, err.Error())
}
fmt.Println(send2Wx(msg).Error())
return c.String(http.StatusOK, "")
}
func send2Wx(msg wxAlert) error {
sendSms, err := json.Marshal(msg)
if err != nil {
return err
}
resp, err := http.Post(wxWebhook, "application/json; charset=utf-8", bytes.NewBuffer(sendSms))
if err != nil {
return err
}
return fmt.Errorf("%d", resp.StatusCode)
}
func fillAlert(data promeAlerts, msg *wxAlert) error {
t := template.Must(template.ParseFiles("./alert_temp.tmpl"))
var doc bytes.Buffer
noMsg := make([]noTep, 0)
for _, v := range data.Alerts {
noMsg = append(noMsg, noTep{
LabelsName: v.Labels.Alertname,
Name: v.Labels.Name,
Instance: v.Labels.Instance,
Status: v.Status,
StartsAt: FormatTime(v.StartsAt),
EndsAt: FormatTime(v.EndsAt),
Description: v.Annotations.Summary,
})
}
err := t.Execute(&doc, Message{Count: len(noMsg), AllInstance: noMsg})
if err != nil {
return err
}
msg.Msgtype = "text"
msg.Text.Content = doc.String()
return nil
}
func FormatTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
参考
全部完成之后的效果