一次编辑,更新所有仪表板:使用 Terraform 大规模管理 Kibana 可观测性配置

0 阅读11分钟

作者:来自 Elastic Jeffrey Rengifo

只需在共享的 HCL 库中定义一次你的黄金信号(golden signals)面板,然后使用 for_each 为每个团队生成对应的仪表板,同时内置配置漂移检测和 Git 回滚能力。

Elastic 提供了 Kibana Dashboards API 和原生 Terraform resource,用于以代码方式管理仪表板。该能力在 Elastic 9.4 中以技术预览(technical preview)形式推出,并在 Elastic 9.5 中正式发布(GA)。

你只需要在 HCL 中定义一次黄金信号(golden signals)面板库,然后通过 for_each 从该库为每个团队生成一个仪表板。当你需要修改错误阈值、面板布局或查询时,只需提交一个 Pull Request,就可以一次性更新所有团队的仪表板。

如果发生配置漂移(drift)或出现问题,你可以通过 Git 回滚进行恢复。

为什么手动管理可观测性仪表板在大规模场景下会失效

大型组织通常会拥有数百个仪表板。各团队会创建类似的面板,并通过 Kibana UI 进行维护。

当需要进行小范围修改时(例如重命名面板、修复字段、添加新的错误阈值),没有简单的方法可以将更改应用到所有仪表板中。你要么逐个打开每个仪表板,在 UI 中进行编辑;要么导出 NDJSON,执行字符串替换,然后重新导入。

仪表板现在也是代码

Elastic 提供了类型化的 Kibana Dashboards API 和原生的elasticstack_kibana_dashboard Terraform resource。你可以在 HCL 文件中定义一个仪表板,然后像管理普通代码一样管理版本和变更。

黄金信号仪表板:一个定义,供所有团队使用

平台团队负责维护一个基于四个黄金信号(golden signals)构建的标准仪表板:延迟(latency)、流量(traffic)、错误(errors)和饱和度(saturation)。

每个团队都应该获得这个标准仪表板,同时部分团队可以添加一两个自己的面板。

我们的目标是:

  • 只维护一个标准定义;

  • 从该定义生成每个团队的仪表板;

  • 一次修改即可同步到所有团队。

前置条件

  • 运行 Elastic 9.4 或更高版本的 Elastic Cloud 部署或自建集群,或者 Elastic Cloud Serverless 项目;

  • 已安装 Terraform;

  • 一个 Elasticsearch API key。

本文使用的完整 Terraform 配置、初始化脚本(seed script)以及捕获的 Terraform plan 输出,都可以在配套代码仓库中获取。

配置 Elastic Terraform provider

在其他 Terraform 文件旁边创建一个 [provider.tf](https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/provider.tf "provider.tf")

`

1.  terraform {
2.    required_providers {
3.      elasticstack = {
4.        source  = "elastic/elasticstack"
5.        version = "~> 0.11"
6.      }
7.    }
8.  }

10.  variable "elasticsearch_endpoint" {
11.    type = string
12.  }

14.  variable "elasticsearch_api_key" {
15.    type      = string
16.    sensitive = true
17.  }

19.  variable "kibana_endpoint" {
20.    type = string
21.  }

23.  variable "kibana_api_key" {
24.    type      = string
25.    sensitive = true
26.  }

28.  provider "elasticstack" {
29.    elasticsearch {
30.      endpoints = [var.elasticsearch_endpoint]
31.      api_key   = var.elasticsearch_api_key
32.    }
33.    kibana {
34.      endpoints = [var.kibana_endpoint]
35.      api_key   = var.kibana_api_key
36.    }
37.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

通过本地 terraform.tfvars 文件提供你的凭据(并将其添加到 .gitignore 中,以确保密钥不会进入代码仓库):

`

1.  elasticsearch_endpoint = "https://...es.region.cloud.es.io"
2.  elasticsearch_api_key  = "..."
3.  kibana_endpoint        = "https://...kb.region.cloud.es.io"
4.  kibana_api_key         = "..."

`AI写代码

只要该 API key 在目标 space 中具有仪表板写入权限(dashboard write privileges),你可以将同一个 API key 同时用于 elasticsearch_api_keykibana_api_key

然后初始化工作目录:

`terraform init` AI写代码

在 HCL 中定义单个团队的 Kibana 仪表板

首先,为单个团队创建一个基准仪表板。面板位于一个 48 列的网格布局中,每个面板都是一个内联配置的 Lens 可视化对象。

对于 KPI 卡片,使用 config_json(它支持辅助指标和数值颜色配置);对于时间序列图表,使用 xy_chart_config

将基础资源添加到新的 [dashboards.tf](https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf "dashboards.tf") 文件中:

``

1.  resource "elasticstack_kibana_dashboard" "golden_signals" {
2.    title            = "Golden Signals - payments"
3.    description      = "Latency, traffic, errors"
4.    query            = { language = "kql", text = "" }
5.    refresh_interval = { pause = false, value = 60000 }
6.    time_range       = { from = "now-15m", to = "now" }

8.    panels = [
9.      {
10.        type = "vis"
11.        grid = { x = 0, y = 0, w = 12, h = 5 }
12.        config_json = jsonencode({
13.          type        = "metric"
14.          data_source = {
15.            type  = "esql"
16.            query = "FROM logs-payments-* | STATS `5xx errors` = COUNT(CASE(status >= 500, 1, null))"
17.          }
18.          metrics = [{ type = "primary", column = "5xx errors" }]
19.        })
20.      },
21.      # More panels follow the same shape: other metric tiles, xy_chart_config line charts, and a breakdown datatable. See the companion repo for the full file.
22.    ]
23.  }

``AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

每个面板都会设置类型和网格位置,然后选择一种图表类型。

KPI 卡片会将完整的 Lens 配置序列化到 config_json 中;ES|QL 查询位于 data_source 下,而指标列则通过 metrics[*].column 中的名称进行引用。

仪表板的时间选择器(time picker)已经会自动限定 ES|QL 面板的数据范围,因此查询中无需显式添加 @timestamp 范围过滤条件。

使用 terraform plan 预览 Kibana 仪表板变更

运行 terraform plan 查看 Terraform 将要创建的内容:

`terraform plan` AI写代码

plan 输出会列出新的 elasticstack_kibana_dashboard.golden_signals resource,以及它将要设置的每个属性:包括顶层仪表板字段,以及每个面板对应的条目,其中包含面板的网格位置、图表类型和数据源。

`

1.  Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
2.    + create

4.  Terraform will perform the following actions:

6.    # elasticstack_kibana_dashboard.golden_signals will be created
7.    + resource "elasticstack_kibana_dashboard" "golden_signals" {
8.        + description      = "Latency, traffic, errors"
9.        + title            = "Golden Signals - payments"
10.        + query            = { language = "kql", text = "" }
11.        + refresh_interval = { pause = false, value = 60000 }
12.        + time_range       = { from = "now-15m", to = "now" }
13.        + panels           = [
14.            # Every panel described in full: KPI tiles (config_json),
15.            # line charts (xy_chart_config), and the breakdown datatable.
16.          ]
17.      }

19.  Plan: 1 to add, 0 to change, 0 to destroy.

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

检查 plan 是在将任何内容发布到 Kibana 之前的最后一步。

现在不要执行 apply。下一节会扩展该文件,添加按团队生成的仪表板,然后通过一次 terraform apply 部署所有内容。

从共享面板库生成每个团队的可观测性仪表板

在基础仪表板之上,每个团队都会获得一组标准面板,同时可以选择添加少量自己的面板。为每个团队硬编码一个 resource 的方式无法扩展。

相反,可以将面板库和团队映射定义为 locals,然后使用 for_each 构建仪表板。

面板库中的每个条目都会描述一种图表类型、一个标题以及所需的数据;resource 会根据 chart_type 自动生成对应的 Lens 配置块(指标卡片使用 config_json,折线图使用 xy_chart_config)。

[dashboards.tf](https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf "dashboards.tf") 的内容替换为:

``

1.  locals {
2.    panel_library = {
3.      errors = {
4.        chart_type     = "metric"
5.        title          = "Error rate"
6.        esql_query_tpl = "FROM {idx} | STATS `5xx errors` = COUNT(CASE(status >= 500, 1, null))"
7.        esql_column    = "5xx errors"
8.      }
9.      saturation = {
10.        chart_type     = "metric"
11.        title          = "Saturation (CPU)"
12.        # Saturation reads from the metrics TSDB, so this query is not parameterized by {idx}.
13.        esql_query_tpl = "TS metrics-payments-* | STATS avg_cpu = AVG(cpu.pct)"
14.        esql_column    = "avg_cpu"
15.      }
16.      latency = {
17.        chart_type = "xy"
18.        title      = "Latency p95"
19.        x_json     = jsonencode({
20.          operation          = "date_histogram"
21.          field              = "@timestamp"
22.          suggested_interval = "auto"
23.        })
24.        y_json = jsonencode({
25.          operation  = "percentile"
26.          field      = "duration_ms"
27.          percentile = 95
28.        })
29.      }
30.      # ... more entries (traffic, cart_value) in the companion repo.
31.    }

33.    teams = {
34.      payments = {
35.        index  = "logs-payments-*"
36.        panels = ["errors", "saturation", "latency", "traffic"]
37.      }
38.      checkout = {
39.        index  = "logs-checkout-*"
40.        panels = ["errors", "cart_value", "latency", "traffic"]
41.      }
42.    }
43.  }

45.  resource "elasticstack_kibana_dashboard" "golden_signals" {
46.    for_each         = local.teams
47.    title            = "Golden Signals - ${each.key}"
48.    description      = "Latency, traffic, and errors for the ${each.key} service"
49.    query            = { language = "kql", text = "" }
50.    refresh_interval = { pause = false, value = 60000 }
51.    time_range       = { from = "now-15m", to = "now" }

53.    sections = [
54.      {
55.        title     = "KPIs"
56.        grid      = { y = 0 }
57.        collapsed = false
58.        panels = [
59.          for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "metric"] : {
60.            type        = "vis"
61.            grid        = { x = (i % 4) * 12, y = 0, w = 12, h = 5 }
62.            config_json = jsonencode({ ... }) # one metric tile per panel; see the companion repo for the full config
63.          }
64.        ]
65.      },
66.      {
67.        title     = "Trends"
68.        grid      = { y = 1 }
69.        collapsed = false
70.        panels = [
71.          for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "xy"] : {
72.            type       = "vis"
73.            grid       = { x = (i % 3) * 16, y = 0, w = 16, h = 10 }
74.            vis_config = { by_value = { xy_chart_config = { ... } } }
75.          }
76.        ]
77.      },
78.      # A third "Breakdown" section holds the request-by-status datatable. See the companion repo.
79.    ]
80.  }

``AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)收起代码块![](https://csdnimg.cn/release/blogv2/dist/pc/img/arrowup-line-top-White.png)

添加一个团队只需要在 teams 中增加一条配置。向所有团队添加一个面板,只需要在 panel_library 中增加一条配置,并在每个团队中添加一个引用。

完整配置(包括数据源 ES|QL 查询、指标、图层、坐标轴默认设置以及图例位置)都位于 dashboards.tf 中。

饱和度(saturation)面板会通过 ES|QL 的 TS 命令查询 metrics 数据流,该命令专为 TSDB(时间序列数据库)设计。要使查询正常工作,与 metrics-payments-* 匹配的数据流必须使用 time_series 模式,因此配置中还提供了一个索引模板(metrics_tsdb.tf),用于启用该模式。

使用 terraform apply 将代码形式的仪表板应用到 Kibana

运行 terraform plan 确认将创建两个团队仪表板(payments 和 checkout),然后执行 apply:

`terraform apply` AI写代码

打开 Kibana,你会看到每个团队都有一个黄金信号(Golden Signals)仪表板,并且每个仪表板都由各自的索引模式(index pattern)支持。

GitOps 流程中的代码化仪表板:在 Pull Request 中审查变更

现在,仪表板已经成为版本控制中的一种资源,就像其他基础设施一样。

你可以编辑面板库或某个团队的配置选择,然后创建一个 Pull Request。审查者可以查看 Terraform plan 的差异,并了解哪些仪表板会发生变化。

例如,假设你将 panel_library.errors.esql_query_tpl 中的“严重错误”(critical error)阈值从 status >= 500 收紧为 status >= 503

运行 terraform plan 后,可以看到该变更会同时应用到两个团队:

注意:完整输出请参见 terraform-plan-update.txt

panel_library.errors 的一次修改,会传播到所有引用它的团队。

Pull Request 合并后,就可以运行 terraform apply

执行 apply 完成后,刷新 Kibana 中的仪表板,新阈值即可生效:

检测仪表板漂移并通过 Git 回滚

如果有人通过 UI 编辑了仪表板,下一次运行 terraform plan 时会显示差异,因为代码状态和实际运行状态已经不再匹配。

要实际体验这一点,请在 Kibana 中打开 Golden Signals - payments 仪表板,将 Latency p95 面板重命名为 Latency p95 (EDITED),然后保存该仪表板。

然后运行 terraform plan

Terraform 会从实际运行中的仪表板读取面板标题,将其与代码中的定义进行比较,并提出撤销 UI 重命名的变更。

你可以决定保留该修改(更新代码以匹配实际状态),或者通过运行 terraform apply 将其回滚。

你可以提交新版本,也可以使用 Git 回滚一个或多个版本。

回顾之前的示例:如果你重新打开修改 panel_library.errors 的 Pull Request,该修改用于扩大错误阈值范围并添加更清晰的标题,那么 git diff dashboards.tf 会用两行内容展示完整变更意图:

每个引用 errors 的团队都会在下一次 terraform apply 时获取新的阈值,而回滚该提交会一次性将所有团队的变更恢复。

总结

手动管理 Kibana 可观测性仪表板无法在超过几个团队的规模下继续扩展。通过 Kibana Dashboards API 和 Terraform,你可以一次定义标准,通过共享面板库组合每个团队的仪表板,并在 Pull Request 中审查每一次变更。

一次编辑即可影响所有团队,并且可以通过回滚提交来恢复变更。

本文提出的文件结构只是众多组织方式中的一种,你可以根据仪表板之间共享信息的程度,以不同方式组织你的仪表板。

后续步骤

原文:Kibana observability dashboards as code with Terraform — Elastic Observability Labs