zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Grafana 告警接入飞书通知

2023-02-25 18:19:04 时间

Grafana 系列文章,版本:OOS v9.3.1

  1. Grafana 的介绍和安装
  2. Grafana监控大屏配置参数介绍(一)
  3. Grafana监控大屏配置参数介绍(二)
  4. Grafana监控大屏可视化图表
  5. Grafana 查询数据和转换数据
  6. Grafana 告警模块介绍
  7. Grafana 告警接入飞书通知

前言

我们已经知道Grafana 已经支持了钉钉、邮件等告警,钉钉能够打入Grafana 跟阿里重视开源也有关系,在Grafana的GitHub上,很早也有人提过这样的PR。

他们希望Grafana能够集成飞书,但被Grafana团队拒绝了,原因是:当时飞书通知的需求不够强烈。

不过没事,没有原生集成,我们还可以使用万能的webHook,通过中转服务内部转换参数后再发送到飞书。

创建告警规则

为了测试,数据源使用Grafana 的Test DB,新创建一个规则。

从Random Walk 类型的图表中查询数据,当数据最大值大于10的时候,发生告警,每1分钟评估一次,如果持续2分钟都符合告警条件,将生成一条告警。

这里表示告警关联的大屏和图表面板,还有一个描述字段

配置WebHook通知

这里为了测试,只填写一个接收地址,没有鉴权,外网环境必须要加鉴权

另外由于Grafana 使用Docker 部署,中转服务部署在本地物理机,如果填127.0.0.1,Docker 内部是无法访问到的,需要填物理机的局域网IP

配置告警策略

最后需要配置一个告警的策略,这边为了测试,时间都配的比较短,Mute timings 是不告警时间配置,可以不设。

部署中转服务

如下这段是中转服务接收到的webHook告警内容。

{
	"receiver": "feishu",
	"status": "resolved",
	"alerts": [{
		"status": "resolved",
		"labels": {
			"alertname": "Simple dummy streaming example",
			"grafana_folder": "test"
		},
		"annotations": {
			"description": "测试大屏告警"
		},
		"startsAt": "2023-01-12T14:46:00Z",
		"endsAt": "2023-01-12T15:07:00Z",
		"generatorURL": "http://localhost:3000/alerting/grafana/zgbSUF24z/view?orgId=1",
		"fingerprint": "09b095c0ac5a31bc",
		"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DSimple+dummy+streaming+example\u0026matcher=grafana_folder%3Dtest",
		"dashboardURL": "http://localhost:3000/d/TXSTREZ?orgId=1",
		"panelURL": "http://localhost:3000/d/TXSTREZ?orgId=1\u0026viewPanel=2",
		"values": null,
		"valueString": ""
	}],
	"groupLabels": {
		"alertname": "Simple dummy streaming example"
	},
	"commonLabels": {
		"alertname": "Simple dummy streaming example",
		"grafana_folder": "test"
	},
	"commonAnnotations": {
		"description": "测试大屏告警"
	},
	"externalURL": "http://localhost:3000/",
	"version": "1",
	"groupKey": "{}/{}:{alertname=\"Simple dummy streaming example\"}",
	"truncatedAlerts": 0,
	"orgId": 1,
	"title": "[RESOLVED] Simple dummy streaming example (test)",
	"state": "ok",
	"message": "**Resolved**\n\nValue: [no value]\nLabels:\n - alertname = Simple dummy streaming example\n - grafana_folder = test\nAnnotations:\n - description = 测试大屏告警\nSource: http://localhost:3000/alerting/grafana/zgbSUF24z/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DSimple+dummy+streaming+example\u0026matcher=grafana_folder%3Dtest\nDashboard: http://localhost:3000/d/TXSTREZ?orgId=1\nPanel: http://localhost:3000/d/TXSTREZ?orgId=1\u0026viewPanel=2\n"
}

上面的字段也很好理解,我们将上面的字段解析封装成飞书的通知格式就可以往飞书发送通知了。

我们根据之前的代码,简单调整封装一下。

AlarmMessage.java 告警接口

public interface AlarmMessage {

    /**
     * 发送文本告警
     * @param content
     */
    void sendData(String content);
}

FeiShuNotifier.java 飞书发送告警代码

@Service
public class FeiShuNotifier implements AlarmMessage {

	private static final String DEFAULT_MESSAGE = " 告警标题:#{title} \n 告警描述:#{description} \n 面板URL:#{panelUrl} \n 发生时间:#{startTime}";

	private final SpelExpressionParser parser = new SpelExpressionParser();

	@Value("${feishu.webhook-url}")
	private String webhookUrl;

	@Value("${feishu.secret}")
	private String secret;

	private Expression message = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);

	private RestTemplate restTemplate = new RestTemplate();

	@Override
	public void sendData(String content) {
		Map<String, Object> message = createMessage(getText(content));
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		restTemplate.postForEntity(webhookUrl, new HttpEntity<>(message, headers), Void.class);
	}


	/**
	 * 构建通知报文
	 * @param content
	 * @return
	 */
	protected Map<String, Object> createMessage(String content) {
		Map<String, Object> messageJson = new HashMap<>();
		messageJson.put("msg_type", "text");

		Map<String, Object> text = new HashMap<>();
		text.put("text", content);
		messageJson.put("content", text);
		Long timestamp = System.currentTimeMillis() / 1000;
		messageJson.put("timestamp", timestamp);
		messageJson.put("sign", getSign(timestamp));

		return messageJson;
	}

	/**
	 * 构建文本内容
	 * @param body
	 * @return
	 */
	private String getText(String body) {
		JSONObject json = JSON.parseObject(body);
		Map<String, Object> root = new HashMap<>();
		root.put("title", json.getString("title"));
		root.put("description", json.getJSONObject("commonAnnotations").getString("description"));
		JSONObject alertObject = json.getJSONArray("alerts").getJSONObject(0);
		root.put("panelUrl", alertObject.getString("panelURL"));
		root.put("startTime", DateUtil.parseUTC(alertObject.getString("startsAt")).toString(TimeZone.getDefault()));
		StandardEvaluationContext context = new StandardEvaluationContext(root);
		context.addPropertyAccessor(new MapAccessor());
		return message.getValue(context, String.class);
	}

	private String getSign(Long timestamp) {
		try {
			String stringToSign = timestamp + "\n" + secret;
			Mac mac = Mac.getInstance("HmacSHA256");
			mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
			byte[] signData = mac.doFinal(new byte[]{});
			return new String(Base64.encodeBase64(signData, false));
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		return "";
	}

}

webhook 接口代码

@RestController
public class WebhookController {

    @Resource
    private FeiShuNotifier feiShuNotifier;

    @RequestMapping("/webhook/receive")
    public void receive(@RequestBody String message) {
        feiShuNotifier.sendData(message);
    }
}

下面为飞书接收到的告警:

另外如果还需要接入企业微信等其他聊天工具,可以使用开源的PrometheusAlert,使用方案见其GitHub。 https://github.com/feiyu563/PrometheusAlert

作者其他文章:

  1. Spring Boot Admin 参考指南
  2. SpringBoot Admin服务离线、不显示健康信息的问题
  3. Spring Boot Admin2 @EnableAdminServer的加载
  4. Spring Boot Admin2 AdminServerAutoConfiguration详解
  5. Spring Boot Admin2 实例状态监控详解
  6. Spring Boot Admin2 自定义JVM监控通知
  7. Spring Boot Admin2 自定义异常监控
  8. Spring Boot Admin 监控指标接入Grafana可视化