【大数据运维监控】Prometheus 可视化页面 Grafana


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

基本概念

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

Data Source

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

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

{
  "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 的简单的程序,可以供大家参考。头条上的显示不是很好:

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 的一个分享就到这里,后续有什么我再来补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值