mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 09:40:58 +00:00
feat(alerter): support Alibaba Cloud Monitor webhook (#4296)
Co-authored-by: Duansg <siguoduan@gmail.com>
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud Monitor 2.0 webhook alert entity.
|
||||
*
|
||||
* @see <a href="https://help.aliyun.com/zh/cms/cloudmonitor-2-0/notification-object">
|
||||
* Alibaba Cloud Monitor webhook payload fields</a>
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AlibabaCloudCmsExternAlert {
|
||||
|
||||
private String specversion;
|
||||
|
||||
private String id;
|
||||
|
||||
private String type;
|
||||
|
||||
private String subtype;
|
||||
|
||||
private String source;
|
||||
|
||||
private String sourcetype;
|
||||
|
||||
private String time;
|
||||
|
||||
private Long timestamp;
|
||||
|
||||
private String subject;
|
||||
|
||||
private String datacontenttype;
|
||||
|
||||
private String severity;
|
||||
|
||||
private String status;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String ruleId;
|
||||
|
||||
private String workspace;
|
||||
|
||||
private String traceId;
|
||||
|
||||
private String alertMessage;
|
||||
|
||||
private String alertEntityId;
|
||||
|
||||
private Resource resource;
|
||||
|
||||
private Map<String, Object> labels;
|
||||
|
||||
private Map<String, Object> annotations;
|
||||
|
||||
private AlertData data;
|
||||
|
||||
private Map<String, Object> alertEntityFields;
|
||||
|
||||
private String ruleUrl;
|
||||
|
||||
private String entityUrl;
|
||||
|
||||
private String alertRuleUrl;
|
||||
|
||||
private String alertHistoryUrl;
|
||||
|
||||
/**
|
||||
* Alert resource.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Resource {
|
||||
|
||||
private Entity entity;
|
||||
|
||||
private Map<String, Object> tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert resource entity.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Entity {
|
||||
|
||||
private String domain;
|
||||
|
||||
@JsonAlias("entity_type")
|
||||
private String entityType;
|
||||
|
||||
@JsonAlias("entity_id")
|
||||
private String entityId;
|
||||
|
||||
private Map<String, Object> prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Threshold alert data.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class AlertData {
|
||||
|
||||
private Object value;
|
||||
|
||||
private Object threshold;
|
||||
|
||||
private String comparisonOperator;
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.AlibabaCloudCmsExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud Monitor 2.0 external alert service.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlibabaCloudCmsExternAlertService implements ExternAlertService {
|
||||
|
||||
private static final String SOURCE = "alibabacloud-cms";
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
public AlibabaCloudCmsExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
AlibabaCloudCmsExternAlert externAlert = JsonUtil.fromJson(content, AlibabaCloudCmsExternAlert.class);
|
||||
if (externAlert == null || StringUtils.isBlank(externAlert.getStatus())) {
|
||||
log.warn("Failed to parse Alibaba Cloud Monitor external alert content: {}", content);
|
||||
return;
|
||||
}
|
||||
alarmCommonReduce.reduceAndSendAlarm(convert(externAlert));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return SOURCE;
|
||||
}
|
||||
|
||||
private SingleAlert convert(AlibabaCloudCmsExternAlert externAlert) {
|
||||
boolean resolved = isResolved(externAlert);
|
||||
long eventTime = getEventTime(externAlert);
|
||||
return SingleAlert.builder()
|
||||
.content(getAlertContent(externAlert))
|
||||
.status(resolved ? CommonConstants.ALERT_STATUS_RESOLVED : CommonConstants.ALERT_STATUS_FIRING)
|
||||
.startAt(eventTime)
|
||||
.activeAt(resolved ? null : eventTime)
|
||||
.endAt(resolved ? eventTime : null)
|
||||
.labels(buildLabels(externAlert))
|
||||
.annotations(buildAnnotations(externAlert))
|
||||
.triggerTimes(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
private boolean isResolved(AlibabaCloudCmsExternAlert externAlert) {
|
||||
return "RESOLVED".equalsIgnoreCase(externAlert.getStatus())
|
||||
|| "RECOVERED".equalsIgnoreCase(externAlert.getStatus())
|
||||
|| "NORMAL_RESOLVE".equalsIgnoreCase(externAlert.getSubtype());
|
||||
}
|
||||
|
||||
private long getEventTime(AlibabaCloudCmsExternAlert externAlert) {
|
||||
if (externAlert.getTimestamp() != null && externAlert.getTimestamp() > 0) {
|
||||
return externAlert.getTimestamp();
|
||||
}
|
||||
if (StringUtils.isNotBlank(externAlert.getTime())) {
|
||||
try {
|
||||
return Instant.parse(externAlert.getTime()).toEpochMilli();
|
||||
} catch (DateTimeParseException e) {
|
||||
log.warn("Failed to parse Alibaba Cloud Monitor event time: {}", externAlert.getTime());
|
||||
}
|
||||
}
|
||||
return Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
private Map<String, String> buildLabels(AlibabaCloudCmsExternAlert externAlert) {
|
||||
Map<String, String> labels = new HashMap<>(16);
|
||||
putValues(labels, externAlert.getLabels());
|
||||
AlibabaCloudCmsExternAlert.Resource resource = externAlert.getResource();
|
||||
if (resource != null) {
|
||||
putValues(labels, resource.getTags());
|
||||
AlibabaCloudCmsExternAlert.Entity entity = resource.getEntity();
|
||||
if (entity != null) {
|
||||
putIfNotBlank(labels, "resourceDomain", entity.getDomain());
|
||||
putIfNotBlank(labels, "resourceType", entity.getEntityType());
|
||||
putIfNotBlank(labels, "resourceId", entity.getEntityId());
|
||||
}
|
||||
}
|
||||
labels.put("__source__", SOURCE);
|
||||
putIfNotBlank(labels, CommonConstants.LABEL_ALERT_NAME, externAlert.getSubject());
|
||||
putIfNotBlank(labels, CommonConstants.LABEL_ALERT_SEVERITY, convertSeverity(externAlert.getSeverity()));
|
||||
putIfNotBlank(labels, "ruleId", externAlert.getRuleId());
|
||||
putIfNotBlank(labels, "workspace", externAlert.getWorkspace());
|
||||
putIfNotBlank(labels, "alertEntityId", externAlert.getAlertEntityId());
|
||||
putIfNotBlank(labels, "userId", externAlert.getUserId());
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Map<String, String> buildAnnotations(AlibabaCloudCmsExternAlert externAlert) {
|
||||
Map<String, String> annotations = new HashMap<>(16);
|
||||
putValues(annotations, externAlert.getAnnotations());
|
||||
AlibabaCloudCmsExternAlert.Resource resource = externAlert.getResource();
|
||||
if (resource != null && resource.getEntity() != null) {
|
||||
putValues(annotations, resource.getEntity().getProp());
|
||||
}
|
||||
putValues(annotations, externAlert.getAlertEntityFields());
|
||||
AlibabaCloudCmsExternAlert.AlertData data = externAlert.getData();
|
||||
if (data != null) {
|
||||
putValue(annotations, "value", data.getValue());
|
||||
putValue(annotations, "threshold", data.getThreshold());
|
||||
putIfNotBlank(annotations, "comparisonOperator", data.getComparisonOperator());
|
||||
}
|
||||
putIfNotBlank(annotations, "alertMessage", externAlert.getAlertMessage());
|
||||
putIfNotBlank(annotations, "traceId", externAlert.getTraceId());
|
||||
putIfNotBlank(annotations, "ruleUrl", externAlert.getRuleUrl());
|
||||
putIfNotBlank(annotations, "entityUrl", externAlert.getEntityUrl());
|
||||
putIfNotBlank(annotations, "alertRuleUrl", externAlert.getAlertRuleUrl());
|
||||
putIfNotBlank(annotations, "alertHistoryUrl", externAlert.getAlertHistoryUrl());
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private String getAlertContent(AlibabaCloudCmsExternAlert externAlert) {
|
||||
if (StringUtils.isNotBlank(externAlert.getAlertMessage())) {
|
||||
return externAlert.getAlertMessage();
|
||||
}
|
||||
if (StringUtils.isNotBlank(externAlert.getSubject())) {
|
||||
return externAlert.getSubject();
|
||||
}
|
||||
return "Alibaba Cloud Monitor alert";
|
||||
}
|
||||
|
||||
private String convertSeverity(String severity) {
|
||||
if (StringUtils.isBlank(severity)) {
|
||||
return null;
|
||||
}
|
||||
return switch (severity.toUpperCase(Locale.ROOT)) {
|
||||
case "EMERGENCY" -> CommonConstants.ALERT_SEVERITY_EMERGENCY;
|
||||
case "CRITICAL" -> CommonConstants.ALERT_SEVERITY_CRITICAL;
|
||||
case "WARN", "WARNING" -> CommonConstants.ALERT_SEVERITY_WARNING;
|
||||
case "INFO", "INFORMATIONAL" -> CommonConstants.ALERT_SEVERITY_INFO;
|
||||
default -> severity.toLowerCase(Locale.ROOT);
|
||||
};
|
||||
}
|
||||
|
||||
private void putValues(Map<String, String> target, Map<String, Object> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
values.forEach((key, value) -> putValue(target, key, value));
|
||||
}
|
||||
|
||||
private void putValue(Map<String, String> target, String key, Object value) {
|
||||
if (StringUtils.isBlank(key) || value == null) {
|
||||
return;
|
||||
}
|
||||
String stringValue;
|
||||
if (value instanceof Map<?, ?> || value instanceof Collection<?>) {
|
||||
stringValue = JsonUtil.toJson(value);
|
||||
} else {
|
||||
stringValue = String.valueOf(value);
|
||||
}
|
||||
putIfNotBlank(target, key, stringValue);
|
||||
}
|
||||
|
||||
private void putIfNotBlank(Map<String, String> target, String key, String value) {
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import java.time.Instant;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaCloudCmsExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Unit test for {@link AlibabaCloudCmsExternAlertService}.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AlibabaCloudCmsExternAlertServiceTest {
|
||||
|
||||
private static final long EVENT_TIME = 1785300000123L;
|
||||
|
||||
@Mock
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
private AlibabaCloudCmsExternAlertService externAlertService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
externAlertService = new AlibabaCloudCmsExternAlertService(alarmCommonReduce);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertTriggeredAlert() {
|
||||
externAlertService.addExternAlert("""
|
||||
{
|
||||
"specversion": "1.0",
|
||||
"id": "alert-event-1",
|
||||
"type": "ALERT",
|
||||
"subtype": "NORMAL_TRIGGER",
|
||||
"time": "2026-07-29T06:00:00Z",
|
||||
"timestamp": 1785300000123,
|
||||
"subject": "ECS CPU usage is high",
|
||||
"severity": "WARNING",
|
||||
"status": "OCCURRED",
|
||||
"userId": "123456",
|
||||
"ruleId": "rule-1",
|
||||
"workspace": "default-cms-123456-cn-hangzhou",
|
||||
"traceId": "trace-1",
|
||||
"alertMessage": "CPU usage exceeded 80%",
|
||||
"alertEntityId": "ecs:i-123",
|
||||
"resource": {
|
||||
"entity": {
|
||||
"domain": "ecs",
|
||||
"entity_type": "instance",
|
||||
"entity_id": "i-123",
|
||||
"prop": {
|
||||
"instanceName": "api-server"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"regionId": "cn-hangzhou",
|
||||
"environment": "production"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"_cms_region": "cn-hangzhou",
|
||||
"customNumber": 7
|
||||
},
|
||||
"annotations": {
|
||||
"current_value": "92.5"
|
||||
},
|
||||
"data": {
|
||||
"value": 92.5,
|
||||
"threshold": 80,
|
||||
"comparisonOperator": ">"
|
||||
},
|
||||
"alertEntityFields": {
|
||||
"privateIp": "10.0.0.1"
|
||||
},
|
||||
"alertHistoryUrl": "https://cmsnext.console.aliyun.com/history",
|
||||
"futureField": "ignored"
|
||||
}
|
||||
""");
|
||||
|
||||
SingleAlert alert = captureAlert();
|
||||
assertEquals(CommonConstants.ALERT_STATUS_FIRING, alert.getStatus());
|
||||
assertEquals(EVENT_TIME, alert.getStartAt());
|
||||
assertEquals(EVENT_TIME, alert.getActiveAt());
|
||||
assertNull(alert.getEndAt());
|
||||
assertEquals("CPU usage exceeded 80%", alert.getContent());
|
||||
assertEquals("alibabacloud-cms", alert.getLabels().get("__source__"));
|
||||
assertEquals("ECS CPU usage is high", alert.getLabels().get("alertname"));
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_WARNING, alert.getLabels().get("severity"));
|
||||
assertEquals("instance", alert.getLabels().get("resourceType"));
|
||||
assertEquals("i-123", alert.getLabels().get("resourceId"));
|
||||
assertEquals("7", alert.getLabels().get("customNumber"));
|
||||
assertEquals("api-server", alert.getAnnotations().get("instanceName"));
|
||||
assertEquals("92.5", alert.getAnnotations().get("value"));
|
||||
assertEquals("80", alert.getAnnotations().get("threshold"));
|
||||
assertEquals("10.0.0.1", alert.getAnnotations().get("privateIp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertResolvedAlertAndIsoTime() {
|
||||
externAlertService.addExternAlert("""
|
||||
{
|
||||
"subtype": "NORMAL_RESOLVE",
|
||||
"time": "2026-07-29T06:00:00Z",
|
||||
"subject": "ECS CPU usage is high",
|
||||
"severity": "CRITICAL",
|
||||
"status": "RESOLVED",
|
||||
"labels": {
|
||||
"instanceId": "i-123"
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
SingleAlert alert = captureAlert();
|
||||
long expectedTime = Instant.parse("2026-07-29T06:00:00Z").toEpochMilli();
|
||||
assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, alert.getStatus());
|
||||
assertEquals(expectedTime, alert.getStartAt());
|
||||
assertNull(alert.getActiveAt());
|
||||
assertEquals(expectedTime, alert.getEndAt());
|
||||
assertEquals("ECS CPU usage is high", alert.getContent());
|
||||
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL, alert.getLabels().get("severity"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatRecoveredStatusAsResolved() {
|
||||
externAlertService.addExternAlert("""
|
||||
{
|
||||
"timestamp": 1785300000123,
|
||||
"subject": "Recovered alert",
|
||||
"status": "RECOVERED"
|
||||
}
|
||||
""");
|
||||
|
||||
SingleAlert alert = captureAlert();
|
||||
assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, alert.getStatus());
|
||||
assertEquals(EVENT_TIME, alert.getEndAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreInvalidPayload() {
|
||||
externAlertService.addExternAlert("invalid json");
|
||||
externAlertService.addExternAlert("{\"subject\":\"missing status\"}");
|
||||
|
||||
verify(alarmCommonReduce, never()).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
assertEquals("alibabacloud-cms", externAlertService.supportSource());
|
||||
}
|
||||
|
||||
private SingleAlert captureAlert() {
|
||||
ArgumentCaptor<SingleAlert> captor = ArgumentCaptor.forClass(SingleAlert.class);
|
||||
verify(alarmCommonReduce).reduceAndSendAlarm(captor.capture());
|
||||
return captor.getValue();
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,11 @@ export class AlertIntegrationComponent implements OnInit {
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
|
||||
icon: 'assets/img/integration/tencent.svg'
|
||||
},
|
||||
{
|
||||
id: 'alibabacloud-cms',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.alibabacloud-cms'),
|
||||
icon: 'assets/img/integration/alibabacloud.svg'
|
||||
},
|
||||
{
|
||||
id: 'alibabacloud-sls',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.alibabacloud-sls'),
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
> Send Alibaba Cloud Monitor 2.0 alerts to the HertzBeat alert platform through a webhook.
|
||||
|
||||
### Prepare a HertzBeat API token
|
||||
|
||||
1. Click **Manage API Tokens** in the upper-right corner of this page.
|
||||
2. Create a token and save it securely. The complete token is displayed only once.
|
||||
|
||||
### Create an Alibaba Cloud Monitor webhook
|
||||
|
||||
1. Log on to the [Alibaba Cloud Monitor 2.0 console](https://cmsnext.console.aliyun.com/).
|
||||
2. Select or create the target workspace, then go to **Alert Center** > **Notification Management** > **Notification Objects**.
|
||||
3. Open the **Custom Webhook** tab and click **Create Webhook**.
|
||||
4. Configure the webhook:
|
||||
- Name: `HertzBeat`
|
||||
- Identifier: for example, `hertzbeat`
|
||||
- URL:
|
||||
|
||||
```text
|
||||
http://{hertzbeat_host}:1157/api/alerts/report/alibabacloud-cms
|
||||
```
|
||||
|
||||
- Headers: add `Authorization` with the value `Bearer {token}`
|
||||
- Method: `POST`
|
||||
- Data format: `JSON`
|
||||
- Language: select as needed
|
||||
5. Save the webhook.
|
||||
|
||||
> Use the notification objects in an Alibaba Cloud Monitor 2.0 workspace. Notification objects in Prometheus Monitoring or ARMS Alert Management use a different webhook format and are not supported by this integration.
|
||||
|
||||
> `{hertzbeat_host}` must be publicly reachable from Alibaba Cloud Monitor. Expose this endpoint through an HTTPS reverse proxy in production.
|
||||
|
||||
### Bind an alert rule
|
||||
|
||||
1. Go to **Alert Center** > **Alert Management** > **Alert Rules**.
|
||||
2. Create or edit an alert rule.
|
||||
3. Select the HertzBeat custom webhook in the alert notification settings.
|
||||
4. Enable recovery notifications if alerts should be resolved automatically in HertzBeat.
|
||||
5. Save and enable the alert rule.
|
||||
|
||||
### Field mapping
|
||||
|
||||
| Alibaba Cloud Monitor field | HertzBeat field |
|
||||
| --- | --- |
|
||||
| `status: OCCURRED/PERSISTENT` | `status: firing` |
|
||||
| `status: RESOLVED/RECOVERED` | `status: resolved` |
|
||||
| `subject` | `labels.alertname` |
|
||||
| `severity` | `labels.severity` |
|
||||
| `alertMessage` | Alert content |
|
||||
| `labels`, `resource.tags` | Alert labels |
|
||||
| `annotations`, threshold, and resource properties | Alert annotations |
|
||||
| `timestamp` or `time` | Alert time |
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### The webhook returns 401 or 403
|
||||
|
||||
- Make sure the HertzBeat API token is active.
|
||||
- Make sure the header is named `Authorization` and its value starts with `Bearer `.
|
||||
|
||||
#### HertzBeat does not receive an alert
|
||||
|
||||
- Make sure the webhook URL and port are publicly reachable.
|
||||
- Make sure the data format is `JSON` and the request method is `POST`.
|
||||
- Check Alibaba Cloud Monitor alert history and the HertzBeat service logs.
|
||||
- If source IP allowlisting is enabled, use the latest CIDR list in the Alibaba Cloud documentation.
|
||||
|
||||
#### An alert is not resolved automatically
|
||||
|
||||
- Make sure recovery notifications are enabled in the alert rule or notification policy.
|
||||
- Make sure the recovery webhook contains a `status` of `RESOLVED` or `RECOVERED`.
|
||||
|
||||
For more information:
|
||||
|
||||
- [Alibaba Cloud Monitor notification objects and webhook fields](https://help.aliyun.com/en/cms/cloudmonitor-2-0/notification-object)
|
||||
- [Alibaba Cloud Monitor alert rules](https://help.aliyun.com/en/cms/cloudmonitor-2-0/alert-rules-cms-2-0)
|
||||
@@ -0,0 +1,75 @@
|
||||
> 将阿里云云监控 2.0 的告警通过 Webhook 发送到 HertzBeat 告警平台。
|
||||
|
||||
### 准备 HertzBeat API Token
|
||||
|
||||
1. 单击页面右上角的 **管理 API Token**。
|
||||
2. 创建一个 Token 并立即妥善保存。Token 只会完整显示一次。
|
||||
|
||||
### 创建阿里云云监控 Webhook
|
||||
|
||||
1. 登录 [阿里云云监控 2.0 控制台](https://cmsnext.console.aliyun.com/)。
|
||||
2. 选择或创建目标工作空间,然后进入 **告警中心** > **通知管理** > **通知对象**。
|
||||
3. 选择 **自定义 Webhook** 页签,单击 **新建 Webhook**。
|
||||
4. 填写 Webhook 配置:
|
||||
- 名称:`HertzBeat`
|
||||
- 标识符:例如 `hertzbeat`
|
||||
- URL:
|
||||
|
||||
```text
|
||||
http://{hertzbeat_host}:1157/api/alerts/report/alibabacloud-cms
|
||||
```
|
||||
|
||||
- Headers:添加 `Authorization`,值为 `Bearer {token}`
|
||||
- Method:`POST`
|
||||
- 数据格式:`JSON`
|
||||
- 语言:按需选择
|
||||
5. 保存 Webhook。
|
||||
|
||||
> 请使用云监控 2.0 工作空间内的通知对象。Prometheus 监控或 ARMS 告警管理中的通知对象使用不同的 Webhook 格式,不适用于此集成。
|
||||
|
||||
> `{hertzbeat_host}` 必须是阿里云云监控可以访问的公网地址。生产环境建议通过 HTTPS 反向代理暴露此接口。
|
||||
|
||||
### 绑定告警规则
|
||||
|
||||
1. 进入 **告警中心** > **告警管理** > **告警规则**。
|
||||
2. 创建或编辑告警规则。
|
||||
3. 在告警通知中选择上一步创建的 HertzBeat 自定义 Webhook。
|
||||
4. 如需在 HertzBeat 中自动恢复告警,请同时启用恢复通知。
|
||||
5. 保存并启用告警规则。
|
||||
|
||||
### 字段映射
|
||||
|
||||
| 阿里云云监控字段 | HertzBeat 字段 |
|
||||
| --- | --- |
|
||||
| `status: OCCURRED/PERSISTENT` | `status: firing` |
|
||||
| `status: RESOLVED/RECOVERED` | `status: resolved` |
|
||||
| `subject` | `labels.alertname` |
|
||||
| `severity` | `labels.severity` |
|
||||
| `alertMessage` | 告警内容 |
|
||||
| `labels`、`resource.tags` | 告警标签 |
|
||||
| `annotations`、阈值和资源属性 | 告警注解 |
|
||||
| `timestamp` 或 `time` | 告警时间 |
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 返回 401 或 403
|
||||
|
||||
- 确认已创建有效的 HertzBeat API Token。
|
||||
- 确认 Webhook Header 名称为 `Authorization`,值以 `Bearer ` 开头。
|
||||
|
||||
#### HertzBeat 未收到告警
|
||||
|
||||
- 确认 Webhook URL 可从公网访问,并且端口已放行。
|
||||
- 确认数据格式选择为 `JSON`,请求方法选择为 `POST`。
|
||||
- 检查阿里云云监控的告警历史以及 HertzBeat 服务日志。
|
||||
- 若配置了来源 IP 白名单,请以阿里云官方文档中的最新地址段为准。
|
||||
|
||||
#### 告警没有自动恢复
|
||||
|
||||
- 确认告警规则或通知策略已启用恢复通知。
|
||||
- 确认恢复 Webhook 中的 `status` 为 `RESOLVED` 或 `RECOVERED`。
|
||||
|
||||
更多信息请参考:
|
||||
|
||||
- [阿里云云监控通知对象与 Webhook 字段](https://help.aliyun.com/zh/cms/cloudmonitor-2-0/notification-object)
|
||||
- [阿里云云监控告警规则](https://help.aliyun.com/zh/cms/cloudmonitor-2-0/alert-rules-cms-2-0)
|
||||
@@ -100,6 +100,7 @@
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
|
||||
"alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
|
||||
"alert.integration.source.volcengine": "Volcengine Monitoring",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
|
||||
"alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
|
||||
"alert.integration.source.volcengine": "火山エンジン監視",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
|
||||
"alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
|
||||
"alert.integration.source.volcengine": "Volcengine Monitoring",
|
||||
|
||||
@@ -251,6 +251,7 @@
|
||||
"alert.integration.source.prometheus": "Prometheus",
|
||||
"alert.integration.source.tencent": "Monitoramento de nuvem Tencent",
|
||||
"alert.integration.source.webhook": "PadrãoWebhook",
|
||||
"alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
|
||||
"alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
|
||||
"alert.integration.source.volcengine": "Volcengine",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.source.alibabacloud-cms": "阿里云云监控",
|
||||
"alert.integration.source.alibabacloud-sls": "阿里云日志服务 SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "华为云监控服务",
|
||||
"alert.integration.source.volcengine": "火山引擎云监控",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.source.alibabacloud-cms": "阿里雲雲監控",
|
||||
"alert.integration.source.alibabacloud-sls": "阿里雲端日誌服務 SLS",
|
||||
"alert.integration.source.huaweicloud-ces": "華為雲監控服務",
|
||||
"alert.integration.source.volcengine": "火山引擎監控",
|
||||
|
||||
Reference in New Issue
Block a user