前文最后总结了我的工具选型:
我们今天先简单介绍 Grafana Terraform provider.
Grafana provider 为 Grafana 提供配置管理资源。是目前 Grafana 官方提供的,覆盖的 Grafana 资源最全的 IaC 工具。
Grafana Terraform Provider 的代码是建立在 grafana-api-golang-client 之上的。
通过 Grafana Terraform Provider, 我们可以管理:
grafana_contact_point
grafana_message_template
grafana_mute_timing
grafana_notification_policy
grafana_rule_group
grafana_cloud_access_policy
grafana_cloud_access_policy_token
grafana_cloud_api_key
grafana_cloud_plugin_installation
grafana_cloud_stack
grafana_cloud_stack_api_key
grafana_cloud_stack_service_account
grafana_cloud_stack_service_account_token
grafana_machine_learning_holiday
grafana_machine_learning_job
grafana_machine_learning_outlier_detector
grafana_cloud_ips
grafana_cloud_organization
grafana_cloud_stack
grafana_builtin_role_assignment
grafana_data_source_permission
(AWS Managed Grafana 也有这个功能)grafana_report
grafana_role
grafana_role_assignment
grafana_team_external_group
grafana_annotation
grafana_api_key
grafana_dashboard
grafana_dashboard_permission
grafana_data_source
grafana_folder
grafana_folder_permission
grafana_library_panel
grafana_organization
grafana_organization_preferences
grafana_playlist
grafana_service_account
grafana_service_account_permission
grafana_service_account_token
grafana_team
grafana_team_preferences
grafana_user
grafana_dashboard
grafana_dashboards
grafana_data_source
grafana_folder
grafana_folders
grafana_library_panel
grafana_organization
grafana_organization_preferences
grafana_team
grafana_user
grafana_users
因为 Grafana 资源相对比较清晰和独立,不像 AWS 会有很多复杂的关联关系。
所以关于 Grafana TF 代码的组织形式可以简单点:
.tf
文件├── dashboard.tf ├── datasource.tf ├── grafana-ds-info.auto.tfvars.json ├── jsonnet (jsonnet 文件夹,dashboard 相关内容都在该文件夹下) ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf
下面以第二种组织结构来详细介绍。
在 main.tf
中,创建 Grafana Provider:
provider "grafana" { }
如果只有一套 Grafana, 那么如上的配置完全就够用了。
如果有多套 Grafana, 则可以通过指定 Grafana provider 的 alias
来实现。具体如下:
provider "grafana" { alias = "aws-managed-grafana" }
后续使用资源的时候,可以通过指定 provider
来区分,实例如下:
# provision folder resource "grafana_folder" "play-grafana" { provider = grafana.aws-managed-grafana uid = "play-grafana" title = "play-grafana" }
📝Notes:
后续为了演示代码的简洁,不展示多 Grafana provider 的情况。
Resource 里也不会有provider
字段。
Grafana 通过 Terraform 使用,是至少需要提供 url 和 apikey 2 类信息的。这 2 类信息可以直接通过环境变量的形式提供,具体如下:
export GRAFANA_URL=https://<your-grafana-domain>/ export GRAFANA_AUTH=<your-grafana-apikey>
GRAFANA_AUTH
的值可以是一个 Grafana API 密钥,basic auth 就是 用户名:密码
,或可以点击这个链接申请 Grafana API 密钥。
除此之外,Grafana Cloud/Synthetic Monitoring/Grafana Oncall 会有一些专用的 apikey 或 token, 这里就不详细介绍了。
📝Notes:
因为我主要用的是 AWS Managed Grafana, 其只有一个默认的 org 1. 也没有开放相关的创建多个 org 的组织。所以我基本上不会用到该资源。
如果有用到该资源,可以创建一个 org.tf
, 具体内容是:
// 创建组织 resource "grafana_organization" "my_org" { name = "my_org" } // 在组织内创建资源 provider "grafana" { alias = "my_org" org_id = grafana_organization.my_org.org_id } resource "grafana_folder" "my_folder" { provider = grafana.my_org title = "Test Folder" }
该资源所需的参数根据所选择的数据源类型(通过 type
参数)而有所不同。
可以在 datasource.tf
下创建。
以下是创建:
的简单示例。
resource "grafana_data_source" "arbitrary-data" { type = "stackdriver" name = "sd-arbitrary-data" json_data_encoded = jsonencode({ "tokenUri" = "https://oauth2.googleapis.com/token" "authenticationType" = "jwt" "defaultProject" = "default-project" "clientEmail" = "client-email@default-project.iam.gserviceaccount.com" }) secure_json_data_encoded = jsonencode({ "privateKey" = "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n" }) }
resource "grafana_data_source" "influxdb" { type = "influxdb" name = "myapp-metrics" url = "http://influxdb.example.net:8086/" basic_auth_enabled = true basic_auth_username = "username" database_name = influxdb_database.metrics.name json_data_encoded = jsonencode({ authType = "default" basicAuthPassword = "mypassword" }) }
基于 AKSK 的创建:
resource "grafana_data_source" "cloudwatch" { type = "cloudwatch" name = "cw-example" json_data_encoded = jsonencode({ defaultRegion = "us-east-1" authType = "keys" }) secure_json_data_encoded = jsonencode({ accessKey = "123" secretKey = "456" }) }
这是基于 role (external) 的创建:
resource "grafana_data_source" "cloudwatch" { type = "cloudwatch" name = "example_cw" json_data_encoded = jsonencode({ assumeRoleArn = "arn:aws:iam::<the-aws-id>:role/<...>" authType = "ec2_iam_role" defaultRegion = "us-east-1" externalId = "<the-aws-id>" }) }
resource "grafana_data_source" "zabbix" { type = "alexanderzobnin-zabbix-datasource" name = "Zabbix-example" url = "http://<zabbix-domain>/api_jsonrpc.php" json_data_encoded = jsonencode({ trends = true username = "Admin" }) secure_json_data_encoded = jsonencode({ password = "Password" }) }
🐾 注意:
Zabbix 的 type 是
alexanderzobnin-zabbix-datasource
使用的前提是安装 Zabbix Grafana 插件.
resource "grafana_data_source" "jaeger-example" { type = "jaeger" name = "example_jaeger" uid = "example_jaeger" url = "http://<jaeger-domain>/trace/" json_data_encoded = jsonencode({ "nodeGraph" : { "enabled" : true } }) } data "grafana_data_source" "jaeger-example" { name = grafana_data_source.jaeger-example.name uid = grafana_data_source.jaeger-example.uid }
📝上面的 data "grafana_data_source" "jaeger-example"
是将 Jaeger Datasource 的 uid 提供给 ES 使用。
当然,如果你直接在创建 Jaeger Datasource 的时候指定了 uid, 如下所示,那么后面在被其他 Datasource 引用时可以直接指定写死。
uid = "example_jaeger"
resource "grafana_data_source" "elasticsearch-example" { type = "elasticsearch" name = "es_example" uid = "es_example" url = "http://<es_host>:9200" // 就是 es index database_name = "[example.*-]YYYY.MM.DD" json_data_encoded = jsonencode({ esVersion = "6.0.0" interval = "Daily" includeFrozen = false maxConcurrentShardRequests = 256 timeField = "@timestamp" logLevelField = "level" logMessageField = "_source" dataLinks = [ { datasourceUid = data.grafana_data_source.jaeger-example.uid // 或 datasourceUid = "example_jaeger" field = "trace_id", url = "${"$"}{__value.raw}" } ] }) }
这里,有以下几个需要注意的地方:
database_name = "[example.*-]YYYY.MM.DD"
在 type 为 es 的情况下,database_name 就是 es 的索引名称dataLinks
这里通过 data link 链接到 Jagger Datasource: datasourceUid = data.grafana_data_source.jaeger-example.uid
(Jaeger Datasource 就是上一节创建的)url = "${"$"}{__value.raw}"
这里要特别注意,实际上传给 Grafana 的是:${__value.raw}
, 但是这个恰好也是 Terraform 的模板/变量替换语法,所以如果直接这样写会将其解析为模板/变量,从而出现该变量不存在的报错。通过${"$"}
转义为 $
+ {__value.raw}
拼成正确的 ${__value.raw}
传给 Grafana.基础配置如下:
resource "grafana_data_source" "prometheus" { type = "prometheus" name = "example_prom" uid = "example_prom" url = "http://my-instances.com" json_data_encoded = jsonencode({ httpMethod = "POST" }) }
官方提供的 Prometheus 兼容实现 - Mimir 的配置如下:
resource "grafana_data_source" "prometheus" { type = "prometheus" name = "mimir" url = "https://my-instances.com" basic_auth_enabled = true basic_auth_username = "username" json_data_encoded = jsonencode({ httpMethod = "POST" prometheusType = "Mimir" prometheusVersion = "2.4.0" }) secure_json_data_encoded = jsonencode({ basicAuthPassword = "password" }) }
在 dashboard.tf
中,创建 dashboard 示例如下:
resource "grafana_dashboard" "metrics" { config_json = file("grafana-dashboard.json") }
也可以通过如下方式创建:
resource "grafana_dashboard" "metrics" { config_json = jsonencode({ title = "as-code dashboard" uid = "ascode" }) }
🐾注意:
config_json
是 String 类型,具体是完整的 Dashboard model JSON。
可以直接通过 file("grafana-dashboard.json")
获取。
如第二个实例,jsonencode
的作用就是使用 JSON 语法将一个 Object 转换为 String.
好了,本次我们介绍了 Grafana Terraform Provider 的基础知识,还是比较简单的,我们使用其:
非常直白清晰。希望对各位有所帮助。