diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java new file mode 100644 index 00000000000..2970dcd63a0 --- /dev/null +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java @@ -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 + * Alibaba Cloud Monitor webhook payload fields + */ +@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 labels; + + private Map annotations; + + private AlertData data; + + private Map 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 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 prop; + } + + /** + * Threshold alert data. + */ + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class AlertData { + + private Object value; + + private Object threshold; + + private String comparisonOperator; + } +} diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java new file mode 100644 index 00000000000..3b032e9fa0c --- /dev/null +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java @@ -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 buildLabels(AlibabaCloudCmsExternAlert externAlert) { + Map 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 buildAnnotations(AlibabaCloudCmsExternAlert externAlert) { + Map 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 target, Map values) { + if (values == null || values.isEmpty()) { + return; + } + values.forEach((key, value) -> putValue(target, key, value)); + } + + private void putValue(Map 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 target, String key, String value) { + if (StringUtils.isNotBlank(value)) { + target.put(key, value); + } + } +} diff --git a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java new file mode 100644 index 00000000000..53c89d113db --- /dev/null +++ b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java @@ -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 captor = ArgumentCaptor.forClass(SingleAlert.class); + verify(alarmCommonReduce).reduceAndSendAlarm(captor.capture()); + return captor.getValue(); + } +} diff --git a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts index 57eab71d8f6..f4e539821d4 100644 --- a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts +++ b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts @@ -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'), diff --git a/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md new file mode 100644 index 00000000000..e36af233f7b --- /dev/null +++ b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md @@ -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) diff --git a/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md new file mode 100644 index 00000000000..72ba07b4e3d --- /dev/null +++ b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md @@ -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) diff --git a/web-app/src/assets/i18n/en-US.json b/web-app/src/assets/i18n/en-US.json index 369ca6600af..d90653bbcf6 100644 --- a/web-app/src/assets/i18n/en-US.json +++ b/web-app/src/assets/i18n/en-US.json @@ -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", diff --git a/web-app/src/assets/i18n/ja-JP.json b/web-app/src/assets/i18n/ja-JP.json index 41f9776a829..6abb46ef906 100644 --- a/web-app/src/assets/i18n/ja-JP.json +++ b/web-app/src/assets/i18n/ja-JP.json @@ -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": "火山エンジン監視", diff --git a/web-app/src/assets/i18n/ko-KR.json b/web-app/src/assets/i18n/ko-KR.json index 23145f738b9..34ee1f4b1ad 100644 --- a/web-app/src/assets/i18n/ko-KR.json +++ b/web-app/src/assets/i18n/ko-KR.json @@ -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", diff --git a/web-app/src/assets/i18n/pt-BR.json b/web-app/src/assets/i18n/pt-BR.json index e089b95d532..601f2bb9885 100644 --- a/web-app/src/assets/i18n/pt-BR.json +++ b/web-app/src/assets/i18n/pt-BR.json @@ -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", diff --git a/web-app/src/assets/i18n/zh-CN.json b/web-app/src/assets/i18n/zh-CN.json index d6397daa0f1..0eda95a3c0a 100644 --- a/web-app/src/assets/i18n/zh-CN.json +++ b/web-app/src/assets/i18n/zh-CN.json @@ -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": "火山引擎云监控", diff --git a/web-app/src/assets/i18n/zh-TW.json b/web-app/src/assets/i18n/zh-TW.json index 4cdbf1d2be7..8d66b3bd679 100644 --- a/web-app/src/assets/i18n/zh-TW.json +++ b/web-app/src/assets/i18n/zh-TW.json @@ -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": "火山引擎監控",