Grafana Grafana

使用 Terraform 管理 Grafana

通过 HCL 创建文件夹、数据源、仪表板、告警、团队和 service account。

15 min 中级

文件 provisioning 涵盖数据源、仪表板和告警,但不包括用户、团队、service account 或权限。Terraform 涵盖所有这些,通过 API 针对任何运行中的 Grafana(自托管或 Grafana Cloud)工作,并保存一个状态文件,以便检测并纠正漂移。本指南设置 provider 并管理常见资源。

先决条件与 provider

你需要 Terraform 或 OpenTofu、一个可访问的 Grafana,以及一个其角色可以管理你所声明内容的 service account 令牌(创建方法参见认证指南)。不要把令牌写进代码:

terraform {
  required_providers {
    grafana = {
      source  = "grafana/grafana"
      version = ">= 2.9.0"   # pin to the version you have tested
    }
  }
}

variable "grafana_url" {
  type    = string
  default = "http://localhost:3000"
}

variable "grafana_token" {
  type      = string
  sensitive = true
}

provider "grafana" {
  url  = var.grafana_url
  auth = var.grafana_token
}

通过环境变量传入令牌,使它永远不会落到文件里:

export TF_VAR_grafana_token='glsa_...'
terraform init

对于 Grafana Cloud,你可以在同一份配置中创建 stack 及其 service account。Grafana 文档展示了一种双 provider 的模式:一个使用 Cloud access policy 令牌来创建 stack(grafana_cloud_stack)和 service account(带其令牌的 grafana_cloud_stack_service_account)的 cloud provider,以及第二个以该 stack 为别名、使用该令牌的 provider。

文件夹与数据源

resource "grafana_folder" "apps" {
  title = "Applications"
}

resource "grafana_data_source" "prometheus" {
  type       = "prometheus"
  name       = "Prometheus"
  uid        = "prometheus"
  url        = "http://prometheus:9090"
  is_default = true

  json_data_encoded = jsonencode({
    timeInterval = "15s"
  })
}

resource "grafana_data_source" "postgres" {
  type = "postgres"
  name = "Postgres"
  uid  = "postgres-main"
  url  = "postgres.internal:5432"
  username = "grafana"

  json_data_encoded = jsonencode({
    database = "app"
    sslmode  = "require"
  })

  secure_json_data_encoded = jsonencode({
    password = var.postgres_password
  })
}

显式设置 uid 能让仪表板和告警规则在各环境之间保持可移植。通过 secure_json_data_encoded 传入的 secret 会被 Grafana 加密存储,但它们也会被写入 Terraform 状态,所以请将状态存放在加密且有访问控制的后端中。

仪表板

resource "grafana_dashboard" "webapp" {
  folder      = grafana_folder.apps.uid
  config_json = file("${path.module}/dashboards/webapp.json")
  overwrite   = true
}

使用 fileset 加载整个目录:

resource "grafana_dashboard" "all" {
  for_each    = fileset("${path.module}/dashboards", "*.json")
  folder      = grafana_folder.apps.uid
  config_json = file("${path.module}/dashboards/${each.value}")
}

仪表板 JSON 与你用于文件 provisioning 的是同一个文件,所以你可以在两种方式之间切换。把仪表板挂到文件夹时,较旧的 provider 版本引用文件夹的 id,而较新的则倾向于其 uid;请查阅你所固定版本的 provider 文档。

告警

Terraform 为告警的每个部分都提供了资源:

resource "grafana_contact_point" "slack" {
  name = "team-slack"

  slack {
    url       = var.slack_webhook_url
    recipient = "#alerts"
  }
}

resource "grafana_notification_policy" "default" {
  group_by      = ["alertname", "namespace"]
  contact_point = grafana_contact_point.slack.name

  group_wait      = "30s"
  group_interval  = "5m"
  repeat_interval = "4h"
}

规则组使用带 rule 块的 grafana_rule_group,其 data 条目与 provisioning YAML 相对应(查询、reduce、阈值)。静默时段和模板有 grafana_mute_timing 和 grafana_message_template。请在 provider 文档中查找确切的属性名称,因为告警资源在 provider 版本之间有过修订。

grafana_notification_policy 像文件 provisioning 一样管理整个策略树,所以请确保只有一个工具拥有它。

团队、service account 与权限

这些是文件无法表达的资源:

resource "grafana_team" "platform" {
  name  = "platform"
  email = "platform@example.com"
}

resource "grafana_service_account" "ci" {
  name = "ci-dashboards"
  role = "Editor"
}

resource "grafana_service_account_token" "ci" {
  name               = "ci-token"
  service_account_id = grafana_service_account.ci.id
}

resource "grafana_folder_permission" "apps" {
  folder_uid = grafana_folder.apps.uid

  permissions {
    team_id    = grafana_team.platform.id
    permission = "Edit"
  }
}

把生成的令牌当作 secret 输出,并将它推入你的 secret 管理器,而不是把它打印出来。

工作流

terraform fmt -check
terraform validate
terraform plan -out tfplan
terraform apply tfplan

在 CI 中,于拉取请求上运行 plan,合并时运行 apply。plan 会确切显示哪些仪表板、数据源和告警将会改变。

纳管已有的内容

如果有人在 UI 中构建了东西,请把它们导入状态,而不是重新创建:

terraform import grafana_folder.apps <folder-uid>
terraform import grafana_dashboard.webapp <dashboard-uid>
terraform import grafana_data_source.prometheus <data-source-uid>

较新的 Terraform 版本也可以使用 import 块。导入 ID 的格式可能因 provider 版本而异,所以请在资源文档中确认。

Terraform 与文件 provisioning 并用

不要用两者同时管理同一个资源。选定一个边界并坚持下去。常见的划分是:数据源和仪表板用文件 provisioning(启动时加载,不需要 API 令牌),团队、service account 和权限用 Terraform。另一种是全部用 Terraform,而 provisioning 文件仅限于服务器设置。

热门指南