0%

Prometheus可视化利器 Grafana

Grafana 其实是没什么好讲的,这里记录下也是因为这个星期在做这个Grafana的多租户设置以及Grafana 服务器的分发,所以也就简单的记录下。

基本概念

在Grafana 里面的基础的概念说多不多,说少不少。这里也不准备全部介绍,就简单的讲一下,笔者在使用的过程中所用到的几个功能模块。

Data Source

数据源,这是我们的Grafana 的一个很重要的东西,Grafana 本身只是一个页面的UI,并不能真的去存储数据。这里额数据源可以理解为我们的源数据,但是其实这里的数据源其实是一个中间结构,相当于一个连接池。简单的画一个图:
datasource
图中的虚线,其实就是我们感觉到的数据的走向。但是这个数据的走向其实是先经过了 DataSource , 在到 Prometheus 的。 这几天我跟踪了下后台的请求,我个人的理解是这样的。

当我们创建一个数据源的时候,其实是在后台帮我们创建一个数据源的配置,然后查询的时候,他查询的路径是: /api/datasources/proxy/13/api/v1/ , 这个路径其实就是我们的数据源里面的一个请求路径,这里的 13 是我们的数据源的 id , 我们看下这个数据源的配置是什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"id": 13,
"orgId": 1,
"name": "prometheus_datasource_test_grafana_30",
"type": "prometheus",
"typeLogoUrl": "",
"access": "proxy",
"url": "http://127.0.0.1:8888",
"password": "",
"user": "",
"database": "",
"basicAuth": true,
"basicAuthUser": "test_grafana_30",
"basicAuthPassword": "",
"withCredentials": false,
"isDefault": false,
"jsonData": {},
"secureJsonFields": {
"basicAuthPassword": true
},
"version": 3,
"readOnly": false
}

可以看到的是,我们的数据源的真实地址是 http://127.0.0.1:8888 . 这个地址是笔者的一个中间服务器地址,那大家可能的就是 Prometheus / Cortex 的地址了。

Organization

我们来看下这个官方的介绍:

Each organization contains their own dashboards, data sources and configuration, and cannot be shared between orgs. While users may belong to more than one, multiple organization are most frequently used in multi-tenant deployments.

这句话什么意思呢?意思就是说 一个 Organization 拥有他们自己的 数据面板,数据源,以及配置,这些东西在两个 Organization 是不能共享的。当然我们的用户可以分属不同的 Organization 。简单的来说这就是一个多租户的管理。而一个 Organization 就是一个租户的组织。

可以为组织内的用户分配角色,也可以为这个角色分配权限,权限有: Admin / Viewer / Editer

Users

用户,这个大家就比较清楚了,就是登陆到 Grafana 的使用者。有管理员和普通用户的区别,管理员就是哪都有他,是 Grafana 的全局管理者,这个可以设置用户成为管理员。用户就是比较简单的用户。这里的用户的权限是针对Grafana 而言的,注意和 Organization 里的 角色权限做下区分。

小结

目前用到的概念比较多的就是这三个,在Grafana 里面也其他的几个概念,因为笔者没有用到,就不列了,大家可以查看 Grafana 的官网。

代码

这里用Go 写了一个操作 Grafana API 的简单的程序,可以供大家参考。头条上的显示不是很好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package grafana

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"prometheus-front-server/pkg/api/client"
"strconv"
"strings"
)

// Package: grafana
// Version: 1.0
//
// Created by SunYang on 2020/5/7 10:24
const (
ErrBadData ErrorType = "bad_data"
ErrTimeout ErrorType = "timeout"
ErrCanceled ErrorType = "canceled"
ErrExec ErrorType = "execution"
ErrBadResponse ErrorType = "bad_response"
ErrServer ErrorType = "server_error"
ErrClient ErrorType = "client_error"
)

type Error struct {
Type ErrorType
Msg string
Detail string
}
type apiGrafanaClientImpl struct {
client client.GrafanaClient
}
type httpAPI struct {
client apiGrafanaClient
}
type apiGrafanaClient interface {
URL(ep string, args map[string]string) *url.URL
Do(context.Context, *http.Request) (*http.Response, []byte, Warnings, error)
DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error)
}
type apiResponse struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
ErrorType ErrorType `json:"errorType"`
Error string `json:"error"`
Warnings []string `json:"warnings,omitempty"`
}
type ErrorType string
type Warnings []string

func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
}

const (
statusAPIError = 422
apiPrefix = "/api"
epGrafanaAccount = apiPrefix + "/admin/users"
epChangeAccountPermission = apiPrefix + "/admin/users/:id/permissions"
epCreateOrganizations = apiPrefix + "/orgs"
epAddAccountToOrganization = apiPrefix + "/orgs/:orgId/users"
epChangeAccountOrgsRole = apiPrefix + "/orgs/:orgId/users/:userId"
epDeleteAccountFromMainOrg = apiPrefix + "/orgs/:orgId/users/:userId"
epAddGrafanaDataSource = apiPrefix + "/datasources"
epChangeDataSourcePermission = apiPrefix + "/datasources/:id/permissions"
)
const (
permission = `{"isGrafanaAdmin": true}`
role = `{"role":"Admin"}`
main_organization = 1
)

type GrafanaAPI interface {
// 创建组织
CreateOrganizations(ctx context.Context, organization string) (OrganizationResult, error)
// 创建用户
CreateGrafanaAcount(ctx context.Context, body string) (CreateGrafanaAcountResult, error)
// 修改用户权限
ChangeAccountPermission(ctx context.Context, id int) (MessageResult, error)
// 添加用户到组织
AddAccountToOrganization(ctx context.Context, orgId int, requestBody string) error
// 修改用户在组织中的角色
ChangAccountOrgRole(ctx context.Context, orgId int, id int) (MessageResult, error)
// 从组织中删除某个用户
DeleteAccountFromMainOrg(ctx context.Context, id int) error
// 添加数据源
AddDataSource(ctx context.Context, requestBody string) (AddDataSourceResult, error)
// 修改数据源的权限
ChangeDataSourcePermission(ctx context.Context, id int) error
}

func (h *httpAPI) CreateOrganizations(ctx context.Context, organization string) (OrganizationResult, error) {
u := h.client.URL(epCreateOrganizations, nil)
request, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(organization))
request.Header.Set("Content-Type", "application/json")
if err != nil {
return OrganizationResult{}, err
}
_, body, _, err := h.client.Do(ctx, request)
if err != nil {
return OrganizationResult{}, err
}
fmt.Println(string(body))
var res OrganizationResult
return res, json.Unmarshal(body, &res)
}
func (h *httpAPI) CreateGrafanaAcount(ctx context.Context, form string) (CreateGrafanaAcountResult, error) {
u := h.client.URL(epGrafanaAccount, nil)
request, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(form))
request.Header.Set("Content-Type", "application/json")
if err != nil {
return CreateGrafanaAcountResult{}, err
}
_, body, _, err := h.client.Do(ctx, request)
if err != nil {
return CreateGrafanaAcountResult{}, err
}
var res CreateGrafanaAcountResult
return res, json.Unmarshal(body, &res)
}

func (h *httpAPI) AddDataSource(ctx context.Context, requestBody string) (AddDataSourceResult, error) {
u := h.client.URL(epAddGrafanaDataSource, nil)
request, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(requestBody))
request.Header.Set("Content-Type", "application/json")
if err != nil {
return AddDataSourceResult{}, err
}
_, body, _, err := h.client.Do(ctx, request)
if err != nil {
return AddDataSourceResult{}, err
}
var res AddDataSourceResult
return res, json.Unmarshal(body, &res)
}

func (h *httpAPI) ChangeAccountPermission(ctx context.Context, id int) (MessageResult, error) {
u := h.client.URL(epChangeAccountPermission, map[string]string{"id": strconv.Itoa(id)})
request, err := http.NewRequest(http.MethodPut, u.String(), strings.NewReader(permission))
if err != nil {
return MessageResult{}, err
}
request.Header.Set("Content-Type", "application/json")
_, body, _, err := h.client.Do(ctx, request)
if err != nil {
return MessageResult{}, err
}
var res MessageResult
return res, json.Unmarshal(body, &res)
}

func (h *httpAPI) AddAccountToOrganization(ctx context.Context, orgId int, requestBody string) error {
u := h.client.URL(epAddAccountToOrganization, map[string]string{"orgId": strconv.Itoa(orgId)})
request, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(requestBody))
if err != nil {
return err
}
request.Header.Set("Content-Type", "application/json")
_, _, _, err = h.client.Do(ctx, request)
if err != nil {
return err
}
return nil
}
func (h *httpAPI) DeleteAccountFromMainOrg(ctx context.Context, id int) error {
u := h.client.URL(epDeleteAccountFromMainOrg, map[string]string{"orgId": strconv.Itoa(main_organization), "userId": strconv.Itoa(id)})
request, err := http.NewRequest(http.MethodDelete, u.String(), nil)
if err != nil {
return err
}
request.Header.Set("Content-Type", "application/json")
_, _, _, err = h.client.Do(ctx, request)
if err != nil {
return err
}

return nil
}
func (h *httpAPI) ChangAccountOrgRole(ctx context.Context, orgId int, id int) (MessageResult, error) {
u := h.client.URL(epChangeAccountOrgsRole, map[string]string{"orgId": strconv.Itoa(orgId), "userId": strconv.Itoa(id)})
request, err := http.NewRequest(http.MethodPatch, u.String(), strings.NewReader(role))
if err != nil {
return MessageResult{}, err
}
request.Header.Set("Content-Type", "application/json")
_, body, _, err := h.client.Do(ctx, request)
if err != nil {
return MessageResult{}, err
}
fmt.Println(string(body))
var res MessageResult
err = json.Unmarshal(body, &res)
return res, err
}
func (h *httpAPI) ChangeDataSourcePermission(ctx context.Context, id int) error {
panic("修改数据源的权限")
}

func NewGrafanaAPI(c client.GrafanaClient) GrafanaAPI {
return &httpAPI{
client: &apiGrafanaClientImpl{
client: c,
},
}
}

func apiError(code int) bool {
return code == statusAPIError || code == http.StatusBadRequest
}

func (h *apiGrafanaClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
panic("implement me")
}

func (h *apiGrafanaClientImpl) URL(ep string, args map[string]string) *url.URL {
return h.client.URL(ep, args)
}

func (h *apiGrafanaClientImpl) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
resp, body, err := h.client.Do(ctx, req)
if err != nil {
return resp, body, nil, err
}
code := resp.StatusCode
if code/100 != 2 && !apiError(code) {
errorType, errorMsg := errorTypeAndMsgFor(resp)
return resp, body, nil, &Error{
Type: errorType,
Msg: errorMsg,
Detail: string(body),
}
}
return resp, body, nil, err
}

func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
switch resp.StatusCode / 100 {
case 4:
return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode)
case 5:
return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode)
}
return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
}

type MessageResult struct {
Message string `json:"message"`
}
type OrganizationResult struct {
OrgId int `json:"orgId"`
Message string `json:"message"`
}
type CreateGrafanaAcountResult struct {
Id int `json:"id"`
Message string `json:"message"`
}
type AddDataSourceResult struct {
DataSource DataSource `json:"datasource"`
Id int `json:"id"`
Message string `json:"message"`
Name string `json:"name"`
}
type DataSource struct {
Id int `json:"id"`
OrgId int `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"`
Access string `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
DataBase string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData interface{} `json:"jsonData"`
SecureJsonFields interface{} `json:"secureJsonFields"'`
Version int64 `json:"version"`
ReadOnly bool `json:"readOnly"`
}

type Client struct {
GrafanaClient GrafanaAPI
}

func NewGrafanaQueryClient(address string) (Client, error) {
grafanaClient, err := client.NewGrafanaClient(client.Config{
Address: address,
})
if err != nil {
return Client{}, err
}
c := Client{GrafanaClient: NewGrafanaAPI(grafanaClient)}
return c, nil
}

总结

这个 Grafana 的一个分享就到这里,后续有什么我再来补充。

这是打赏的地方...

本文标题:Prometheus可视化利器 Grafana

文章作者:Mr.Sun

发布时间:2020年05月08日 - 17:22:07

最后更新:2020年06月15日 - 09:46:27

原始链接:http://www.blog.sun-iot.xyz/posts/19b38673

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

---------Thanks for your attention---------