适用于 PowerShell 7.0 及以上版本
在 DevOps 和 SRE 实践中,Grafana 已经成为基础设施和应用监控可视化的事实标准。通过丰富的仪表板和告警规则,运维团队可以实时洞察系统健康状态。然而,当需要管理大量仪表板、在不同环境间迁移配置、或者将监控数据与其他系统联动时,手动操作 Grafana Web 界面效率低下且难以保持一致性。
Grafana 提供了功能完善的 HTTP API,PowerShell 天然擅长与 REST API 交互。两者的结合使自动化仪表板管理成为可能:批量创建标准化的监控面板、在不同 Grafana 实例之间同步仪表板配置、定期备份仪表板定义、以及基于外部数据源动态生成面板。这种脚本化的管理方式特别适合多环境、多团队的运维场景。
本文将介绍如何使用 PowerShell 调用 Grafana HTTP API,实现仪表板的查询、创建、导出备份和批量管理操作。
连接 Grafana 并获取仪表板列表
Grafana 的 HTTP API 使用基本认证或 API Token 进行身份验证。以下代码展示了如何封装连接参数,并列出所有仪表板的基本信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| $grafanaConfig = @{ BaseUrl = "http://localhost:3000" User = "admin" Password = "admin" }
$authPair = "{0}:{1}" -f $grafanaConfig.User, $grafanaConfig.Password $authBytes = [System.Text.Encoding]::UTF8.GetBytes($authPair) $authHeader = "Basic {0}" -f [Convert]::ToBase64String($authBytes)
$searchUrl = "{0}/api/search?type=dash-db" -f $grafanaConfig.BaseUrl $response = Invoke-RestMethod -Uri $searchUrl -Headers @{ Authorization = $authHeader } -Method Get
$dashboards = foreach ($item in $response) { [PSCustomObject]@{ 标题 = $item.title UID = $item.uid URI = $item.uri 类型 = $item.type 是否星标 = $item.isStarred 标签 = ($item.tags -join ", ") } }
$dashboards | Format-Table -AutoSize
|
上述脚本首先将 Grafana 的连接信息封装到哈希表中,方便后续复用。然后构建 HTTP Basic Authentication 头,调用 /api/search 接口并指定 type=dash-db 仅返回已保存的仪表板(排除文件夹等非仪表板资源)。通过 foreach 循环将返回的 JSON 数据映射为结构化的 PowerShell 对象,方便后续筛选和格式化输出。
执行结果示例:
1 2 3 4 5 6
| 标题 UID URI 类型 是否星标 标签 ---- --- --- ---- -------- ---- 系统总览 abc123xy db/system-overview dash-db True operations API 响应时间 def456gh db/api-response-time dash-db False monitoring, api 数据库性能 ghi789ij db/database-performance dash-db False database Kubernetes 集群监控 jkl012mn db/kubernetes-cluster dash-db True k8s, operations
|
创建新的 Grafana 仪表板
通过 API 创建仪表板时,需要构造完整的仪表板 JSON 定义。以下示例创建一个包含系统 CPU 和内存使用率面板的监控仪表板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| $dashboardJson = @{ dashboard = @{ uid = "ps-auto-system" title = "PowerShell 自动创建 - 系统监控" tags = @("automated", "powershell", "system") timezone = "browser" schemaVersion = 39 refresh = "30s" time = @{ from = "now-1h" to = "now" } panels = @( @{ id = 1 title = "CPU 使用率 (%)" type = "stat" gridPos = @{ h = 8; w = 6; x = 0; y = 0 } targets = @( @{ expr = '100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)' refId = "A" legendFormat = "{{instance}}" } ) fieldConfig = @{ defaults = @{ unit = "percent" thresholds = @{ steps = @( @{ color = "green"; value = $null } @{ color = "yellow"; value = 60 } @{ color = "red"; value = 85 } ) } } } } @{ id = 2 title = "内存使用率 (%)" type = "gauge" gridPos = @{ h = 8; w = 6; x = 6; y = 0 } targets = @( @{ expr = '(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100' refId = "A" legendFormat = "{{instance}}" } ) fieldConfig = @{ defaults = @{ unit = "percent" thresholds = @{ steps = @( @{ color = "green"; value = $null } @{ color = "yellow"; value = 70 } @{ color = "red"; value = 90 } ) } } } } ) } overwrite = $true }
$createUrl = "{0}/api/dashboards/db" -f $grafanaConfig.BaseUrl $body = $dashboardJson | ConvertTo-Json -Depth 10
$result = Invoke-RestMethod -Uri $createUrl -Headers @{ Authorization = $authHeader "Content-Type" = "application/json" } -Method Post -Body $body
Write-Host "仪表板创建成功" Write-Host " URL: {0}{1}" -f $grafanaConfig.BaseUrl, $result.url Write-Host " 版本: $($result.version)" Write-Host " UID: $($result.uid)"
|
这段代码的核心是构造 Grafana 所需的仪表板 JSON 结构。dashboard 对象包含仪表板的元信息(标题、标签、时区)和面板定义。每个面板通过 gridPos 控制在仪表板网格中的位置和尺寸,targets 定义 Prometheus 查询表达式,fieldConfig 配置显示单位和阈值颜色。将 overwrite 设为 $true 允许重复执行脚本更新已有仪表板,实现幂等操作。
执行结果示例:
1 2 3 4
| 仪表板创建成功 URL: http://localhost:3000/d/ps-auto-system 版本: 1 UID: ps-auto-system
|
批量导出并备份仪表板
定期备份仪表板配置是运维最佳实践。以下脚本将所有仪表板导出为独立的 JSON 文件,并按日期归档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| $backupDate = Get-Date -Format "yyyyMMdd" $backupDir = "GrafanaBackup_$backupDate" New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
Write-Host "开始备份 Grafana 仪表板至: $backupDir"
$searchUrl = "{0}/api/search?type=dash-db" -f $grafanaConfig.BaseUrl $allDashboards = Invoke-RestMethod -Uri $searchUrl -Headers @{ Authorization = $authHeader } -Method Get
$backupResults = foreach ($dashboard in $allDashboards) { $detailUrl = "{0}/api/dashboards/uid/{1}" -f $grafanaConfig.BaseUrl, $dashboard.uid $detail = Invoke-RestMethod -Uri $detailUrl -Headers @{ Authorization = $authHeader } -Method Get
$safeName = $dashboard.title -replace '[\\/:*?\"<>|]', '_' $filePath = Join-Path $backupDir "$safeName.json"
$exportData = @{ meta = @{ exportedAt = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") grafanaVersion = $detail.meta.version uid = $dashboard.uid } dashboard = $detail.dashboard }
$exportData | ConvertTo-Json -Depth 20 | Set-Content -Path $filePath -Encoding UTF8
[PSCustomObject]@{ 仪表板 = $dashboard.title UID = $dashboard.uid 面板数 = $detail.dashboard.panels.Count 文件大小 = "{0:N1} KB" -f ((Get-Item $filePath).Length / 1KB) 状态 = "已备份" } }
Write-Host "`n备份完成,共导出 $($backupResults.Count) 个仪表板" $backupResults | Format-Table -AutoSize
$summaryPath = Join-Path $backupDir "_backup_summary.txt" $summaryContent = @( "Grafana 仪表板备份摘要" "备份时间: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" "仪表板总数: $($backupResults.Count)" "备份来源: $($grafanaConfig.BaseUrl)" "" "仪表板列表:" foreach ($r in $backupResults) { " - $($r.仪表板) (UID: $($r.UID), 面板数: $($r.面板数))" } ) $summaryContent | Set-Content -Path $summaryPath -Encoding UTF8
|
这个脚本实现了完整的备份流程。首先创建以日期命名的备份目录,然后逐一获取每个仪表板的完整 JSON 定义。文件名经过特殊字符清理确保在文件系统上合法。导出的 JSON 不仅包含仪表板定义本身,还附加了导出时间、Grafana 版本等元数据,方便后续追溯。最终还生成一份文本格式的备份摘要文件,便于快速查看备份内容。
执行结果示例:
1 2 3 4 5 6 7 8 9 10
|
-------- --- ------ -------- ---- . . . .
|
跨实例同步仪表板配置
在多环境(开发、测试、生产)部署场景中,保持 Grafana 仪表板配置一致是常见需求。以下脚本从源实例拉取仪表板并推送到目标实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| $sourceConfig = @{ BaseUrl = "http://grafana-dev.internal:3000" User = "admin" Password = "dev-admin-pass" }
$targetConfig = @{ BaseUrl = "http://grafana-prod.internal:3000" User = "admin" Password = "prod-admin-pass" }
function New-GrafanaAuthHeader { param($User, $Password) $pair = "{0}:{1}" -f $User, $Password $bytes = [System.Text.Encoding]::UTF8.GetBytes($pair) "Basic {0}" -f [Convert]::ToBase64String($bytes) }
$sourceAuth = New-GrafanaAuthHeader @sourceConfig $targetAuth = New-GrafanaAuthHeader @targetConfig
$searchUrl = "{0}/api/search?type=dash-db&tag=sync" -f $sourceConfig.BaseUrl $syncDashboards = Invoke-RestMethod -Uri $searchUrl -Headers @{ Authorization = $sourceAuth } -Method Get
Write-Host "找到 $($syncDashboards.Count) 个标记为同步的仪表板"
$syncResults = foreach ($dashboard in $syncDashboards) { $sourceUrl = "{0}/api/dashboards/uid/{1}" -f $sourceConfig.BaseUrl, $dashboard.uid $sourceData = Invoke-RestMethod -Uri $sourceUrl -Headers @{ Authorization = $sourceAuth } -Method Get
$pushBody = @{ dashboard = $sourceData.dashboard overwrite = $true }
$targetUrl = "{0}/api/dashboards/db" -f $targetConfig.BaseUrl $body = $pushBody | ConvertTo-Json -Depth 20
try { $pushResult = Invoke-RestMethod -Uri $targetUrl -Headers @{ Authorization = $targetAuth "Content-Type" = "application/json" } -Method Post -Body $body
[PSCustomObject]@{ 仪表板 = $dashboard.title 目标UID = $pushResult.uid 目标版本 = $pushResult.version 同步状态 = "成功" 同步时间 = Get-Date -Format "HH:mm:ss" } } catch { [PSCustomObject]@{ 仪表板 = $dashboard.title 目标UID = $dashboard.uid 目标版本 = "N/A" 同步状态 = "失败: $($_.Exception.Message)" 同步时间 = Get-Date -Format "HH:mm:ss" } } }
Write-Host "`n同步结果:" $syncResults | Format-Table -AutoSize
|
这段代码实现了跨 Grafana 实例的仪表板同步。通过自定义函数 New-GrafanaAuthHeader 封装认证逻辑,避免重复代码。同步策略基于标签筛选:只有在源实例中标记为 sync 的仪表板才会被同步。使用 try/catch 处理推送过程中的异常,确保单条仪表板同步失败不会中断整个批处理。overwrite = $true 保证幂等性,重复执行不会创建重复仪表板。
执行结果示例:
1 2 3 4 5 6 7 8 9
|
-------- ------- -------- -------- --------
|
注意事项
认证安全:避免在脚本中硬编码 Grafana 用户名和密码。建议使用环境变量、Azure Key Vault 或 PowerShell SecretManagement 模块存储凭据。生产环境中优先使用 Grafana Service Account Token 而非管理员账号密码。
API 权限控制:为自动化脚本创建专用的 Service Account,仅授予必要的权限(如 Dashboard Viewer、Dashboard Editor),避免使用全局管理员权限。通过最小权限原则降低误操作风险。
JSON 序列化深度:Grafana 仪表板的 JSON 结构嵌套层级较深,使用 ConvertTo-Json 时务必指定足够的 -Depth 参数(建议 10 以上),否则深层配置可能被截断为字符串 System.Object[]。
版本管理:每次通过 API 更新仪表板都会递增版本号。建议在 CI/CD 流水线中将导出的 JSON 文件纳入 Git 版本控制,实现仪表板配置的可追溯和回滚能力。
数据源依赖:仪表板中的查询面板依赖特定的数据源(如 Prometheus、InfluxDB)。跨实例同步时需确保目标实例已配置同名数据源,否则面板将显示查询错误。可先通过 /api/datasources 接口同步数据源配置。
并发控制:批量操作仪表板时,注意 Grafana 对并发请求的速率限制。在循环中使用 Start-Sleep 添加适当间隔(如 100-200 毫秒),或使用 -ThrottleLimit 参数控制并发度,避免触发 HTTP 429 Too Many Requests 错误。