在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Prometheus这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


Prometheus - 自定义告警模板开发:个性化告警内容与格式设计 🚨

在现代云原生架构中,监控系统已成为保障服务稳定性的核心组件。Prometheus 作为 CNCF 毕业项目,凭借其强大的数据模型、灵活的查询语言和活跃的生态系统,已成为事实上的监控标准。然而,开箱即用的告警通知往往过于通用,难以满足团队对告警信息精准传达的需求。

本文将深入探讨如何通过自定义告警模板(Alert Templates)来实现个性化的告警内容与格式设计,帮助运维和开发团队快速定位问题、提高响应效率,并减少误报带来的干扰。


为什么需要自定义告警模板?🤔

默认情况下,Prometheus Alertmanager 发送的告警信息包含基本字段如 alertnameinstanceseverity 等,但缺乏上下文、业务语义或操作指引。例如:

[FIRING:1] HighCPUUsage (critical)
Instance: 10.0.1.5:9100

这样的告警信息虽然指出了问题,但无法回答以下关键问题:

  • 这个实例属于哪个服务?
  • CPU 高负载持续了多久?
  • 是否有相关日志或指标图表可参考?
  • 应该联系谁处理?
  • 是否已有自动化恢复措施?

通过自定义模板,我们可以将这些上下文信息嵌入告警消息中,使其成为可操作的洞察,而不仅仅是“噪音”。

💡 最佳实践:好的告警应具备 Specific(具体)、Actionable(可操作)、Relevant(相关) 三大特性。


Alertmanager 模板机制基础 🔧

Alertmanager 使用 Go 的 text/templatehtml/template 包进行消息渲染。模板文件通常以 .tmpl 结尾,支持变量、条件判断、函数调用等逻辑。

模板结构概览

Alertmanager 的告警数据以如下结构传递给模板:

type Data struct {
	Receiver string          // 接收器名称
	Status   string          // "firing" 或 "resolved"
	Alerts   Alerts          // 告警列表
	GroupLabels map[string]string // 分组标签
	CommonLabels map[string]string // 所有告警共有的标签
	CommonAnnotations map[string]string // 共有注解
	ExternalURL string       // Alertmanager 外部 URL
}

每个 Alert 对象包含:

type Alert struct {
	Status       string            // "firing" 或 "resolved"
	Labels       map[string]string // 标签(如 job, instance, severity)
	Annotations  map[string]string // 注解(如 summary, description)
	StartsAt     time.Time         // 触发时间
	EndsAt       time.Time         // 解决时间(仅 resolved 状态)
	GeneratorURL string            // Prometheus 查询链接
}

📌 注意:模板中可通过 .Alerts 遍历所有告警,或使用 .CommonLabels 获取共性信息。


编写第一个自定义模板 🎯

假设我们希望在企业微信或 Slack 中收到如下格式的告警:

🚨 [FIRING] 高 CPU 使用率
服务: user-service
实例: 10.0.1.5:9100
严重性: critical
持续时间: 5m23s
描述: 该实例 CPU 使用率超过 90%,可能影响用户请求响应。
建议: 检查是否有异常进程或扩容实例。
查看图表: <Prometheus 链接>

步骤 1:定义模板文件

创建 custom.tmpl 文件:

{{ define "custom.text" }}
{{ range .Alerts }}
{{ if eq .Status "firing" }}🚨{{ else }}✅{{ end }} [{{ .Status | toUpper }}] {{ .Annotations.summary }}
服务: {{ .Labels.service | default "unknown" }}
实例: {{ .Labels.instance }}
严重性: {{ .Labels.severity }}
持续时间: {{ (.StartsAt | since) }}
描述: {{ .Annotations.description }}
建议: {{ .Annotations.runbook_url | default "请联系 SRE 团队" }}
查看图表: {{ .GeneratorURL }}
{{ end }}
{{ end }}

步骤 2:在 Alertmanager 配置中引用

# alertmanager.yml
templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  receiver: 'wechat'

receivers:
  - name: 'wechat'
    wechat_configs:
      - send_resolved: true
        to_user: '@all'
        message: '{{ template "custom.text" . }}'

关键函数说明

  • {{ range .Alerts }}:遍历所有告警(即使只有一条)
  • {{ if eq .Status "firing" }}:条件判断状态
  • {{ .Labels.service | default "unknown" }}:若标签不存在则返回默认值
  • {{ .StartsAt | since }}:计算从触发至今的时间差(需自定义函数)

⚠️ 注意:since 并非内置函数,需在 Alertmanager 启动时注册。下文将介绍如何扩展模板函数。


深入模板语法:变量、函数与控制流 🧩

1. 访问标签与注解

标签(Labels)用于标识告警来源,注解(Annotations)用于提供人类可读信息。

# prometheus_rules.yml
groups:
- name: example
  rules:
  - alert: HighCPUUsage
    expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
    for: 5m
    labels:
      severity: critical
      service: user-service
      team: backend
    annotations:
      summary: "高 CPU 使用率"
      description: "实例 {{ $labels.instance }} CPU 使用率超过 90%"
      runbook_url: "https://wiki.example.com/runbooks/cpu-high"

在模板中:

  • {{ .Labels.severity }}critical
  • {{ .Annotations.description }} → 渲染后的描述(含变量替换)

2. 条件渲染

根据严重性使用不同图标:

{{ define "severity.icon" }}
{{ if eq .Labels.severity "critical" }}🔥
{{ else if eq .Labels.severity "warning" }}⚠️
{{ else }}ℹ️
{{ end }}
{{ end }}

{{ template "severity.icon" . }}

3. 时间格式化

Alertmanager 提供 humanizeDuration 函数:

持续时间: {{ .StartsAt | humanizeDuration }}

但注意:humanizeDuration 返回如 "5m23.456s",若需更简洁格式(如 "5分钟"),需自定义函数。


扩展模板函数:Java 开发自定义 Alertmanager 🧪

虽然 Alertmanager 本身是 Go 编写的,但我们可以通过 包装层Webhook 接收器 实现 Java 端的深度定制。以下是两种常见方案:

方案一:使用 Webhook 接收器 + Spring Boot

Alertmanager 支持将告警发送到任意 HTTP endpoint。我们可用 Java 构建一个 webhook 服务,接收原始 JSON,再按需格式化并转发到企业微信、钉钉等。

步骤 1:Alertmanager 配置 Webhook
receivers:
  - name: 'java-webhook'
    webhook_configs:
      - url: 'http://alert-processor:8080/webhook'
        send_resolved: true
步骤 2:Spring Boot 接收器实现
// AlertPayload.java
public class AlertPayload {
    private String receiver;
    private String status;
    private List<Alert> alerts;
    private Map<String, String> groupLabels;
    private Map<String, String> commonLabels;
    private Map<String, String> commonAnnotations;
    private String externalURL;

    // getters and setters
}

public class Alert {
    private String status;
    private Map<String, String> labels;
    private Map<String, String> annotations;
    private Instant startsAt;
    private Instant endsAt;
    private String generatorURL;

    // getters and setters
}
// WebhookController.java
@RestController
public class WebhookController {

    @PostMapping("/webhook")
    public ResponseEntity<String> handleAlert(@RequestBody AlertPayload payload) {
        List<String> messages = new ArrayList<>();
        
        for (Alert alert : payload.getAlerts()) {
            StringBuilder msg = new StringBuilder();
            
            // 图标
            String icon = "ℹ️";
            if ("critical".equals(alert.getLabels().get("severity"))) {
                icon = "🚨";
            } else if ("warning".equals(alert.getLabels().get("severity"))) {
                icon = "⚠️";
            }
            
            msg.append(icon).append(" [").append(alert.getStatus().toUpperCase()).append("] ")
               .append(alert.getAnnotations().getOrDefault("summary", "未知告警"))
               .append("\n");
            
            msg.append("服务: ").append(alert.getLabels().getOrDefault("service", "N/A")).append("\n");
            msg.append("实例: ").append(alert.getLabels().get("instance")).append("\n");
            msg.append("严重性: ").append(alert.getLabels().get("severity")).append("\n");
            
            // 计算持续时间
            Duration duration = Duration.between(alert.getStartsAt(), Instant.now());
            long minutes = duration.toMinutes();
            long seconds = duration.getSeconds() % 60;
            msg.append("持续时间: ").append(minutes).append("分").append(seconds).append("秒\n");
            
            msg.append("描述: ").append(alert.getAnnotations().get("description")).append("\n");
            msg.append("建议: ").append(alert.getAnnotations().getOrDefault("runbook_url", "请联系 SRE 团队")).append("\n");
            msg.append("查看图表: ").append(alert.getGeneratorURL());
            
            messages.add(msg.toString());
        }

        // 发送到企业微信(示例)
        sendToWeChat(String.join("\n---\n", messages));
        
        return ResponseEntity.ok("OK");
    }

    private void sendToWeChat(String content) {
        // 调用企业微信机器人 API
        // https://work.weixin.qq.com/api/doc/90000/90136/91770
        RestTemplate restTemplate = new RestTemplate();
        Map<String, Object> body = Map.of("msgtype", "text", "text", Map.of("content", content));
        restTemplate.postForEntity("YOUR_WECHAT_WEBHOOK_URL", body, String.class);
    }
}

优势:完全控制格式、可集成数据库查询(如获取负责人信息)、支持多通道分发。

方案二:修改 Alertmanager 源码(高级)

若需在 Alertmanager 内部扩展函数(如中文时间格式),可 fork 源码并添加自定义模板函数。

但此方案维护成本高,一般不推荐。Webhook 方案更灵活且解耦。


实战案例:多级告警与值班集成 📞

场景需求

  • P0 级告警(如数据库宕机):立即电话通知值班人员
  • P1 级告警(如 API 错误率升高):企业微信通知 + 自动创建 Jira 工单
  • P2 级告警(如磁盘使用率高):每日汇总邮件

实现思路

  1. 在 Prometheus 规则中定义 priority 标签
  2. Alertmanager 使用路由(route)按优先级分发
  3. 不同接收器使用不同模板
Prometheus 规则示例
- alert: DatabaseDown
  expr: up{job="mysql"} == 0
  labels:
    severity: critical
    priority: p0
  annotations:
    summary: "MySQL 数据库宕机"
    description: "实例 {{ $labels.instance }} 无法连接"

- alert: HighAPIErrorRate
  expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
  labels:
    severity: warning
    priority: p1
  annotations:
    summary: "API 错误率过高"
    runbook_url: "https://wiki.example.com/runbooks/api-errors"
Alertmanager 路由配置
route:
  receiver: 'default'
  group_by: ['alertname', 'service']
  routes:
    - matchers:
        - priority = "p0"
      receiver: 'phone-call'
    - matchers:
        - priority = "p1"
      receiver: 'wechat-and-jira'
    - matchers:
        - priority = "p2"
      receiver: 'daily-email'

receivers:
  - name: 'phone-call'
    webhook_configs:
      - url: 'http://oncall-system:8080/call'

  - name: 'wechat-and-jira'
    webhook_configs:
      - url: 'http://alert-processor:8080/wechat'
      - url: 'http://alert-processor:8080/jira'

  - name: 'daily-email'
    email_configs:
      - to: 'alerts@example.com'
        html: '{{ template "email.summary" . }}'
Java Webhook 处理 Jira 工单
@PostMapping("/jira")
public void createJiraTicket(@RequestBody AlertPayload payload) {
    Alert alert = payload.getAlerts().get(0); // 假设单条
    
    String summary = "[ALERT] " + alert.getAnnotations().get("summary");
    String description = String.format(
        "告警详情:\n- 服务: %s\n- 实例: %s\n- 描述: %s\n- Prometheus 链接: %s",
        alert.getLabels().get("service"),
        alert.getLabels().get("instance"),
        alert.getAnnotations().get("description"),
        alert.getGeneratorURL()
    );

    // 调用 Jira REST API
    // https://developer.atlassian.com/cloud/jira/platform/rest/v3/
    RestTemplate rt = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    headers.setBasicAuth("user", "api_token");
    headers.setContentType(MediaType.APPLICATION_JSON);

    Map<String, Object> issue = Map.of(
        "fields", Map.of(
            "project", Map.of("key", "OPS"),
            "summary", summary,
            "description", description,
            "issuetype", Map.of("name", "Incident")
        )
    );

    HttpEntity<Map> entity = new HttpEntity<>(issue, headers);
    rt.postForEntity("https://your-domain.atlassian.net/rest/api/3/issue", entity, String.class);
}

🔗 Atlassian 官方文档:Jira REST API v3


模板调试技巧:避免“模板地狱” 🛠️

自定义模板易出错,且 Alertmanager 日志不总是清晰。以下是调试方法:

1. 使用 amtool 测试模板

Alertmanager 自带命令行工具 amtool,可模拟告警发送:

amtool alert add \
  alertname=TestAlert \
  instance=10.0.1.5:9100 \
  severity=critical \
  service=user-service \
  --annotation=summary="测试告警" \
  --annotation=description="这是一个测试"

然后检查 Alertmanager 日志或 webhook 接收端。

2. 在模板中打印调试信息

{{ define "debug" }}
DEBUG: Labels = {{ .Labels }}
DEBUG: Annotations = {{ .Annotations }}
{{ end }}

3. 使用在线 Go 模板 playground

虽然不能完全模拟 Alertmanager 上下文,但可验证语法:


高级技巧:动态内容与外部数据集成 🌐

1. 从 CMDB 获取服务负责人

假设我们有一个 CMDB 服务,可通过服务名查询负责人:

private String getOwner(String service) {
    ResponseEntity<String> response = restTemplate.getForEntity(
        "http://cmdb/api/services/" + service + "/owner", 
        String.class
    );
    return response.getBody(); // e.g., "zhangsan@company.com"
}

在告警消息中加入:

负责人: {{ getOwner .Labels.service }}

⚠️ 注意:避免在模板中直接调用外部 API(性能 & 可靠性)。应在 webhook 层预加载。

2. 附加 Grafana 面板链接

Prometheus 的 GeneratorURL 指向自身,但团队可能更习惯看 Grafana。

可在注解中预定义:

annotations:
  dashboard: "https://grafana.example.com/d/abc123?var-instance={{ $labels.instance }}"

模板中直接引用:

Grafana 面板: {{ .Annotations.dashboard }}

安全与权限控制 🔒

自定义模板可能暴露敏感信息(如实例 IP、内部服务名)。需注意:

  1. 不要在告警中包含密码、密钥等
  2. 对外通道(如 Slack)应过滤敏感标签

在 Java Webhook 中实现过滤:

// 移除敏感标签
Map<String, String> safeLabels = new HashMap<>(alert.getLabels());
safeLabels.remove("internal_ip");
safeLabels.remove("aws_instance_id");
  1. 使用 Alertmanager 的 inhibit_rules 避免重复告警
inhibit_rules:
  - source_matchers:
      - alertname = "HostDown"
    target_matchers:
      - alertname =~ ".*Down"
    equal: ['instance']

当主机宕机时,抑制该主机上所有服务的 Down 告警。


性能考量:批量告警与速率限制 ⚖️

当大规模故障发生时,可能产生数百条告警。需:

  1. 合理设置 group_waitgroup_interval
route:
  group_wait: 30s        # 首次发送前等待时间
  group_interval: 5m     # 同一组后续告警间隔
  repeat_interval: 3h    # 重复通知间隔
  1. 在模板中汇总告警
{{ define "summary.text" }}
🚨 [{{ .Status | toUpper }}] 共 {{ .Alerts | len }} 条告警
受影响服务: {{ range .Alerts }}{{ .Labels.service }}, {{ end }}
最新告警: {{ index .Alerts 0 | .Annotations.summary }}
{{ end }}
  1. Webhook 服务需具备高并发能力

使用 Spring WebFlux 或异步处理避免阻塞。


国际化与多语言支持 🌍

跨国团队可能需要多语言告警。可在标签中指定语言:

labels:
  lang: zh

Java Webhook 根据 lang 选择模板:

String template = "alert_" + alert.getLabels().getOrDefault("lang", "en") + ".ftl";
String content = freeMarkerTemplate.render(template, data);

使用 FreeMarker 或 Thymeleaf 管理多语言模板。


可视化:告警处理流程图 📊

以下 mermaid 图展示了从 Prometheus 到最终通知的完整流程:

User Communication Channel Webhook (Java) Alertmanager Prometheus User Communication Channel Webhook (Java) Alertmanager Prometheus alt [使用内置模板] [使用 Webhook] 发送告警 (JSON) 应用路由规则 渲染模板 (或发送原始数据) 直接发送格式化消息 POST /webhook (原始 JSON) 自定义处理 (查询CMDB, 格式化等) 发送个性化消息 接收告警

此流程突显了 Webhook 方案的灵活性:解耦告警生成与通知逻辑


最佳实践总结 ✅

  1. 注解优于标签:将人类可读信息放入 annotations,机器标识放入 labels
  2. 提供 Runbook 链接:每个告警应有对应的处理手册
  3. 避免告警疲劳:通过合理分组、抑制规则减少噪音
  4. 测试模板变更:使用 amtool 或 staging 环境验证
  5. 监控 Alertmanager 自身:确保告警系统可靠

📚 推荐阅读:Prometheus 官方告警最佳实践


结语:让告警成为你的助手,而非负担 🤝

自定义告警模板不仅是技术实现,更是SRE 文化的体现——通过工程手段将运维经验固化为可复用的自动化流程。精心设计的告警能显著缩短 MTTR(平均恢复时间),提升团队幸福感。

无论是通过 Alertmanager 内置模板,还是借助 Java Webhook 实现深度定制,核心目标始终一致:在正确的时间,将正确的信息,传递给正确的人,并附带明确的行动指引

开始你的模板之旅吧!从一条简单的 CPU 告警开始,逐步加入服务上下文、负责人信息、自动化建议……最终,你会拥有一个真正“智能”的告警系统。

💬 互动:你在实践中遇到过哪些告警模板的挑战?欢迎在评论区分享你的解决方案!


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐