PowerShell 技能连载 - Azure Monitor 仪表板自动化

适用于 PowerShell 7.0 及以上版本,需要 Az.Monitor 模块

在云原生运维中,Azure Monitor 仪表板是将海量监控数据转化为可视化洞察的核心工具。通过仪表板,运维团队可以一目了然地掌握虚拟机 CPU 利用率、存储账户延迟、应用网关吞吐量等关键指标,从而快速定位性能瓶颈和潜在故障。然而,当企业规模扩展到数十个订阅、上百个资源时,手动在 Azure 门户中拖拽创建仪表板不仅耗时,而且难以保证一致性。

PowerShell 提供了完整的 Azure Dashboard JSON 模板操控能力,结合 Az.Monitor 模块,我们可以将仪表板的创建、修改和部署完全纳入基础设施即代码(IaC)流程。这意味着每套环境都能拥有标准化的监控视图,变更可追溯、可审计、可回滚,大幅降低人为失误风险。

本文将围绕三个核心场景展开:动态构建仪表板 JSON 模板、配置指标告警与自动通知、以及跨订阅批量部署标准化仪表板,帮助你建立一套完整的 Azure Monitor 仪表板自动化工作流。

仪表板 JSON 模板构建

Azure Dashboard 的底层是一个 JSON 文档,定义了每个磁贴(tile)的类型、位置、大小和数据源。我们可以用 PowerShell 哈希表和 ConvertTo-Json 动态生成这个结构,实现参数化的仪表板模板。

以下函数封装了仪表板 JSON 的构建逻辑,支持自定义标题、订阅 ID 和资源组参数,并预置了 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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
function New-AzDashboardTemplate {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$DashboardName,

[Parameter(Mandatory)]
[string]$SubscriptionId,

[Parameter(Mandatory)]
[string]$ResourceGroupName,

[int]$RefreshIntervalSeconds = 300
)

$cpuTile = @{
position = @{ x = 0; y = 0; colSpan = 6; rowSpan = 4 }
metadata = @{
type = 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
inputs = @(
@{
name = 'query'
value = @{
id = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Compute/virtualMachines"
chartType = 0
metrics = @(
@{ name = 'Percentage CPU'; resourceId = "/subscriptions/$SubscriptionId" }
)
timespan = @{ duration = 'PT1H' }
interval = 'PT5M'
}
}
)
settings = @{
content = @{
options = @{
chart = @{
groupBy = $null
topRows = 10
}
}
}
}
}
}

$memTile = @{
position = @{ x = 6; y = 0; colSpan = 6; rowSpan = 4 }
metadata = @{
type = 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
inputs = @(
@{
name = 'query'
value = @{
id = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Compute/virtualMachines"
chartType = 0
metrics = @(
@{ name = 'Available Memory Bytes'; resourceId = "/subscriptions/$SubscriptionId" }
)
timespan = @{ duration = 'PT1H' }
interval = 'PT5M'
}
}
)
}
}

$dashboard = @{
id = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Portal/dashboards/$DashboardName"
name = $DashboardName
type = 'Microsoft.Portal/dashboards'
location = 'global'
tags = @{ createdBy = 'PowerShell-Automation'; createdAt = (Get-Date -Format 'yyyy-MM-dd') }
properties = @{
lenses = @{
'0' = @{
order = 0
parts = @($cpuTile, $memTile)
}
}
metadata = @{
model = @{
editable = $true
timeRange = @{
value = @{ relative = @{ duration = 24; timeUnit = 1 } }
type = 'MsPortalFx.Composition.Configuration.ValueTypes.TimeRangeType.Relative'
}
filter = @{
value = $null
type = 'MsPortalFx.Composition.Configuration.ValueTypes.FilterType.Callout'
}
}
}
}
}

# 深度设置为 10 层,确保嵌套结构完整输出
$dashboard | ConvertTo-Json -Depth 10
}

# 生成仪表板 JSON
$json = New-AzDashboardTemplate `
-DashboardName 'prod-vm-monitor' `
-SubscriptionId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' `
-ResourceGroupName 'rg-production'

$json | Out-File -FilePath './prod-vm-monitor-dashboard.json' -Encoding utf8
Write-Host "仪表板 JSON 已生成,文件大小:$((Get-Item './prod-vm-monitor-dashboard.json').Length) 字节"

执行结果示例:

1
仪表板 JSON 已生成,文件大小:2847 字节

生成的 JSON 文件可以直接通过 Azure 门户导入,也可以用 New-AzPortalDashboard 或 REST API 部署到指定资源组。通过修改 $cpuTile$memTile 中的指标名称,你可以快速扩展出网络吞吐量、磁盘 I/O 等更多监控视图。

指标告警与自动通知

仪表板负责展示,告警负责驱动行动。在 Azure Monitor 体系中,指标告警规则(Metric Alert Rule)配合操作组(Action Group),可以在指标突破阈值时自动触发邮件、Webhook、短信等通知渠道。以下脚本演示了完整的告警链路创建流程:

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
# 先确保已登录并选择正确的订阅
# Connect-AzAccount
# Set-AzContext -SubscriptionId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

$ResourceGroup = 'rg-production'
$ActionGroupName = 'ag-ops-team'
$AlertRuleName = 'alert-vm-cpu-high'

# 创建操作组:邮件通知 + Webhook
$emailReceiver = New-AzActionGroupReceiver `
-Name 'ops-email' `
-EmailAddress 'ops-team@contoso.com'

$webhookReceiver = New-AzActionGroupReceiver `
-Name 'incident-webhook' `
-WebhookUri 'https://hooks.example.com/azure-alerts'

$actionGroup = Set-AzActionGroup `
-ResourceGroupName $ResourceGroup `
-Name $ActionGroupName `
-ShortName 'OpsTeam' `
-Receiver $emailReceiver, $webhookReceiver

Write-Host "操作组已创建:$($actionGroup.Name)"

# 获取操作组的 ARM ID,告警规则需要引用
$actionGroupId = $actionGroup.Id

# 创建指标告警规则:CPU 使用率超过 85% 持续 5 分钟触发
$targetVm = Get-AzVM -ResourceGroupName $ResourceGroup -Name 'vm-web-01'
$vmResourceId = $targetVm.Id

$criteria = New-AzMetricAlertRuleV2Criteria `
-MetricName 'Percentage CPU' `
-TimeAggregation 'Average' `
-Operator 'GreaterThan' `
-Threshold 85

$actionGroupObject = New-AzMetricAlertRuleV2ActionGroup `
-ActionGroupId $actionGroupId

# 设置告警规则的维度过滤(可选)
$dimension = New-AzMetricAlertRuleV2DimensionSelection `
-DimensionName 'VMName' `
-ValuesToInclude '*'

$alert = Add-AzMetricAlertRuleV2 `
-Name $AlertRuleName `
-ResourceGroupName $ResourceGroup `
-WindowSize 'PT5M' `
-Frequency 'PT1M' `
-TargetResourceId $vmResourceId `
-Condition $criteria `
-ActionGroup $actionGroupObject `
-Severity 2 `
-Description "VM $($targetVm.Name) CPU 使用率超过 85% 持续 5 分钟"

Write-Host "告警规则已创建:$($alert.Name)"
Write-Host "目标资源:$vmResourceId"
Write-Host "阈值条件:Average Percentage CPU > 85%"
Write-Host "评估窗口:5 分钟,评估频率:1 分钟"

执行结果示例:

1
2
3
4
5
操作组已创建:ag-ops-team
告警规则已创建:alert-vm-cpu-high
目标资源:/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-production/providers/Microsoft.Compute/virtualMachines/vm-web-01
阈值条件:Average Percentage CPU > 85%
评估窗口:5 分钟,评估频率:1 分钟

告警触发后,Azure 会同时向 ops-team@contoso.com 发送告警邮件,并向 https://hooks.example.com/azure-alerts 推送 Webhook 请求。你可以将 Webhook 对接到企业微信、飞书、Slack 等即时通讯平台,实现秒级告警触达。

多订阅仪表板批量部署

在企业级场景中,运维团队通常需要管理多个订阅(开发、测试、预生产、生产),每个订阅都应部署一套标准化的监控仪表板。手动逐个部署显然不现实,下面通过参数化模板和循环部署来解决这个问题:

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
# 定义目标订阅列表和对应的资源配置
$subscriptions = @(
@{
SubscriptionId = 'aaaa1111-bbbb-2222-cccc-3333dddd4444'
Name = 'production'
ResourceGroup = 'rg-production'
VmPrefix = 'vm-prod'
AlertEmail = 'prod-ops@contoso.com'
}
@{
SubscriptionId = 'eeee5555-ffff-6666-gggg-7777hhhh8888'
Name = 'staging'
ResourceGroup = 'rg-staging'
VmPrefix = 'vm-stg'
AlertEmail = 'staging-ops@contoso.com'
}
@{
SubscriptionId = 'iiii9999-jjjj-0000-kkkk-1111llll2222'
Name = 'development'
ResourceGroup = 'rg-dev'
VmPrefix = 'vm-dev'
AlertEmail = 'dev-ops@contoso.com'
}
)

$deployResults = [System.Collections.Generic.List[PSObject]]::new()

foreach ($sub in $subscriptions) {
Write-Host "`n--- 正在处理订阅:$($sub.Name) ---" -ForegroundColor Cyan

# 切换到目标订阅
$context = Set-AzContext -SubscriptionId $sub.SubscriptionId
Write-Host " 已切换到订阅:$($context.Subscription.Name)"

# 生成仪表板 JSON
$dashboardName = "$($sub.Name)-standard-dashboard"
$dashboardJson = New-AzDashboardTemplate `
-DashboardName $dashboardName `
-SubscriptionId $sub.SubscriptionId `
-ResourceGroupName $sub.ResourceGroup

# 将 JSON 部署为 Azure 仪表板
$tempFile = New-TemporaryFile
$dashboardJson | Out-File -FilePath $tempFile.FullName -Encoding utf8

# 使用 REST API 部署仪表板
$token = (Get-AzAccessToken).Token
$uri = "https://management.azure.com/subscriptions/$($sub.SubscriptionId)/resourceGroups/$($sub.ResourceGroup)/providers/Microsoft.Portal/dashboards/$dashboardName`?api-version=2020-09-01-preview"

$headers = @{
Authorization = "Bearer $token"
'Content-Type' = 'application/json'
}

$response = Invoke-RestMethod -Uri $uri -Method Put -Headers $headers -Body $dashboardJson
Write-Host " 仪表板已部署:$dashboardName"

# 为该订阅创建操作组和告警规则
$agName = "ag-$($sub.Name)-ops"
$emailReceiver = New-AzActionGroupReceiver -Name 'team-email' -EmailAddress $sub.AlertEmail
$actionGroup = Set-AzActionGroup `
-ResourceGroupName $sub.ResourceGroup `
-Name $agName `
-ShortName "$($sub.Name)Ops" `
-Receiver $emailReceiver

Write-Host " 操作组已创建:$agName"

# 记录部署结果
$deployResults.Add([PSCustomObject]@{
Subscription = $sub.Name
Dashboard = $dashboardName
ActionGroup = $agName
Status = 'Deployed'
DeployTime = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
})
}

# 输出部署汇总
Write-Host "`n========== 部署汇总 ==========" -ForegroundColor Yellow
$deployResults | Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--- 正在处理订阅:production ---
已切换到订阅:production-subscription
仪表板已部署:production-standard-dashboard
操作组已创建:ag-production-ops

--- 正在处理订阅:staging ---
已切换到订阅:staging-subscription
仪表板已部署:staging-standard-dashboard
操作组已创建:ag-staging-ops

--- 正在处理订阅:development ---
已切换到订阅:development-subscription
仪表板已部署:development-standard-dashboard
操作组已创建:ag-dev-ops

========== 部署汇总 ==========
Subscription Dashboard ActionGroup Status DeployTime
------------ --------- ----------- ------ ----------
production production-standard-dashboard ag-production-ops Deployed 2026-04-07 10:30:15
staging staging-standard-dashboard ag-staging-ops Deployed 2026-04-07 10:30:42
development development-standard-dashboard ag-dev-ops Deployed 2026-04-07 10:31:08

这个脚本的核心思路是将订阅特定的参数(资源组名、VM 前缀、告警邮箱)外置到配置数组中,然后通过循环统一调用前面定义的模板生成函数。你可以进一步将 $subscriptions 数组替换为从 CSV 或 Azure Key Vault 读取的配置,实现真正的配置与代码分离。

注意事项

  1. Az.Monitor 模块版本:建议使用 Az.Monitor 4.0 以上版本,旧版本的 Add-AzMetricAlertRuleV2 参数名和行为有差异。安装前先执行 Get-InstalledModule Az.Monitor 确认当前版本。

  2. 仪表板 JSON 深度问题:Azure Dashboard 的 JSON 嵌套层级较深(通常 8-10 层),使用 ConvertTo-Json 时必须指定 -Depth 10,否则内层数据会被截断为字符串。

  3. REST API Token 有效期:通过 Get-AzAccessToken 获取的 Bearer Token 默认有效期 1 小时。如果批量部署涉及大量订阅,建议在循环内每次都重新获取 Token,避免中途过期导致 401 错误。

  4. 告警规则配额限制:每个 Azure 订阅的指标告警规则数量有上限(默认 5000 条),跨订阅批量创建前应检查 Get-AzMetricAlertRuleV2 的返回数量,避免超出配额。

  5. 操作组的 Webhook 超时:Azure Action Group 发送 Webhook 时,超时时间为 10 秒。如果你的下游服务响应较慢,建议在中间加一个队列服务(如 Azure Functions + Service Bus),避免 Webhook 调用失败。

  6. 仪表板权限控制:通过 PowerShell 创建的仪表板默认只有创建者有编辑权限。如果需要团队成员共同维护,应通过 Azure RBAC 为资源组级别的 Microsoft.Portal/dashboards 资源分配 ContributorReader 角色。

PowerShell 技能连载 - Grafana 仪表板集成

适用于 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
# Grafana 连接配置
$grafanaConfig = @{
BaseUrl = "http://localhost:3000"
User = "admin"
Password = "admin"
}

# 构建 Basic Auth Header
$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
# 定义仪表板 JSON 结构
$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"

# 导出仪表板 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 仪表板至: GrafanaBackup_20251022

备份完成,共导出 4 个仪表板

仪表板 UID 面板数 文件大小 状态
-------- --- ------ -------- ----
系统总览 abc123xy 6 12.3 KB 已备份
API 响应时间 def456gh 3 5.7 KB 已备份
数据库性能 ghi789ij 5 9.1 KB 已备份
Kubernetes 集群监控 jkl012mn 8 18.4 KB 已备份

跨实例同步仪表板配置

在多环境(开发、测试、生产)部署场景中,保持 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
# 定义源和目标 Grafana 实例
$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

# 从源实例获取所有带 "sync" 标签的仪表板
$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
找到 3 个标记为同步的仪表板

同步结果:

仪表板 目标UID 目标版本 同步状态 同步时间
-------- ------- -------- -------- --------
系统总览 abc123xy 4 成功 14:35:12
API 响应时间 def456gh 2 成功 14:35:14
数据库性能 ghi789ij 3 成功 14:35:16

注意事项

  1. 认证安全:避免在脚本中硬编码 Grafana 用户名和密码。建议使用环境变量、Azure Key Vault 或 PowerShell SecretManagement 模块存储凭据。生产环境中优先使用 Grafana Service Account Token 而非管理员账号密码。

  2. API 权限控制:为自动化脚本创建专用的 Service Account,仅授予必要的权限(如 Dashboard Viewer、Dashboard Editor),避免使用全局管理员权限。通过最小权限原则降低误操作风险。

  3. JSON 序列化深度:Grafana 仪表板的 JSON 结构嵌套层级较深,使用 ConvertTo-Json 时务必指定足够的 -Depth 参数(建议 10 以上),否则深层配置可能被截断为字符串 System.Object[]

  4. 版本管理:每次通过 API 更新仪表板都会递增版本号。建议在 CI/CD 流水线中将导出的 JSON 文件纳入 Git 版本控制,实现仪表板配置的可追溯和回滚能力。

  5. 数据源依赖:仪表板中的查询面板依赖特定的数据源(如 Prometheus、InfluxDB)。跨实例同步时需确保目标实例已配置同名数据源,否则面板将显示查询错误。可先通过 /api/datasources 接口同步数据源配置。

  6. 并发控制:批量操作仪表板时,注意 Grafana 对并发请求的速率限制。在循环中使用 Start-Sleep 添加适当间隔(如 100-200 毫秒),或使用 -ThrottleLimit 参数控制并发度,避免触发 HTTP 429 Too Many Requests 错误。