WebAssembly-плагины бэкенда

WebAssembly-плагины бэкенда запускают бэкенд плагина, скомпилированный в WebAssembly (WASM), внутри процесса Grafana, в изолированной виртуальной машине, вместо отдельного процесса ОС, с которым Grafana общается по gRPC.

Caution

Драйвер WASM-бэкенда экспериментальный. Он поддерживает сокращённый набор вызовов бэкенда, а описанный на этой странице хостовый ABI ещё не стабилен.

Когда использовать WebAssembly-плагины

Используйте WASM-бэкенд, когда нужен портируемый бэкенд-плагин, который дёшево изолировать:

  • Один модуль для всех платформ. WASM-модуль выполняется без изменений на любой ОС и архитектуре CPU, которые поддерживает Grafana, так что не нужно собирать и поставлять бинарник под каждую платформу.
  • Без CGO. Рантайм написан на чистом Go, поэтому для сборки и запуска Grafana с этим драйвером не нужны C-тулчейн и нативная компиляция.
  • Выполнение внутри процесса. Отдельный процесс не запускается, поэтому нет ни надзора за процессами, ни выделения портов, ни межпроцессного сокета.
  • Изоляция по возможностям. Модуль стартует без доступа к сети, файловой системе и переменным окружения и взаимодействует с внешним миром только через хостовые функции host_http_fetch и host_log в рамках списка разрешений.

Выбирайте стандартный gRPC-плагин бэкенда, когда нужны потоковые вызовы, сбор метрик, чанковые запросы или обработчики admission и conversion: драйвер WASM их пока не поддерживает.

Как Grafana выбирает драйвер бэкенда

Grafana строит цепочку провайдеров фабрик бэкенда для каждого плагина. Она по очереди запрашивает у каждого провайдера фабрику и использует первую, которая вернула непустой результат. Для WASM-плагина цепочка разрешается так:

  1. Core-плагины, например встроенные источники данных.
  2. WASM-провайдер, который заявляет плагин только если тот объявил себя как WASM.
  3. Провайдер gRPC по умолчанию, который обрабатывает всё остальное.

WASM-провайдер заявляет плагин, когда выполняется любое из условий:

  • В plugin.json поле backendRuntime установлено в "wasm".
  • Путь к исполняемому файлу плагина заканчивается на .wasm, что покрывает плагины, которые не устанавливают backendRuntime, но поставляют WASM-бинарник.

Если ни одно условие не выполняется, WASM-провайдер ничего не возвращает, и цепочка продолжается к провайдеру gRPC.

Настройка WebAssembly-плагина

Объявите плагин как WASM в plugin.json и укажите wasmFilePath с путём к модулю.

JSON
{
  "id": "myorg-wasm-datasource",
  "type": "datasource",
  "backend": true,
  "executable": "gpx_myorg_wasm_datasource",
  "backendRuntime": "wasm",
  "wasmFilePath": "module/main.wasm",
  "wasmSandbox": {
    "allowedOutboundHosts": ["api.example.com", "cache.example.com:8443"],
    "allowedEnvVars": ["MYORG_API_TOKEN"],
    "allowedReadPaths": ["/etc/myorg/certs:/certs"]
  }
}

Бэкендом WASM управляют следующие ключи:

  • backend: Установите true, чтобы Grafana загрузила бэкенд для плагина.
  • backendRuntime: Установите "wasm", чтобы явно выбрать драйвер WASM.
  • wasmFilePath: Путь к WASM-модулю, разрешается относительно каталога плагина. Если его не указывать, Grafana использует обычный путь executable.
  • wasmSandbox: Необязательные списки разрешений песочницы, которые запрашивает плагин. См. раздел Списки разрешений песочницы.

Полный справочник манифеста плагина приведён в схеме плагина.

Песочница и изоляция

WASM-плагин выполняется внутри виртуальной машины wazero. У него нет доступа к файловой системе, сокетам и переменным окружения, пока Grafana явно их не предоставит, и он не может повлиять ни на что за пределами собственной памяти, кроме как через хостовые функции.

Списки разрешений песочницы

Каждая возможность, доступная гостевому модулю, предоставляется через список разрешений. Grafana составляет эти списки из трёх источников и объединяет их:

  1. Блок wasmSandbox в plugin.json. Учитывается только при статусе подписи плагина valid или internal. Манифест и его подпись покрываются одним и тем же подписанным хэшем, поэтому корректная подпись подтверждает и списки разрешений; неподписанный или изменённый манифест не может расширить собственную песочницу.
  2. [plugins.wasm_sandbox.<идентификатор плагина>]. Записи для одного плагина, задаваемые администратором.
  3. [plugins.wasm_sandbox]. Значения по умолчанию для всех WASM-плагинов.

Списки складываются: источник может расширить то, что разрешил другой, но не сузить. Чтобы списки разрешений задавались исключительно администратором, установите ignore_manifest_grants = true в секции [plugins.wasm_sandbox].

ini
[plugins.wasm_sandbox]
allowed_outbound_hosts = api.example.com
allowed_env_vars = SHARED_TOKEN
allowed_read_paths = /etc/ssl:/certs

[plugins.wasm_sandbox.grafana-glaber-datasource]
allowed_outbound_hosts = glaber:8080
allowed_env_vars = GLABER_TOKEN
Ключ Тип Описание
allowed_outbound_hosts список Хосты в виде host или host:port, доступные гостю через host_http_fetch. Сравниваются без учёта регистра; хост без порта не совпадает с тем же хостом с портом.
allowed_env_vars список Имена переменных окружения хоста, передаваемых гостевому модулю. Имена, отсутствующие в окружении сервера Grafana, пропускаются.
allowed_read_paths список Каталоги хоста, смонтированные в файловую систему гостя только для чтения, в виде hostPath:guestPath. Путь без гостевого пути монтируется по тому же пути в госте.
ignore_manifest_grants логическое Игнорировать блок wasmSandbox манифеста plugin.json для всех плагинов. Допустим только в родительской секции.

Списки записываются в обычном для Grafana синтаксисе: значения через запятую, при необходимости в кавычках, либо массив JSON.

Вывод гостевого модуля в стандартные потоки вывода и ошибок, включая сообщения о паниках, перехватывается и записывается в лог сервера Grafana на уровне debug.

Гостевой ABI

WASM-модуль бэкенда взаимодействует с Grafana через небольшой набор экспортируемых функций и импортируемых хостовых функций. Полезная нагрузка кодируется protobuf-сообщениями pluginv2 — тем же проводным форматом, что использует драйвер gRPC, поэтому гостевой модуль может переиспользовать proto-типы Grafana Plugin SDK.

Экспорт

Модуль экспортирует только те функции, которые поддерживает. Любой отсутствующий экспорт обрабатывается так же, как gRPC-плагин, не зарегистрировавший соответствующую службу: вызов завершается ошибкой plugins.ErrMethodNotImplemented.

  • alloc(size uint32) uint32 и free(ptr uint32): Выделяют и освобождают память гостя. Хост никогда не пишет по адресу, который выбрал сам: он всегда сначала просит гостя выделить буфер.
  • wasm_query_data: Обрабатывает pluginv2.QueryDataRequest и возвращает pluginv2.QueryDataResponse.
  • wasm_check_health: Обрабатывает pluginv2.CheckHealthRequest и возвращает pluginv2.CheckHealthResponse.
  • wasm_call_resource: Обрабатывает pluginv2.CallResourceRequest и возвращает pluginv2.CallResourceResponse.

Каждая экспортируемая функция принимает единственный аргумент uint64 и возвращает единственный результат uint64. Буферы адресуются упакованным указателем (ptr<<32 | len), что удерживает ABI в пределах одного параметра и одного возвращаемого значения.

Импорт

Хостовый модуль grafana_host предоставляет единственные способы, которыми гость может выйти за пределы собственной памяти:

  • host_http_fetch: Выполняет исходящий HTTP-запрос. Гость передаёт упакованный указатель на JSON-полезную нагрузку с полями method, url, необязательными headers и необязательным body. Хост проверяет назначение по списку разрешений песочницы, выполняет запрос HTTP-клиентом Grafana и записывает обратно JSON-полезную нагрузку с полями status, headers, body и error. Принимаются только URL со схемами http и https, а размер тела ответа ограничен.
  • host_log: Передаёт строку лога в лог сервера Grafana на уровне debug.

Сборка модуля

Собирайте гостевой модуль как c-shared под цель wasip1, чтобы рантайм Go экспортировал _initialize, а не _start. Grafana запускает модуль через _initialize и возвращает управление хосту, не завершая гостя; поэтому модуль в стиле исполняемого файла с _start не работает.

sh
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o module/main.wasm ./cmd/plugin

Доступ к внешней сети

По умолчанию песочница отклоняет все исходящие запросы. Вызов host_http_fetch успешен, только если хост назначения совпадает с одним из следующих:

  • Явный список разрешений. Хосты из allowedOutboundHosts манифеста plugin.json (только для подписанных плагинов) и из секции [plugins.wasm_sandbox] конфигурации; сравниваются без учёта регистра. См. раздел Списки разрешений песочницы.
  • URL источника данных. Для плагина источника данных хост настроенного URL экземпляра источника данных всегда доступен, потому что Grafana берёт его из управляемых администратором настроек экземпляра, а не от гостя. Хост без порта не совпадает с тем же хостом с портом, и наоборот.

Заблокированный запрос завершается ошибкой с указанием хоста, и Grafana записывает предупреждение в лог.

Компиляция модуля и кэш

wazero не интерпретирует байткод на лету, а компилирует WASM-модуль в машинный код перед первым вызовом. Для крупного модуля (бэкенд на Go в WASM занимает порядка 30 МБ и десятки тысяч функций) эта компиляция занимает десятки секунд и происходит при каждом старте процесса Grafana. Размер файла при этом не главный фактор: время определяется количеством функций и объёмом кода, а не байтами на диске.

Чтобы не компилировать один и тот же модуль повторно, Grafana включает дисковый кэш компиляции wazero в каталоге данных (<data>/wasm-compilation-cache, см. [paths] data в конфигурации). Кэш:

  • Ключуется по содержимому модуля и версии wazero, поэтому обновление плагина или wazero просто добавляет новую запись, а не переиспользует устаревшую.
  • Разделяется между всеми WASM-плагинами: записи разных модулей не конфликтуют.
  • Безопасен для нескольких процессов: записи создаются через временный файл и атомарное переименование, так что параллельные запуски Grafana не повредят кэш.

Первый запуск после чистой установки или обновления модуля остаётся медленным — это ожидаемо. Все последующие старты читают готовый результат с диска и занимают меньше секунды. Если каталог кэша недоступен для записи, Grafana пишет предупреждение в лог и продолжает работу, компилируя модуль без кэша.

Кэш можно безопасно удалить в любой момент: Grafana пересоздаст его при следующем старте.

Ограничения

  • Только три вызова. Драйвер реализует QueryData, CheckHealth и CallResource. Все остальные методы бэкенда, включая сбор метрик, чанковые запросы, потоковую передачу, admission и conversion, возвращают plugins.ErrMethodNotImplemented.
  • Один ответ для ресурсов. CallResource — это один обмен запросом и ответом. Гость не может передать несколько чанков CallResourceResponse потоком.
  • Сериализованные вызовы. Grafana сериализует каждый вызов в один экземпляр модуля, поэтому параллельные запросы к одному плагину встают в очередь, а не выполняются одновременно.
  • Нет горячей перезагрузки. Изменение модуля требует перезапуска плагина, который заново компилирует и создаёт его. Первый старт с новым модулем пройдёт без кэша (см. раздел «Компиляция модуля и кэш»), последующие — из кэша.

Устранение неполадок

Если WASM-плагин не загружается или падает во время работы:

  1. Проверьте лог сервера Grafana на уровне debug на строки WASM plugin log и WASM plugin output от гостя.
  2. Убедитесь, что в plugin.json указано "backendRuntime": "wasm" и что wasmFilePath указывает на существующий модуль.
  3. Для заблокированных запросов найдите предупреждение Blocked outbound WASM plugin request to disallowed host и добавьте хост в allowedOutboundHosts манифеста plugin.json либо в allowed_outbound_hosts секции [plugins.wasm_sandbox]. Разрешения из манифеста неподписанного плагина игнорируются, поэтому в этом случае добавляйте хост в конфигурацию.
  4. При ошибках module closed with exit_code(0) проверьте, что модуль собран как c-shared под wasip1 и экспортирует _initialize, а не _start.

Связанные материалы