Grafanaの使い方ガイド：よく使われるGrafanaアクションの自動化

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/77b83b7c-dcb7-392a-9f4b-4bc046fb032a.png)

Grafanaはおそらく最も人気のある可視化ソフトウェアであり、Hosted Grafanaは[MetricFire](https://www.metricfire.com/japan/?utm_source=blog&utm_medium=Qiita&utm_campaign=Japan&utm_content=Grafana%20Tutorial%3A%20Automating%20Common%20Grafana%20Actions)によって提供されています。 毎日、ユーザーは特定のアクションを実行する必要があり、それらのほとんどは反復的なものではあります。 たとえば、ダッシュボードを含むさまざまなフォルダを自動的に作成したい場合があります。 このチュートリアルでは、DevOpsコミュニティーで非常に人気のあるTerraformを使用してこれを行う方法と、クライアントライブラリを使用してさらに自動化する方法を紹介していきます。

[MetricFire](https://www.metricfire.com/japan/?utm_source=blog&utm_medium=Qiita&utm_campaign=Japan&utm_content=Grafana%20Tutorial%3A%20Automating%20Common%20Grafana%20Actions)のマネージされたGrafanaを利用すれば数分以内にGrafanaの使用を開始できます。 ここで[無料トライアル](https://www.hostedgraphite.com/signup?utm_source=blog&utm_medium=Qiita&utm_campaign=Japan&utm_content=Grafana%20Tutorial%3A%20Automating%20Common%20Grafana%20Actions)をチェックするか、[デモを予約](https://www.metricfire.com/demo-japan/?utm_source=blog&utm_medium=Qiita&utm_campaign=Japan&utm_content=Grafana%20Tutorial%3A%20Automating%20Common%20Grafana%20Actions)してチームに直接ご相談ください。 それでは、自動化について詳しく見ていきましょう。

#Terraformの紹介
Terraformがすでに持っている[ドキュメント}(https://www.terraform.io/docs/configuration/index.html)をベースにするのは難しいので、より多くのヘルプを見つけることができるリンクを使用して、最も基本的な情報を簡単な方法で紹介していきます。

Terraformの基本的なブロックは[リソース](https://www.terraform.io/docs/configuration/resources.html)です。リソースの宣言は、どの構成で何を作成する必要があるかを示しています。 Terraformの背後にある主なアイデアの1つは、同じ構成を何度適用しても、最終結果は同じになるということです。

ソフトウェア製品が異なれば、作成および管理できるオブジェクトも異なります。これらのソフトウェア製品は、リソースの名前空間として理解していただければと思います。 Terraformの用語では[プロバイダー](https://www.terraform.io/docs/configuration/providers.html)と呼ばれます。 Grafanaプロバイダーはその1つであり、このチュートリアルで使用していきます。

Terraformのコードはすべて、.tfで終わるファイルに記述されています。もちろん、すべてのコードを整理する方法については、さまざまな[ガイドライン](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)があります。コードを[モジュール](https://www.terraform.io/docs/modules/index.html)と呼ばれる個別のユニットにグループ化できます。 Terraformレジストリにいくつかのパブリックモジュールがあります。他のさまざまな用語については[こちら](https://www.terraform.io/docs/glossary.html)をご確認ください。

[Terragrunt](https://terragrunt.gruntwork.io/)や[Atlantis](https://www.runatlantis.io/)など、Terraformでさらに多くのことを達成するのに役立つ自動化製品もあります。今回カバーするには内容が多すぎるので、ここから、Terraformの構文と単純な構成の適用方法に精通していると仮定し、ご説明させて頂きます。

#Grafanaプロバイダー
Grafanaプロバイダーは、ダッシュボード、データソース、フォルダー、組織、アラート通知チャネルなどのリソースの管理を許可します。 これを使用するには、Grafanaへの管理アクセスをプロバイダーに提供する必要があります。 次のプロバイダーブロックを.tfファイルに追加してください。

```
provider "grafana" {
  url  = "http://grafana.example.com/"
  auth = "eyJrIjoicEXAMPLEEXAMPLEEXAMPLEEXAMPLEk15T2VBbkFCdTYiLCJuIjoidGVycmFmb3JtX3R1dG9yaWFsIiwiaWQiOjF9"
}
```

Authは、Grafanaから取得できるトークン、またはコロン文字「：」で区切られたユーザー名とパスワードの組み合わせのいずれかです。

個人設定メニューの[API　Key]セクションに移動すると、トークンを取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/5eaa9c01-c582-f5f3-74d4-bf3e75bc4a7f.png)

[New API Key]をクリックして、管理者ロールで新しいキーを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/2ef213be-e085-59b3-954b-4da9efb65beb.png)

次に、一度だけ表示されるため、安全な場所に保存する必要があるキーを取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/3c9064d8-5698-0e19-6799-8998ce8e8bd8.png)

これは、上記のスニペットのようにauthパラメーターに貼り付ける必要があるキーです。 ただ、心配しないでください。これは、ローカルのGrafanaで生成されたキーの例にすぎません。 Grafanaインスタンスを指すようにURLを変更した後は、Grafanaプロバイダーのさまざまなリソースを自由に探索できます。 この記事では、すべてのリソースの概要を説明します。

#実践例
これらの例は、Terraform0.12およびGrafana6.7.2でテストされています。        

###フォルダー
1つのフォルダの作成は次のように簡単です。

```
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.
```

すると以下のような結果が生まれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/700e6938-f9c3-e681-6d72-8623a26c9b99.png)

###データソース
ダッシュボードをそのフォルダーにインポートする前に、それらのダッシュボードで使用されるデータソースをいくつか作成する必要があります。 Grafanaプロバイダーを使用すると簡単です。

渡す必要のある正確な引数は、作成するデータソースのタイプによって異なります。 ここですべてのオプションを調べてください。 このチュートリアルでは、Prometheusデータソースを作成する方法を示します。

```
resource "grafana_data_source" "metrics" {
  type      	= "prometheus"
  name      	= "metricfire"
  url       	= "http://127.0.0.1:12345/"
}

```

結果が以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/18232764-465a-4af3-22be-fa33d09ad8e0.png)

###ダッシュボード
フォルダとデータソースができたので、そのデータソースからのデータを視覚化するダッシュボードをインポートできます。 ダッシュボードのJSONオブジェクトをインラインで指定するか、次の例のようにファイル関数を使用できます。

```
resource "grafana_dashboard" "metrics" {
  config_json = file("metricfire-dashboard.json")
}
```
残念ながら、grafana_dashboardリソースはダッシュボードのフォルダーへのインポートをサポートしていないため、以前に作成したフォルダーにダッシュボードをインポートすることはできません。 前のスニペットを実行すると、次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/e4d84cc1-e839-82ca-7302-bd115e583966.png)

そのファイルには、いくつかのリンクのみを含む単純なダッシュボードがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/c981b96c-9613-97e7-c601-5ed0614d478c.png)

###アラート通知チャネル
このリソースは、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"
  }
}
```

結果が以下です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/f37b3a2b-66ae-84c5-9344-f0797bc5a478.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/3cf7974f-7863-aac1-b023-45ac2dc0edd0.png)

###オーガナイゼーション
最後に、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になります。

上記のコードを適用すると、次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/6c4a7230-7859-606b-0031-0568882298b9.png)

###注意

TerraformとそのGrafanaプロバイダーを使用してこれらすべてのことを実行できますが、いくつかの重大な注意事項があります。 たとえば、さまざまな組織でフォルダ、データソース、またはその他のリソースを作成することはできません。 あなたは主要な組織でのみそれを行うことができます。 これは、Grafanaがそれらの間をナビゲートするための非常に優れたRESTful APIを公開していないためです。つまり、Grafanaは、ユーザーに対して現在選択されている組織など、サーバー側の状態を保存します。 これは、それをTerraformの哲学に統合する良い方法がないことを意味します。 それを行うための[いくつかの試み](https://github.com/terraform-providers/terraform-provider-grafana/issues/59)がありましたが、公式はまだ利用できません。

したがって、Grafanaですべてのアクションを完全に自動化するには、Goなどの汎用プログラミング言語に切り替える必要があります。

#Goでアクションを自動化
GoでGrafanaアクションを自動化するための非常に人気のあるSDKがあります。これは[grafana-tools / sdk](https://github.com/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 _ =クライアント部分が追加されました。 これで、クライアントを使用して、Grafanaでやりたいことを何でも行うことができます。 たとえば、次の3つの組織を作成しましょう。

- 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)
   }
}
```

すると、以下の結果になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614998/9fc05e84-e26b-008a-be18-2d820174e004.png)


また、GrafanaのAPIは2回以上実行してもエラーは発生しません。代わりに、既に存在し、最終結果が同じであるため、成功が返されます。

```
$ ./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を介していくつかの診断情報を出力します。ご覧のとおり、2番目の列は常に<nil>であり、エラーが発生していないことを意味します。

#自動化のテスト
汎用プログラミング言語の大きな力には、大きな責任が伴います。 常にコードをテストする必要があります。 自動化プログラムの単体テストをいくつか書いてみましょう。

コードをさまざまな関数やモジュールに分離することで、それらを個別にテストできます。 私たちのプログラムのためにそれを実行しましょう。もちろん、独自のプログラムははるかに複雑になります。これは単なる例です。 コードは次のようになります。

```
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 := &amp;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 := &amp;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のドキュメント](https://godoc.org/github.com/grafana-tools/sdk)を読んで、必要なアクションを自動化してみてください。