prometheus alertmanager 对接飞书

发布于:2025-08-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

alertmanager 直接配置 飞书 的 webhook ,发现并不满足飞书接口的 json 格式。报错如下

level=error ts=2025-08-28T04:57:02.734Z caller=dispatch.go:310 component=dispatcher msg="Notify for alerts failed" num_alerts=23 err="prometheusalert-webhook/webhook[0]: notify retry canceled due to unrecoverable error after 1 attempts: unexpected status code 400: https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxx"

上网查询发现开源项目 prometheusalert 按照官方文档配置配置飞书地址,v4.9.1 版本默认的模板和网上找到的模板 空卡片的情况,如下

然后就打算自己写个 python 查询,接收 alertmanager 的消息体,做修改转发给 飞书。

cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: alert-flask-cm
  namespace: monitor
data:
  app.py: |
    from flask import Flask, request, jsonify
    import json
    import requests
    import logging
    from datetime import datetime

    app = Flask(__name__)

    # 飞书机器人 Webhook 地址
    webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx"

    # 日志配置
    logging.basicConfig(level=logging.INFO)

    def format_time(timestr):
        """把 2025-08-08T06:55:43.825666166Z → 2025-08-08 06:55:43"""
        try:
            dt = datetime.fromisoformat(timestr.replace("Z", "+00:00"))
            return dt.strftime("%Y-%m-%d %H:%M:%S")
        except Exception:
            return timestr

    @app.route('/alert', methods=['POST'])
    def receive_alert():
        try:
            alert_response = request.json
            logging.info("收到 Alertmanager 消息体: %s", json.dumps(alert_response, ensure_ascii=False))

            alerts = alert_response.get("alerts", [])
            if not alerts:
                logging.info("没有告警")
                return "no alerts", 200

            send_status = []
            for alert in alerts:
                status = alert.get("status", "firing")
                if status == "firing":
                    msg_json = format_alert_to_feishu(alert)
                elif status == "resolved":
                    msg_json = format_resolved_to_feishu(alert)
                else:
                    logging.info("未知状态: %s", status)
                    continue

                logging.info("生成飞书消息体: %s", msg_json)
                response = send_alert(msg_json)
                if response is None:
                    send_status.append("发送失败")
                else:
                    send_status.append(f"发送成功:{response.status_code}")

            return "; ".join(send_status), 200

        except Exception as e:
            logging.exception("处理告警异常")
            return f"error: {str(e)}", 500

    def send_alert(json_data):
        try:
            response = requests.post(webhook_url, json=json.loads(json_data), timeout=5)
            response.raise_for_status()
            logging.info("发送飞书成功,状态码: %s", response.status_code)
            return response
        except requests.exceptions.RequestException as e:
            logging.error("发送飞书失败: %s", e)
            return None

    def format_alert_to_feishu(alert):
        labels = alert.get("labels", {})
        annotations = alert.get("annotations", {})

        alert_name = labels.get("alertname", "Unknown")
        instance = labels.get("instance", "Unknown")
        severity = labels.get("severity", "N/A")
        summary = annotations.get("summary", "")
        description = annotations.get("description", "无描述")
        start_time = format_time(alert.get("startsAt", "Unknown"))

        lines = [
        f"**告警名称**:{alert_name}",
        f"**告警实例**:{instance}",
        f"**告警级别**:{severity}",
        ]
        if summary:
            lines.append(f"**告警摘要**:{summary}")
        lines.append(f"**告警描述**:{description}")
        lines.append(f"**触发时间**:{start_time}")

        content = "\n".join(lines)

        webhook_msg = {
            "msg_type": "interactive",
            "card": {
                "header": {
                    "title": {"tag": "plain_text", "content": "===== == 告警 == ====="},
                    "template": "red"
                },
                "elements": [
                    {"tag": "div", "text": {"tag": "lark_md", "content": content}}
                ]
            }
        }
        return json.dumps(webhook_msg, ensure_ascii=False)

    def format_resolved_to_feishu(alert):
        labels = alert.get("labels", {})
        annotations = alert.get("annotations", {})

        alert_name = labels.get("alertname", "Unknown")
        instance = labels.get("instance", "Unknown")
        summary = annotations.get("summary", "")
        success_msg = annotations.get("success", "告警已恢复")
        description = annotations.get("description", "无描述")
        end_time = format_time(alert.get("endsAt", "Unknown"))

        lines = [
        f"**告警名称**:{alert_name}",
        f"**告警实例**:{instance}",
        ]
        if summary:
            lines.append(f"**告警摘要**:{summary}")
        lines.append(f"**告警描述**:{description}")
        lines.append(f"**恢复说明**:{success_msg}")
        lines.append(f"**恢复时间**:{end_time}")

        content = "\n".join(lines)

        webhook_msg = {
            "msg_type": "interactive",
            "card": {
                "header": {
                    "title": {"tag": "plain_text", "content": "===== == 恢复 == ====="},
                    "template": "green"
                },
                "elements": [
                    {"tag": "div", "text": {"tag": "lark_md", "content": content}}
                ]
            }
        }
        return json.dumps(webhook_msg, ensure_ascii=False)

    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=4000)

deployment 中的镜像需要自己构建,随便找个 python 镜像作为 base pip 安装 flask、requetsts 即可

deploy-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: alert-flask
  namespace: monitor
spec:
  replicas: 1
  selector:
    matchLabels:
      app: alert-flask
  template:
    metadata:
      labels:
        app: alert-flask
    spec:
      containers:
      - name: alert-flask
        image: python:3.11-slim-bookworm-flask
        command: ["python", "/app/app.py"]
        ports:
        - containerPort: 4000
        volumeMounts:
        - name: app-cm
          mountPath: /app
      volumes:
      - name: app-cm
        configMap:
          name: alert-flask-cm
---
apiVersion: v1
kind: Service
metadata:
  name: alert-flask-svc
  namespace: monitor
spec:
  selector:
    app: alert-flask
  ports:
  - name: http
    port: 4000
    targetPort: 4000
  type: ClusterIP

上面的 configmap、deploy、service 部署好后,更改 alertmanager 的配置

receivers:
- name: feishu
  webhook_configs:
  - send_resolved: true
    url: http://alert-flask-svc:4000/alert

然后飞书就能收到告警了


网站公告

今日签到

点亮在社区的每一天
去签到