Grafana dashboardお引っ越しを(地味につまずく)HTTP APIでやろう

# TL;DR

こんにちは。nntsuguです。

昨日は好きなGrafana Community Dashboardである[Node Exporter Server Metrics](https://grafana.com/dashboards/405)にfd消費量を表示するグラフを付け足す話を書きました。
[昨日の投稿はこちら](https://qiita.com/nntsugu/items/0973c5f4c888a3a89e53)


- 行の繰り返しはRow Options > のRepeat forで設定できる
- 列の繰り返しはPanel titleをクリック > Edit > General > Templating > Repeat Panelで設定できる

~~直感的じゃないよね~~
ということを伝えたかったお話でした。


さて今回のお話ですが、みなさんGrafana間のDashboardの移行はどうされていますか？
僕は本番環境のメトリクスを眺めながら、本番環境に新しいパネルを追加していくことが多いです。
筋の良し悪しは別として、上記を検証環境や負荷試験環境に反映させたいニーズがあります。

どこかの環境やOrg.で拡張したDashboardを別の環境やOrg.にも反映したい場合の方法は

- GUIで対象DashboardをExportして、GUIで反映したい先にImportする
- GUIで対象DashboardをExportして、[HTTP API - Dashbouard API](http://docs.grafana.org/http_api/dashboard/)で反映したい先にCreateする

あたりでしょうか(※1)。
今回は後者について記載していきます。
GrafanaにはHTTP APIドキュメントがあるのですが、記載に過不足誤りがあるため、いくつかのつまずきポイントがあります。
これから同じことをされる方の一助となればと思います。


※1
最近のバージョンでは、Dashboardを一括Exportする機能がなさそう。
またHTTP APIにもExport関連は無いようです。
ご存知の方、ご教示いただけるとうれしいです。

性懲りもなくGrafana話です。まぁいいよね。


# 前提

- Grafana 4.6.1以降で試しました。


# つまずきポイント

GrafanaのHTTP APIを使う場合のつまずきポイント。
かなり気づきにくい印象があるので先に書いておきます。

## つまずき1. HTTP API Documentの読み方 : 認証編

HTTP APIを叩く際に、認証情報が必要になる。
認証方法は2種類あり、APIに応じて使い分ける必要がある。

-　管理者権限が必要なもの: AdminユーザのID/PasswordによるBasic認証
-　管理者権限が不要なもの: API_TOKEN(※)を用いたBearer認証

※API_TOKENの取得方法は [2. API Tokenの取得](https://qiita.com/nntsugu/private/a87d0e6db47c57d48f6d#2-api-token%E3%81%AE%E5%8F%96%E5%BE%97) を参照。



誤った認証方法を使用すると、下記のように"Permission denied"となる。


```bash
$ curl -H "Authorization: Bearer ${API_TOKEN}" -X PUT --insecure -H "Content-Type: application/json" -d '{
   "name":"AkaiR"
 }' http://localhost:3000/api/orgs/${ORGID}
```

```bash

{"message":"Permission denied"}
```

[Admin API](http://docs.grafana.org/http_api/admin/#admin-api) に解説があるが、各APIのExample Requestの記載がまちまち＆誤っている場合もあるため、最初は戸惑うかもしれない。
上記Update Organisation APIはBasic認証を使用する必要があるが、ドキュメントの記載はBearer認証となっている。
http://docs.grafana.org/http_api/org/#update-current-organisation

実際、GrafanaのIssueには、使用している認証が間違っていることが原因でAPIが動かないという問い合わせもちょいちょいある。
https://github.com/grafana/grafana/issues/7767#issuecomment-285265524

### 認証方法の見分け方

#### Example RequestのAuthorizationを見る

- **Authorization: Basic hogefugaもしくは記載なし** ならBasic認証(リクエストにAdminの認証情報 ex. -u admin:secret を含める)
  - 記載なしサンプル: [Global Users](http://docs.grafana.org/http_api/admin/#global-users)
  - Basic認証サンプル: [Search Users](http://docs.grafana.org/http_api/user/#search-users)

- **Authorization: Bearer hogefuga** ならAPI_TOKENでのBearer認証(リクエストに-H "Authorization: Bearer ${API_TOKEN}"を含める)
  - サンプル: [Create / Update dashboard](http://docs.grafana.org/http_api/dashboard/#create-update-dashboard)

- **上記の見分け方で上手くいかない場合** ドキュメントの記載が間違っている可能性大。ドキュメントにある以外の認証方法を試してみると◎
  - サンプル: [Update current Organisation](http://docs.grafana.org/http_api/org/#update-current-organisation) : ドキュメントの記載はAuthorization: Bearerだが、Basic認証でないとPermission deniedとなる


## つまずき2. GUIからExportされるJSONファイルはHTTP API - Dashboard APIでそのまま使用できない


[GUIからExport](https://qiita.com/nntsugu/private/a87d0e6db47c57d48f6d#5-1-%E7%A7%BB%E8%A8%AD%E3%81%97%E3%81%9F%E3%81%84dashboard%E3%82%92export%E3%81%99%E3%82%8B)したDashboardのJSON
は、そのままHTTP APIで使用することができない。
dashboardオブジェクトの中に入れてあげる必要がる。

```bash
{ "dashboard":
  ###ここにExportしたJSONのを入れる###
}
```

[Dashboard Create](https://qiita.com/nntsugu/private/a87d0e6db47c57d48f6d#5-2-export%E3%81%97%E3%81%9Fdashboard%E3%81%AEjson%E3%81%8B%E3%82%89http-api%E3%81%A7create-dashboard%E3%81%99%E3%82%8B
)時に、下記のように強引にdashboardオブジェクトで包み込んでいる。

```bash
sed -ie '1s/^/\{\"dashboard\"\:/' ${_DASHBOARD_JSON}
echo '}' >> ${_DASHBOARD_JSON}
```

ExportしたJSONと[ドキュメント](http://docs.grafana.org/http_api/dashboard/#create-update-dashboard)を見比べればわかる。。と言えばわかるがとても見落としやすい印象がある。。

# 手順(HTTP APIでDashboardをCreateするまで)

## 1. Grafanaを起動

今回は、https://hub.docker.com/r/grafana/grafana/ のGrafana公式docker imageを使用します。

```bash
sudo docker run \
  -d \
  -p 3000:3000 \
  --name=grafana \
  -e "GF_SECURITY_ADMIN_PASSWORD=secret" \
  grafana/grafana
```

※話がそれますが、dockerhubなどの公開レポジトリのdocker imageを使用する場合は、極力公式なものか、Dockerfileが公開されているもの使用しましょう。
　野良imageは危険です。ビットコインを掘られたりすることがあります。

GF_SECURITY_ADMIN_PASSWORDにはGrafanaのGUIにログインするための、adminユーザのパスワードです。
今後APIを叩く際にも利用します。

Docker起動後、http://localhost:3000/でGrafana GUIのログイン画面が表示されます。
ログインID:admin
パスワード: GF_SECURITY_ADMIN_PASSWORDにセットした値
でログインできることを確認しておくと◎

## 2. API Tokenの取得

### 2-1. API Tokenの取得と保存

```bash
_CREDENTIAL_FOR_ADMIN='admin:secret' #(A)
ORGID=1 #(B)
API_TOKEN=`curl -X POST -H "Content-Type: application/json" -d '{"name":"apikeycurl", "role": "Admin"}' http://admin:secret@localhost:3000/api/auth/keys | jq -r ' .key '`
echo ${API_TOKEN} >> ~/.token #(C)
```

- (A)_CREDENTIAL_FOR_ADMINにはadminユーザのID:Passwordを設定します。
- (B)ORGIDは決め打ちにしています。1はデフォルトで作られるMain Org.のID。複数のOrganizationを作成する場合は[Create Organisation API](http://docs.grafana.org/http_api/org/#create-organisation)あたりでOrganizationを追加し、適切ORGIDなものを指定してください。
- (C)適当な場所に保存しておきます。ちゃんと運用する場合はちゃんとセキュアな保存場所に保存してくださいね。Vaultとか、k8sのSecretとか。


### 2-2. お好みでOrganization nameを変更

```bash
curl -u ${_CREDENTIAL_FOR_ADMIN} -X PUT --insecure -H "Content-Type: application/json" -d '{
  "name":"AkaiR"
}' http://localhost:3000/api/orgs/${ORGID}
```



## 3. ユーザを作成

```bash
curl -X POST http://${_CREDENTIAL_FOR_ADMIN}@localhost:3000/api/user/using/${ORGID} #(A)
curl -u ${_CREDENTIAL_FOR_ADMIN} -X POST --insecure -H "Content-Type: application/json" -d '{
  "name": "AkaiR",
  "email": "akair@hogefuga.jp",
  "login": "akair",
  "password": "Ak@Ir"
}' http://localhost:3000/api/admin/users #(B)
curl -u ${_CREDENTIAL_FOR_ADMIN} -X PATCH --insecure -H "Content-Type: application/json" -d '{
    "role": "Admin"
}' http://localhost:3000/api/org/users/2 #(C)
```

- (A) ユーザを作成したいOrganizationに切り替えておきます
- (B) ユーザを作成します
- (C) (B)で作成したユーザのroleを"Admin"に変更します

この時点で、 http://localhost:3000/ へakair:Ak@Irでログインできるようになっています。
Organization nameもAkaiRとなっているはず。


## 4. データソースを定義

データソースはグラフ化するデータの出処のことです。
今回はPrometheusからデータを取得します。

```bash
curl -X POST --insecure -H "Authorization: Bearer ${API_TOKEN}" -H "Content-Type: application/json" -d '{
  "name":"Prometheus",
  "type":"prometheus",
  "url":"http://prometheus_url:9090",
  "access":"proxy",
  "basicAuth":false
}' http://localhost:3000/api/datasources #(A)
```

- (A) prometheus_url:9090はご自身の環境のものに置き換えてください




## 5. Dashboardを作成


### 5-1. 移設したいDashboardをExportする

今回は、https://qiita.com/nntsugu/items/0973c5f4c888a3a89e53 でカスタマイズしたDashboardをExportしました。

ヘッダの<img width="34" alt="Screen Shot 2017-12-25 at 18.23.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/46760a3c-1883-4b15-9c8f-0c6ffcd3f4bf.png">をクリック > 下記画像の赤枠"Export"を開きます。

<img width="1248" alt="Screen Shot 2017-12-25 at 18.23.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/a0f6d450-6b0a-e4cf-6685-c18a499eacdd.png">


現れたダイアログの、"Save to file"でJSONをダウンロードします。

<img width="1054" alt="Screen Shot 2017-12-25 at 18.31.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/98beeb25-55a9-16c7-fa96-ee2f50a64b39.png">


ダウンロードしたファイルは、

/path/to/grafana/templates/node_by_hostname.json

に配置しました。


### 5-2. ExportしたDashboardのJSONから、HTTP APIでCreate Dashboardする

```bash
curl -X POST http://${_CREDENTIAL_FOR_ADMIN}@localhost:3000/api/user/using/${ORGID} #(A)
_DASHBOARD_JSON="/path/to/grafana/templates/node_by_hostname.json" #(B)
sed -ie '1s/^/\{\"dashboard\"\:/' ${_DASHBOARD_JSON} #(C)
echo '}' >> ${_DASHBOARD_JSON}
curl -X POST --insecure -H "Authorization: Bearer ${API_TOKEN}" -H "Content-Type: application/json" -d @${_DASHBOARD_JSON} http://localhost:3000/api/dashboards/db #(D)
```

- (A) Dashboardを作成したいOrganizationに切り替える(1つしかないけど、明示的に切り替えたい派)
- (B) ExportしたJSONファイルへのPathを指定する
- (C) (B)をdashboardオブジェクトの中に入れる
- (D) Create dashboard APIでDashboardを作成する


## 6. Dashboardが出来ている確認する

### 6-1. ログイン

http://localhost:3000/

<img width="794" alt="Screen Shot 2017-12-25 at 20.18.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/a005041a-1400-cd0d-b053-eb691cc4ba74.png">

[3. ユーザを作成](https://qiita.com/nntsugu/private/a87d0e6db47c57d48f6d#3-%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92%E4%BD%9C%E6%88%90)で作ったakairユーザでログインする。


### 6-2. Dashboardができているか確認

<img width="1017" alt="Screen Shot 2017-12-25 at 20.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/ee4c86f9-dfc3-74fc-4658-221e55602e78.png">

<img width="1279" alt="Screen Shot 2017-12-06 at 15.29.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/14859/d9022d37-72b1-8641-a7b4-9b87691dd064.png">

おつかれさまでした。