go-plugin 已经存在很长时间了,同时hashicorp公司的好多产品都在使用vault
,terrform
,nomad
,waypoint
详细的介绍可以参考官方文档,以下只是一个简单的学习试用(demo来自官方)
项目准备
- go mod
go mo init demo-plugin
go get github.com/hashicorp/go-plugin
go get github.com/hashicorp/go-hclog
- 项目结构
├── Dockerfile
├── README.md
├── commons
│ └── greeter_interface.go
├── go.mod
├── go.sum
├── main.go
└── plugin
└── greeter_impl.go
代码说明
go-plugin 的开发模式,是基于二进制文件按需运行的(plugin 为二进制应用,懒加载运行)
- plugin
基于net-rpc模式开发的
commons/greeter_interface.go 主要定义了关于plugin 接口的实现
package commons
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
)
// Greeter greeter
type Greeter interface {
Greet() string
}
// GreeterRPC Here is an implementation that talks over RPC
type GreeterRPC struct{ client *rpc.Client }
// Greet greet
func (g *GreeterRPC) Greet() string {
var resp string
// how to identifi
err := g.client.Call("Plugin.Greet", new(interface{}), &resp)
if err != nil {
// You usually want your interfaces to return errors. If they don't,
// there isn't much other choice here.
panic(err)
}
return resp
}
// GreeterRPCServer Here is the RPC server that GreeterRPC talks to, conforming to
// the requirements of net/rpc
type GreeterRPCServer struct {
// This is the real implementation
Impl Greeter
}
// Greet greet for service impl
func (s *GreeterRPCServer) Greet(args interface{}, resp *string) error {
*resp = s.Impl.Greet()
return nil
}
// GreeterPlugin This is the implementation of plugin.Plugin so we can serve/consume this
//
// This has two methods: Server must return an RPC server for this plugin
// type. We construct a GreeterRPCServer for this.
//
// Client must return an implementation of our interface that communicates
// over an RPC client. We return GreeterRPC for this.
//
// Ignore MuxBroker. That is used to create more multiplexed streams on our
// plugin connection and is a more advanced use case.
type GreeterPlugin struct {
// Impl Injection
Impl Greeter
}
// Server server
func (p *GreeterPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &GreeterRPCServer{Impl: p.Impl}, nil
}
// Client client
func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &GreeterRPC{client: c}, nil
}
plugin/greeter_impl.go plugin 二进制程序的入口以及服务暴露
package main
import (
"demo-plugin/commons"
"os"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
)
// Here is a real implementation of Greeter
type GreeterHello struct {
logger hclog.Logger
}
func (g *GreeterHello) Greet() string {
g.logger.Debug("message from GreeterHello.Greet")
return "dalong demoapp!"
}
// handshakeConfigs are used to just do a basic handshake between
// a plugin and host. If the handshake fails, a user friendly error is shown.
// This prevents users from executing bad plugins or executing a plugin
// directory. It is a UX feature, not a security feature.
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "hello",
}
func main() {
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Trace,
Output: os.Stderr,
JSONFormat: true,
})
greeter := &GreeterHello{
logger: logger,
}
// pluginMap is the map of plugins we can dispense.
var pluginMap = map[string]plugin.Plugin{
"greeter": &commons.GreeterPlugin{Impl: greeter},
}
logger.Debug("message from plugin", "foo", "bar")
// 暴露plugin 的rpc 服务,懒运行机制
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshakeConfig,
Plugins: pluginMap,
})
}
- client (插件调用)
main.go
package main
import (
"demo-plugin/commons"
"fmt"
"log"
"os"
"os/exec"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
)
func main() {
// Create an hclog.Logger
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stdout,
Level: hclog.Info,
})
// We're a host! Start by launching the plugin process.
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: handshakeConfig,
Plugins: pluginMap,
Cmd: exec.Command("./plugin/greeter"),
Logger: logger,
})
defer client.Kill()
// Connect via RPC
rpcClient, err := client.Client()
if err != nil {
log.Fatal(err)
}
// Request the plugin
raw, err := rpcClient.Dispense("greeter")
if err != nil {
log.Fatal(err)
}
// We should have a Greeter now! This feels like a normal interface
// implementation but is in fact over an RPC connection.
greeter := raw.(commons.Greeter)
fmt.Println("from plugin message:" + greeter.Greet())
}
// handshakeConfigs are used to just do a basic handshake between
// a plugin and host. If the handshake fails, a user friendly error is shown.
// This prevents users from executing bad plugins or executing a plugin
// directory. It is a UX feature, not a security feature.
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "hello",
}
// pluginMap is the map of plugins we can dispense.
var pluginMap = map[string]plugin.Plugin{
"greeter": &commons.GreeterPlugin{},
}
- Dockerfile
all-in-one的部署运行
FROM golang:1.14-alpine AS build-env
RUN /bin/sed -i 's,http://dl-cdn.alpinelinux.org,https://mirrors.aliyun.com,g' /etc/apk/repositories
WORKDIR /go/src/app
COPY . .
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn
RUN apk update && apk add git
&& go build -o ./plugin/greeter ./plugin/greeter_impl.go && go build -o basic .
FROM alpine:latest
RUN /bin/sed -i 's,http://dl-cdn.alpinelinux.org,https://mirrors.aliyun.com,g' /etc/apk/repositories
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY --from=build-env /go/src/app/basic .
COPY --from=build-env /go/src/app/plugin/greeter ./plugin/
ENTRYPOINT [ "./basic" ]
运行
- 构建
go build -o ./plugin/greeter ./plugin/greeter_impl.go
go build -o basic .
- 运行
./basic
- 效果
参考资料
https://github.com/hashicorp/go-plugin
https://github.com/hashicorp/go-plugin/tree/master/examples/basic
https://github.com/rongfengliang/go-plugin-learning