hashicorp go-plugin构建golang插件系统

一、go-plugin 简介

1.1 go-plugin 是什么?

我们知道 Go 语言缺乏动态加载代码的机制,Go 程序通常是独立的二进制文件,因此难以实现类似于 C++ 的插件系统。即使go的最新标准引入了 go plugin 机制,但是由于限制性条件比较多导致在生产环境中不是很好用,比如插件的编写环境和插件的使用环境要保持一致,如 gopath、go sdk 版本等。

HashiCorp 公司开源的 go-plugin 库解决了上述问题,允许应用程序通过本地网络(本机)的 gRPC 调用插件,规避了 Go 无法动态加载代码的缺点。go-plugin 是一个通过RPC 实现的 Go 插件系统,并在 Packer、Terraform, Nomad、Vault 等由 HashiCorp 主导的项目中均有应用。

顺便说一句,Vault 开源代码,我这几天看了下,代码写的很不错,感兴趣的小伙伴可以看看vault是怎么使用 go-plugin,很值得借鉴,后续会针对 vault 的源代码的插件部分进行剖析。

1.2 特性

go-plugin 的特性包括:

  1. 插件是 Go 接口的实现: 这让插件的编写、使用非常自然。对于插件编写者来说,他只需要实现一个 Go 接口即可;对于插件的用户来说,就像在同一个进程中使用和调用函数即可。go-plugin 会处理好本地调用转换为 gRPC 调用的所有细节

  2. 跨语言支持:插件可以被任何主流语言编写(和使用),该库支持通过 gRPC 提供服务插件,而基于 gRPC 的插件是允许被任何语言编写的。

  3. 支持复杂的参数、返回值: go-plugin 可以处理接口、io.Reader/Writer 等复杂类型,我们为您提供了一个库(MuxBroker),用于在客户端/服务器之间创建新连接,以服务于附加接口或传输原始数据。

  4. 双向通信: 为了支持复杂参数,宿主进程能够将接口实现发送给插件,插件也能够回调到宿主进程(这点还需要看官网的双向通信的例子好好理解下)

  5. 内置日志系统: 任何使用 log 标准库的的插件,都会自动将日志信息传回宿主机进程。宿主进程会镜像日志输出,并在这些日志前面加上插件二进制文件的路径。这会使插件的调试变简单。如果宿主机使用 hclog,日志数据将被结构化。如果插件同样使用 hclog,插件的日志会发往宿主机并被结构化。

  6. 协议版本化: 支持一个简单的协议版本化,可增加版本号使之前插件无效。当接口签名变化、协议版本改变等情况时,协议版本话是很有用的。当协议版本不兼容时,会发送错误消息给终端用户。

  7. 标准输出/错误同步: 插件以子进程的方式运行,这些插件可以自由的使用标准输出/错误,并且输出会被镜像回到宿主进程。

  8. TTY Preservation: 插件子进程可以链接到宿主进程的 stdin 标准输入文件描述符,允许以TTY方式运行的软件。

  9. 插件运行状态中,宿主进程升级: 插件可以"reattached",所以可以在插件运行状态中升级宿主机进程。NewClient 函数使用 ReattachConfig 选项来确定是否 Reattach 以及如何 Reattach。

  10. 加密通信: gRPC信道可以加密

1.3 架构优势

  1. 插件不影响宿主机进程:插件崩溃了,不会导致宿主进程崩溃

  2. 插件容易编写:仅仅写个 go 应用程序并执行 go build。或者使用其他语言来编写 gRPC 服务 ,加上少量的模板来支持 go-plugin。

  3. 易于安装:只需要将插件放到宿主进程能够访问的目录即可,剩下的事情由宿主进程来处理。

  4. 完整性校验:支持对插件的二进制文件进行 Checksum

  5. 插件是相对安全的:插件只能访问传递给它的接口和参数,而不是进程的整个内存空间。另外,go-plugin 可以基于 TLS 和插件进行通信。

1.4 适用场景

go-plugin 目前仅设计为在本地[可靠]网络上工作,不支持 go-plugin 在真实网络,并可能会导致未知的行为。

即不能将 go-plugin 用于在两台服务器之间的远程过程调用,这点和传统的RPC有很大区别,望谨记。

二、核心数据结构

2.1 Plugin接口

Plugin 是一个接口,是插件进程和宿主进程进行通信的桥梁。

不管是插件编写者还是插件使用者,都需要实现 plugin.Plugin 接口,只是各自的实现不同。

type Plugin interface {
   // Server should return the RPC server compatible struct to serve
   // the methods that the Client calls over net/rpc.
   Server(*MuxBroker) (interface{}, error)

   // Client returns an interface implementation for the plugin you're
   // serving that communicates to the server end of the plugin.
   Client(*MuxBroker, *rpc.Client) (interface{}, error)
}

Server 接口:Server 接口应返回与 RPC server 兼容的结构以提供方法,客户端可以通过net/rpc来调用此方法。

Client 接口:Client 接口返回你提供服务的插件的接口实现,该接口实现将与该插件的服务器端进行通信。

2.2 GRPCPlugin 接口

type GRPCPlugin interface {
    // 由于gRPC插件以单例方式服务,因此该方法仅调用一次
    GRPCServer(*GRPCBroker, *grpc.Server) error
 
    // 插件进程退出时,context会被go-plugin关闭
    GRPCClient(context.Context, *GRPCBroker, *grpc.ClientConn) (interface{}, error)
}

GRPCPlugin的接口实现,在grpc的例子中我们再详细解释。

2.3 plugin.client接口

这个接口负责管理一个插件进程的完整生命周期,包括创建插件进程、连接到插件进程、分配接口实现、处理杀死进程。

对于每个插件,宿主机进程需要创建一个plugin.Client实例。

type Client struct {
    // 插件客户端配置
    config            *ClientConfig
    // 插件进程是否已经退出
    exited            bool
    l                 sync.Mutex
    // 插件进程的RPC监听地址
    address           net.Addr
    // 插件进程对象
    process           *os.Process
    // 协议客户端,宿主进程需要调用其Dispense方法来获得业务接口的Stub
    client            ClientProtocol
    // 
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值