Grafana使用指南:自动化常用的Grafana操作
Grafana是可能是最受欢迎的可视化软件,Hosted Grafana 是由 MetricFire提供的。用户每天需要执行特定的操作,其中大部分是重复性的。例如,可能希望自动创建包含不同文件夹的仪表板。本教程将介绍如何使用在DevOps社区非常受欢迎的Terraform来实现这一目标,并展示如何使用客户端库进一步自动化此过程。
如果你使用MetricFire的托管Grafana,你可以在几分钟内开始使用Grafana。你可以在这里检查免费试用版或预定演示会议直接向团队咨询。现在让我们更详细地了解自动化。
Terraform介绍
由于使用Terraform已经提供的[文档](https://www.terraform.io/docs/configuration/index.html)作为基础有些困难,因此我们将使用链接来查找更多帮助,并以简单的方式介绍最基本的信息。
Terraform的基本模块是资源。资源声明表示需要在哪个配置中创建什么。 Terraform背后的主要理念之一是,无论应用多少次相同的配置,最终结果都是相同的。
如果软件产品不同,那么可以创建和管理的对象也会不同。希望能够将这些软件产品理解为资源的命名空间。在Terraform术语中,称之为提供者。Grafana提供者是其中之一,并将在本教程中使用。
Terraform的代码都被写在以.tf结尾的文件中。当然,关于如何组织所有的代码,有各种不同的指南。你可以将代码分组为称为模块的单独单位。Terraform注册表中有一些公共模块。请查看其他不同的术语。
在Terraform中,还有诸如Terragrunt和Atlantis等自动化产品,可以帮助您实现更多的功能。由于本次涉及的内容过多,我们假设您已经熟悉Terraform语法和简单配置的应用方法,并在此基础上进行解释。
Grafana供应商
Grafana提供商允许管理仪表板、数据源、文件夹、组织和警报通知通道等资源。若要使用它,必须向提供商提供对Grafana的管理访问权限。请将以下提供商代码块添加到.tf文件中。
provider "grafana" {
url = "http://grafana.example.com/"
auth = "eyJrIjoicEXAMPLEEXAMPLEEXAMPLEEXAMPLEk15T2VBbkFCdTYiLCJuIjoidGVycmFmb3JtX3R1dG9yaWFsIiwiaWQiOjF9"
}
Auth可以是从Grafana获取的令牌,也可以是以冒号字符“:”分隔的用户名和密码组合之一。
当您进入个人设定菜单中的[API Key]部分时,您可以获得令牌。
点击[新 API 密钥],以管理员角色创建一个新的密钥。
接下来,获取一次性显示的关键字,需要将其保存在安全的地方。
这是一个必须粘贴到auth参数中,就像上面代码片段所示的密钥。但是,请不要担心。这只是本地Grafana生成的密钥示例。一旦更改了URL以指向Grafana实例,您就可以自由地探索各种Grafana提供者的资源。本文将总结所有资源的概述。
实践案例
这些示例已经在Terraform 0.12和Grafana 6.7.2中进行了测试。
文件夹
创建一个文件夹非常简单。
resource "grafana_folder" "collection" {
title = "Monitoring Systems"
}
当使用terraform apply应用此配置时,将会产生以下输出结果。
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# grafana_folder.collection will be created
+ resource "grafana_folder" "collection" {
+ id = (known after apply)
+ title = "Monitoring Systems"
+ uid = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
grafana_folder.collection: Creating...
grafana_folder.collection: Creation complete after 0s [id=1]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
然后会产生以下的结果。
数据源
在将仪表板导入到该文件夹之前,您需要创建一些用于这些仪表板的数据源。使用Grafana提供程序可以方便地完成。
根据所创建的数据源类型,所需传递的准确参数将不同。请查看此处的所有选项。本教程将展示如何创建Prometheus数据源。
resource "grafana_data_source" "metrics" {
type = "prometheus"
name = "metricfire"
url = "http://127.0.0.1:12345/"
}
仪表盘
有了文件夹和数据源之后,您可以导入一个可视化仪表盘,以展示来自该数据源的数据。您可以在内联中指定仪表盘的JSON对象,或者像以下示例那样使用文件函数。
resource "grafana_dashboard" "metrics" {
config_json = file("metricfire-dashboard.json")
}
很遗憾,Grafana仪表板资源不支持将仪表板导入到文件夹中,因此无法将仪表板导入到之前创建的文件夹中。运行前面的代码片段将会得到以下结果。
那个文件里只包含一些链接的简单仪表板。
警报通知渠道
此资源引用Grafana的本机警报机制所使用的通道。所需参数仅为名称和类型。所有其他设置都在配置键下。您可以在此处找到它们。
因此,您可以使用以下代码片段创建Grafana向Alertmanager返回警报通知的警报通知渠道。
resource "grafana_alert_notification" "am_integration" {
name = "Alertmanager"
type = "prometheus-alertmanager"
is_default = true
settings = {
url = "http://myalertmanager.com:1234"
basicAuthUser = "mybasicuser"
basicAuthPassword = "supersecret"
}
}
结果如下。
组织
最后,让我们来看一下Grafana提供程序目前支持的最后一个资源。组织是将仪表板和其他所有内容分离到单独的单元中的方法。这些可以按团队、服务或公司内有意义的抽象级别进行创建。
创建新的组织并添加用户非常容易。您可以使用以下代码片段:
resource "grafana_organization" "CoolProduct" {
name = "My Cool Product"
admin_user = "admin"
create_users = false
admins = [
"admin@localhost"
]
editors = [
"foobar@foobar.com"
]
viewers = [
"bazbaz@bazbaz.com"
]
}
如果create_users为true,则会创建占位符用户,但只会添加已经存在且符合指定角色的用户,若为false则不会创建。
应用以上的代码后,会变成如下:
请注意
使用Terraform和其Grafana供应商,您可以执行所有这些操作,但是有一些重要的注意事项。例如,您不能在不同的组织中创建文件夹、数据源或其他资源。您只能在主要组织中执行此操作。这是因为Grafana没有提供用于在这些组织之间导航的非常出色的RESTful API。换句话说,Grafana会将服务器端状态(如当前选择的组织)保存到用户中。这意味着没有很好的方法将其与Terraform的理念集成起来。虽然有一些尝试来实现这一点,但官方尚未提供。
所以,要在Grafana中完全自動化所有操作,需要切换到Go等通用编程语言。
用Go语言实现自动化动作。
有一个非常受欢迎的SDK,可以用于自动化Go中的Grafana操作。这个SDK被称为grafana-tools/sdk。本文介绍了可以用作基线的最低限度的程序,并展示了一些简单的测试,以增强自动化功能的可信度。
根据作者观点,Go语言相对容易理解与使用,制作Go应用程序也比较简单。此外,由于本文不是语言教程,需要有一定的经验基础。
裸奔程序
原始程序中有一些代码,可以连接到Grafana实例并执行各种操作。这与使用Terraform代码开始的方法相同,使用Grafana提供程序块。
要使用SDK来实现这一点,需要调用sdk.NewClient方法来创建一个新的客户端。在这里,可以使用基本认证或API密钥之一。在这种情况下,我们将使用基本认证。
package main
import (
"github.com/grafana-tools/sdk"
)
func main() {
client := sdk.NewClient("http://localhost:3000", "admin:admin", sdk.DefaultHTTPClient)
var _ = client
}
由于Go语言中未使用的变量会导致编译错误,因此添加了var _ = client部分。现在,您可以使用客户端来实现在Grafana中想要做的任何事情。例如,让我们创建以下三个组织。
-
- Metric
-
- Fire
- IsAwesome
package main
import (
"context"
"fmt"
"github.com/grafana-tools/sdk"
)
func main() {
client := sdk.NewClient("http://localhost:3000", "admin:admin", sdk.DefaultHTTPClient)
for _, org := range []string{"Metric", "Fire", "IsAwesome"} {
sm, err := client.CreateOrg(context.Background(), sdk.Org{Name: org})
// TODO: add proper error handling.
fmt.Println(sm, err)
}
}
然后,会产生以下结果。
此外,即使多次执行Grafana的API,也不会引发错误。相反,将返回成功,因为已存在且最终结果相同。
$ ./sdk_test
{<nil> <nil> 0xc000184110 <nil> <nil> <nil> <nil> <nil>} <nil>
{<nil> <nil> 0xc0001841f0 <nil> <nil> <nil> <nil> <nil>} <nil>
{<nil> <nil> 0xc000012ee0 <nil> <nil> <nil> <nil> <nil>} <nil>
程序通过fmt.Println输出一些诊断信息。如您所见,第二列始终是,并且表示没有发生错误。
自动化测试
使用通用的编程语言具有巨大的能力,也伴随着巨大的责任。对代码进行不断地测试是必要的。让我们来编写一些自动化程序的单元测试。
通过将代码分离到各种函数和模块中,可以对它们进行单独的测试。我们来执行它们以用于我们的程序。当然,实际的程序会更加复杂。这只是一个简单的例子。代码如下:
package main
import (
"context"
"fmt"
"github.com/grafana-tools/sdk"
)
// Preparator is a struct for holding state related to our Grafana
// automation.
type Preparator struct {
c *sdk.Client
}
// PrepareOrg prepares an organization for our use with the given name.
func (p *Preparator) PrepareOrg(ctx context.Context, orgName string) error {
_, err := p.c.CreateOrg(ctx, sdk.Org{Name: orgName})
return err
}
func main() {
client := sdk.NewClient("http://localhost:3000", "admin:admin", sdk.DefaultHTTPClient)
prep := &Preparator{c: client}
for _, org := range []string{"Metric", "Fire", "IsAwesome"} {
// TODO: add proper error handling.
fmt.Printf("creating org %s: %v\n", org, prep.PrepareOrg(context.TODO(), org))
}
}
现在,您可以创建一个简单的测试案例来检查PrepareOrg函数是否正常工作。目前,我们只需要确保组织存在而无需进行其他操作。这就是我们要检查的内容。
以下是简单的测试代码旧你参考:
package main
import (
"context"
"testing"
"github.com/grafana-tools/sdk"
)
// Tests whether preparator prepares a given organization properly.
func TestPreparatorPrepareOrg(t *testing.T) {
testcases := []string{"a", "b", "c", "d", "e", "f"}
// TODO: refactor this into a separate place.
client := sdk.NewClient("http://localhost:3000", "admin:admin", sdk.DefaultHTTPClient)
prep := &Preparator{c: client}
for _, org := range testcases {
if err := prep.PrepareOrg(context.TODO(), org); err != nil {
t.Fatalf("preparing org %v: %v", org, err)
}
// Now let's check if it has been created successfully.
if _, err := client.GetOrgByOrgName(context.TODO(), org); err != nil {
t.Fatalf("getting org %v: %v", org, err)
}
}
}
当你执行它时,
go test -v ./...
=== RUN TestPreparatorPrepareOrg
--- PASS: TestPreparatorPrepareOrg (0.28s)
PASS
ok sdk_test 0.286s
在阅读完SDK文档后,请尝试自动化所需的操作。