【大數據運維監控】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 的一個分享就到這裏,後續有什麼我再來補充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章