不是用Go编写的Grafana后端插件如何通信其连接设置?



我正在尝试用c++写一个Grafana后端插件。我这样做是为了与具有c++ API的自定义工具(称为"灯塔")集成。我创建了一个c++ gRPC应用程序,它实现了这个文档所建议的Grafana插件协议。

我的问题是:连接设置,例如gRPC服务器端口,如何在主Grafana进程和后端插件之间进行通信。我希望能够在plugin.json配置文件中指定连接字符串或端口号,但似乎没有任何这样的字段。

我安装了我的插件并试图加载它。当然,它没有工作,但是给了我这样的日志输出:

logger=plugin.loader t=2023-01-11T11:58:25.984652862Z level=debug msg="Loading plugin" path=/var/lib/grafana/plugins/lighthouse-datasource/plugin.json
logger=plugin.loader t=2023-01-11T11:58:25.984878566Z level=debug msg="Plugin is unsigned" id=lighthouse-datasource
logger=plugin.signature.validator t=2023-01-11T11:58:25.984904895Z level=warn msg="Permitting unsigned plugin. This is not recommended" pluginID=lighthouse-datasource pluginDir=/var/lib/grafana/plugins/lighthouse-datasource
logger=plugin.loader t=2023-01-11T11:58:25.984925224Z level=info msg="Plugin registered" pluginID=lighthouse-datasource
logger=plugin.lighthouse-datasource t=2023-01-11T11:58:25.984963175Z level=debug msg="starting plugin" path=/var/lib/grafana/plugins/lighthouse-datasource/lighthouse_backend_linux_amd64 args=[/var/lib/grafana/plugins/lighthouse-datasource/lighthouse_backend_linux_amd64]
logger=plugin.lighthouse-datasource t=2023-01-11T11:58:25.985195211Z level=debug msg="plugin started" path=/var/lib/grafana/plugins/lighthouse-datasource/lighthouse_backend_linux_amd64 pid=7065
logger=plugin.lighthouse-datasource t=2023-01-11T11:58:25.98523206Z level=debug msg="waiting for RPC address" path=/var/lib/grafana/plugins/lighthouse-datasource/lighthouse_backend_linux_amd64
logger=plugin.loader t=2023-01-11T11:59:31.176451438Z level=error msg="Could not start plugin" pluginId=lighthouse-datasource err="timeout while waiting for plugin to start"
logger=plugin.lighthouse-datasource t=2023-01-11T11:59:31.177010088Z level=debug msg="plugin process exited" path=/var/lib/grafana/plugins/lighthouse-datasource/lighthouse_backend_linux_amd64 pid=7065 error="signal: killed"

消息msg="waiting for RPC address"表明Grafana进程正在等待某些东西,可能是插件本身,以提供gRPC地址。如果这种解释是正确的,插件应该如何提供这些信息?

我最终能够通过寻找"waiting for RPC address"消息的起源来弄清楚这一点。事实证明,该消息来自HashiCorp go-plugin中的代码。如上所述,插件实现服务部分,而插件用户(在本例中为Grafana)实现客户端部分。

在扫描了这些代码之后,我能够拼凑出事情是如何组合在一起的。Grafana将插件可执行文件作为子进程生成,然后附加管道来捕获其标准输出和标准输出。Grafana期望一个特殊的管道分隔值输出到stdout,其中包括连接信息。对于我的简单示例,值输出看起来像这样:

1|2|unix|/tmp/some_path_for_uds|grpc

Grafana记录插件的stderr输出,这对调试很有用。

上面我有一点搞错了,如果你运行的是Unix/Linux,那么插件架构期望gRPC使用Unix域套接字。这是有道理的,但我之前没有想到。因此,在我的例子中,我期望的地址+端口结果只是如上所示的UDS文件系统路径。

这是我的c++插件入口点的最终粗糙版本:

#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <grpcpp/grpcpp.h>
#include "DiagnosticsServiceImpl.h"
#include "Logger/Logger.h"

void RunServer(const std::string& udsAddress)
{
const std::string addressURI("unix://" + udsAddress);
::unlink(udsAddress.c_str());

DiagnosticsServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
grpc::ServerBuilder builder;

// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(addressURI, grpc::InsecureServerCredentials());

// Register "service" as the instance through which we'll communicate with
// clients. In this case it corresponds to an *synchronous* service.
builder.RegisterService(&service);

// Finally assemble the server.
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cerr << "Server listening on " << udsAddress;

// Build start handshake output
std::ostringstream strm;
strm << "1|2|unix|" << udsAddress << "|grpcn";
const std::string startOutput(strm.str());

// Now output it
std::cout << startOutput << std::endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
int main(int argc, char** argv)
{
const std::string udsAddress("/tmp/lighthouse_backend_plugin_uds");

std::cerr << "Grafana Lighthouse Backend Plugin starting...";

RunServer(udsAddress);

std::cerr << "Grafana Lighthouse Backend Plugin terminating...";

return 0;
}

我花了好几天才弄明白,希望它能在将来为别人节省一些时间。

最新更新