You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python上传Grafana告警组失败:连接错误问题求助

问题描述

尝试通过Python脚本借助grafanalib库生成AlertGroup并上传至Grafana,期望脚本运行后告警自动同步到Grafana,但执行时出现连接错误:

File "/usr/lib/python3/dist-packages/requests/adapters.py"
line 565, in send raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPConnectionPool(host='none', port=80): Max retries exceeded with url: /api/ruler/grafana/api/v1/rules/testfolder/Production%20Alerts (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7feb3b36b910>: Failed to establish a new connection: [Errno -2] Name or service not known'))

所用代码

from grafanalib.core import AlertGroup
from grafanalib._gen import DashboardEncoder, loader
import json
import requests
from os import getenv


def get_alert_json(alert: AlertGroup):
    '''
    get_alert_json generates JSON from grafanalib AlertGroup object

    :param alert - AlertGroup created via grafanalib
    '''

    return json.dumps(alert.to_json_data(), sort_keys=True, indent=4, cls=DashboardEncoder)


def upload_to_grafana(alertjson, folder, server, api_key, session_cookie, verify=True):
    '''
    upload_to_grafana tries to upload the AlertGroup to grafana and prints response
    WARNING: This will first delete all alerts in the AlertGroup before replacing them with the provided AlertGroup.

    :param alertjson - AlertGroup json generated by grafanalib
    :param folder - Folder to upload the AlertGroup into
    :param server - grafana server name
    :param api_key - grafana api key with read and write privileges
    '''
    groupName = json.loads(alertjson)['name']

    headers = {}
    if api_key:
        print("using bearer auth")
        headers['Authorization'] = f"Bearer {api_key}"

    if session_cookie:
        print("using session cookie")
        headers['Cookie'] = session_cookie

    print(f"deleting AlertGroup {groupName} in folder {folder}")
    r = requests.delete(f"http://{server}/api/ruler/grafana/api/v1/rules/{folder}/{groupName}", headers=headers, verify=verify)
    print(f"{r.status_code} - {r.content}")

    headers['Content-Type'] = 'application/json'

    print(f"ensuring folder {folder} exists")
    r = requests.post(f"http://{server}/api/folders", data={"title": folder}, headers=headers)
    print(f"{r.status_code} - {r.content}")

    print(f"uploading AlertGroup {groupName} to folder {folder}")
    r = requests.post(f"http://{server}/api/ruler/grafana/api/v1/rules/{folder}", data=alertjson, headers=headers, verify=verify)
    # TODO: add error handling
    print(f"{r.status_code} - {r.content}")


grafana_api_key = getenv("eyJrIjoiS1ZjV0I2SWdlUm5lMnpYUVpzT2ZRUVpqMkM0QUVtYnIiLCJuIjoiTXlfa2V5IiwiaWQiOjF9")
grafana_server = getenv("localhost:3000")
grafana_cookie = getenv("GRAFANA_COOKIE")

# Generate an alert from the example
my_alergroup_json = get_alert_json(loader("example.alertgroup.py"))
upload_to_grafana(my_alergroup_json, "testfolder", grafana_server, grafana_api_key, grafana_cookie)

example.alertgroup.py 内容

from grafanalib.core import (
    AlertGroup,
    AlertRulev9,
    Target,
    AlertCondition,
    AlertExpression,
    GreaterThan,
    OP_AND,
    RTYPE_LAST,
    EXP_TYPE_CLASSIC,
    EXP_TYPE_REDUCE,
    EXP_TYPE_MATH
)

# An AlertGroup is one group contained in an alert folder.
alertgroup = AlertGroup(
    name="Production Alerts",
    # Each AlertRule forms a separate alert.
    rules=[
        # Alert rule using classic condition > 3
        AlertRulev9(
            # Each rule must have a unique title
            title="Alert for something 1",
            uid='alert1',
            # Several triggers can be used per alert
            condition='B',
            triggers=[
                # A target refId must be assigned, and exist only once per AlertRule.
                Target(
                    expr="from(bucket: \"sensors\")\n  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n  |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n  |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n  |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n  |> yield(name: \"mean\")",
                    # Set datasource to name of your datasource
                    datasource="influxdb",
                    refId="A",
                ),
                AlertExpression(
                    refId="B",
                    expressionType=EXP_TYPE_CLASSIC,
                    expression='A',
                    conditions=[
                        AlertCondition(
                            evaluator=GreaterThan(3),
                            operator=OP_AND,
                            reducerType=RTYPE_LAST
                        )
                    ]
                )
            ],
            annotations={
                "summary": "The database is down",
                "runbook_url": "runbook-for-this-scenario.com/foo",
            },
            labels={
                "environment": "prod",
                "slack": "prod-alerts",
            },
            evaluateFor="3m",
        ),
        # Alert rule using reduce and Math
        AlertRulev9(
            # Each rule must have a unique title
            title="Alert for something 2",
            uid='alert2',
            condition='C',
            # Several triggers can be used per alert
            triggers=[
                # A target refId must be assigned, and exist only once per AlertRule.
                Target(
                    expr="from(bucket: \"sensors\")\n  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n  |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n  |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n  |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n  |> yield(name: \"mean\")",
                    # Set datasource to name of your datasource
                    datasource="influxdb",
                    refId="A",
                ),
                AlertExpression(
                    refId="B",
                    expressionType=EXP_TYPE_REDUCE,
                    expression='A',
                    reduceFunction='mean',
                    reduceMode='dropNN'
                ),
                AlertExpression(
                    refId="C",
                    expressionType=EXP_TYPE_MATH,
                    expression='$B < 3'
                )
            ],
            annotations={
                "summary": "The database is down",
                "runbook_url": "runbook-for-this-scenario.com/foo",
            },
            labels={
                "environment": "prod",
                "slack": "prod-alerts",
            },
            evaluateFor="3m",
        )
    ]
)
问题排查与解决

1. 核心问题:环境变量读取错误

错误日志显示host='none',说明grafana_server变量的值为None,根源是getenv()用法错误:

  • getenv("localhost:3000")是读取名为localhost:3000的环境变量,而非直接赋值字符串;
  • getenv("eyJrIjoi...")同理,错误地将API key作为环境变量名传入,而非读取对应名称的环境变量。

修复方式

方式一:直接赋值(测试场景)

如果只是快速测试,无需依赖环境变量,直接给变量赋值:

grafana_api_key = "eyJrIjoiS1ZjV0I2SWdlUm5lMnpYUVpzT2ZRUVpqMkM0QUVtYnIiLCJuIjoiTXlfa2V5IiwiaWQiOjF9"
grafana_server = "localhost:3000"
grafana_cookie = getenv("GRAFANA_COOKIE")  # 若该环境变量确实存在则保留,否则直接赋值或删除

方式二:正确使用环境变量

先在终端设置环境变量:

# Linux/macOS
export GRAFANA_API_KEY="eyJrIjoiS1ZjV0I2SWdlUm5lMnpYUVpzT2ZRUVpqMkM0QUVtYnIiLCJuIjoiTXlfa2V5IiwiaWQiOjF9"
export GRAFANA_SERVER="localhost:3000"
# 可选:设置cookie
export GRAFANA_COOKIE="your_cookie_value"

再在脚本中读取:

grafana_api_key = getenv("GRAFANA_API_KEY")
grafana_server = getenv("GRAFANA_SERVER")
grafana_cookie = getenv("GRAFANA_COOKIE")

2. 额外问题修复

(1)文件夹创建请求格式错误

Grafana创建文件夹接口要求JSON格式,但原代码用data参数传入字典,默认会以form-encoded格式发送,需改为json参数:

r = requests.post(f"http://{server}/api/folders", json={"title": folder}, headers=headers)

(2)API权限验证

确保你的API key拥有Editor或Admin权限,否则会触发权限不足错误。

(3)Grafana服务状态确认

检查Grafana服务是否正常运行,且能通过http://localhost:3000正常访问。

内容的提问来源于stack exchange,提问作者kartik Ganotra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 00:47:52