Когда Grafana и Go сталкиваются, происходит волшебство, но не то, что связано с волшебными палочками и пыльцой фей. Это суровое, типобезопасное колдовство, где бэкенд-плагины превращают хаос в элегантные дашборды. Как человек, который укрощал JSON в три часа ночи, я проведу вас через создание готовых к продакшену плагинов Grafana на Go, с историями о обработке ошибок и дисциплиной, навязанной компилятором.

Почему выбрать Go для плагинов Grafana?

Go — это не просто язык; это набор инструментов для выживания разработчиков бэкенда. Для плагинов Grafana он предлагает:

  • Бинарную простоту — одиночные скомпилированные бинарные файлы побеждают в борьбе с адом зависимостей.
  • Суперсилы параллелизма — работа с потоками данных без экзистенциального страха.
  • Кросс-компиляция — нацелены на Linux/Windows/macOS с GOOS=linux GOARCH=amd64.
  • Строгая типизация — ваше будущее «я» скажет вам спасибо при рефакторинге в полночь.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main
import (
	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)
func main() {
	backend.SetupPluginEnvironment("my-awesome-plugin")
	plugin := newDemoPlugin()
	httpadapter.New(plugin.router).Register(plugin.mgr)
	backend.Serve(plugin.mgr)
}

Базовый каркас плагина — «hello world», который превращается в Годзиллу.

Создание вашего первого плагина источника данных

1. Каркас как у профи

Запустите свой терминал и призовите богов каркасов:

npx @grafana/create-plugin@latest
# Выберите: «Backend data source» → Go → «Custom implementation»

Это создаёт:

  • magefile.go — ваш командир по сборке.
  • go.mod — тюремщик зависимостей.
  • каталог /pkg — где творится магия.

2. Танцующий обработчик запросов

Grafana общается с вашим плагином через QueryData — ваш плагин отвечает фреймами данных. Вот танго:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (ds *DataSource) QueryData(
	ctx context.Context,
	req *backend.QueryDataRequest,
) (*backend.QueryDataResponse, error) {
	responses := backend.Responses{}
	for _, q := range req.Queries {
		response := backend.DataResponse{}
		// Ваша бизнес-логика здесь!
		result, err := ds.query(q)
		if err != nil {
			response.Error = err
		} else {
			frame := data.NewFrame("response")
			frame.Fields = append(frame.Fields,
				data.NewField("time", nil, []time.Time{time.Now()}),
				data.NewField("value", nil, []float64{result}),
			)
			response.Frames = append(response.Frames, frame)
		}
		responses[q.RefID] = response
	}
	return &responses, nil
}

Совет от профессионала: обрабатывайте ошибки, как будто обезвреживаете бомбы — одно неверное движение и БУМ!

3. Волшебство конфигурации

Секреты должны храниться в хранилищах, а не в коде. Обрабатывайте конфигурацию безопасно:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type DataSource struct {
	backend.CheckHealthHandler
	settings settings
}
type settings struct {
	ApiKey    string `json:"apiKey"`
	MaxRetries int   `json:"maxRetries"`
}
func NewDataSource(ctx context.Context, config backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
	var s settings
	if err := json.Unmarshal(config.JSONData, &s); err != nil {
		return nil, fmt.Errorf("settings decode: %w", err)
	}
	return &DataSource{settings: s}, nil
}

Бонус: используйте sdk-go/backend/encryption для секретов, таких как API-ключи.

Отладка: Искусство войны

Настройка горячей перезагрузки

  1. Фронтенд: npm run dev (следит за src/)
  2. Бэкенд: mage -v build:linux (запускать после изменений)
  3. Grafana: npm run server (запускает Docker на localhost:3000) Когда что-то ломается (а это произойдёт):
# Следите за логами Grafana как детектив
docker logs -f grafana-dev 2>&1 | grep "MY_PLUGIN"
sequenceDiagram participant User participant Grafana participant Plugin(Frontend) participant Plugin(Backend) participant DataSource User->>Grafana: Запускает запрос на дашборд Grafana->>Plugin(Frontend): Отправляет запрос Plugin(Frontend)->>Plugin(Backend): Пересылает запрос через gRPC Plugin(Backend)->>DataSource: API-запрос с аутентификацией DataSource-->>Plugin(Backend): Возвращает необработанные данные Plugin(Backend)->>Plugin(Frontend): Преобразует в DataFrame Plugin(Frontend)->>Grafana: Структурированный ответ Grafana->>User: Визуализирует данные

Поток данных: где ваш код встречается с реальностью.

Продвинутые паттерны для проверенных в бою плагинов

Потоковая передача данных

Когда важна реальная время:

func (ds *DataSource) SubscribeStream(_ context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
	return &backend.SubscribeStreamResponse{
		Status: backend.SubscribeStreamStatusOK,
	}, nil
}
func (ds *DataSource) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			data := fetchLiveData()
			frame := createDataFrame(data)
			if err := sender.SendFrame(frame, data.Include); err != nil {
				return err
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

Предупреждение: может вызвать зависимость от дашбордов в реальном времени.

Маршруты ресурсов

Создавайте собственные конечные точки для магических трюков:

func newDemoPlugin() *DemoPlugin {
	p := &DemoPlugin{
		mgr:  manager.New(),
		router: mux.NewRouter(),
	}
	p.router.HandleFunc("/api/custom-endpoint", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`{"status":"волшебство завершено"}`))
	})
	return p
}

Доступ через: http://localhost:3000/api/plugins/my-plugin-id/api/custom-endpoint

Публикация: Ваш момент славы

  1. Версионируйте как профи: mage -v build:linux build:windows build:darwin
  2. Подпишите манифест: mage sign
  3. Соберите: mage packageAll создаёт dist/ с зипами
  4. Отправьте: Загрузите на Grafana через их портал публикации

«Go в плагинах Grafana — как эспрессо — маленький, бодрящий и держит вашу систему в тонусе.» — Я, в 4 утра, отлаживая тайм-ауты.

Напутствие

Создание плагинов Grafana на Go похоже на ковку Экскалибура — утомительно, пока клинок внезапно не зазвучит. Помните:

  • Тестируйте беспощадно: backend.NewLogger() — ваш спутник в отладке.
  • Версионируйте всё: Обновления Grafana SDK не ждут никого.
  • Принимайте ошибки: Они просто функции в маскировке. Ваше путешествие с плагинами начинается с одного go build. Где оно закончится? Наверное, с дашбордом, который заставит коллег спрашивать: «Как вы это сделали