二年前发布的golang 1.8打开了在运行时加载动态链接组件的大门,我们很想知道我们是否可以在KrakenD工具箱中包含这个强大的功能。
我们想分享一下我们如何增强产品以支持golang插件的经验和细节。
为什么有人会在Go中使用插件?
该 插件 的概念广为人知,并支持多种编程语言和环境。它使第三方开发人员能够扩展应用程序,添加新功能或自定义行为,而无需触及核心应用程序的单行,同时避免重新编译。
我们今天发布的KrakenD(aka,KrakenD-CE)包括一系列额外的 中间件 ,这些中间件存放在单独的存储库中,这些存储库被编译并包含在KrakenD-CE的最终 二进制文件 中。
作为产品的所有者,我们必须决定在最终分发中默认包含哪种中间件,但我们理解我们不包含某些内容的合理决定可能不符合您的特定兴趣。我们的折衷方案是拥有一个具有最佳性能的纯API网关,并且某些功能将始终根据其性质而退出,并且永远不会加入krakend-ce。这会强制用户导入此中间件并自行编译最终的二进制文件。
这是一个实际的例子:如果你想要包含New Relic仪器中间件来查看面板中的指标,那么你必须分叉KrakenD-CE存储库,导入包,在管道工厂中添加中间件并编译项目。虽然这没什么大不了的,但是将文件放入文件夹并启动服务器会更容易,不是吗?
如果您想知道为什么NR模块不包含在我们的发行版中(但是?),因为将代理添加到网关中会对性能和内存消耗产生很大影响,因此在维护者修复此问题之前,它就会消失。
好消息和坏消息
好消息是,通过插件,我们可以为KrakenD提供大量功能,而不会讨论这个特定功能集是否应该在分发内部(或者至少是一个不那么激烈的讨论)。
坏消息是正如官方文档所说 “插件支持目前只在 Linux 上可用” 。虽然99.9%的用户在Linux机箱或Linux容器中部署生产,但现在KrakenD是多平台的,但插件却不是。
调整KrakenD框架
让我们亲自动手,看一下支持插件所需的Go代码示例。
对于初始迭代,我们只添加了一个新的配置部分和一个 plugin 包。
以下是struct添加的新配置的定义ServiceConfig:
type Plugin struct { folder string `mapstructure:"folder"` Pattern string `mapstructure:"pattern"` }
在配置文件中,我们现在可以使用插件定义文件夹的位置,以及用于过滤文件夹内容的模式。典型的配置代码段可能如下所示:
"plugin": { "folder": "./plugins/", "pattern": ".so" }
负责扫描插件文件夹并加载所有附带插件的整个 软件包 暴露了一个功能func Load(cfg config.Plugin) (int, error),因此使用起来很方便。
func Open (pluginName string) (err error) { defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("%v", r) } } }() _, err = pluginOpener(pluginName) return } // pluginOpener keeps the plugin open function in a var for easy testing var pluginOpener = plugin.Open
我们决定将注册逻辑委托给插件会给我们一个更好的解耦(旧的IoC原则)。扫描并过滤插件文件夹的内容后,该Load函数只调用plugin.Open包装可能的错误和恐慌。目前它不使用返回*plugin.Plugin的查找。查看文档以获取更多详细信息。
这是Pull Request,包含框架的所有必需更改。
调整KrakenD-CE发行版
为了在启动网关之前加载插件,executor.go需要使用以下代码块扩展脚本:
... if "" != os.Getenv("KRAKEND_ENABLE_PLUGINS") && cfg.Plugin != nil { logger.Info("Plugin experiment enabled!") pluginsLoaded, err := plugin.Load(*cfg.Plugin) if err != nil { logger.Error(err.Error()) } logger.Info("Total plugins loaded:", pluginsLoaded) } ...
由于插件功能仍处于试验阶段,因此应在配置文件和KRAKEND_ENABLE_PLUGINSenv_var中启用它。
这是完整的Pull请求。
写插件
根据pluginGolang包的要求,插件应该有一个main包,因此main不会调用它们的功能。
所述弹性搜索火星插件是如何捆绑插件并经由所述KrakenD-CE二进制一个非常明显的例子krakend-martian包并且没有在为了使用火星LIB在插件,以避免标记的碰撞。
这是插件所需的所有代码:
package main
import (
"github.com/devopsfaith/krakend-martian/register"
"github.com/kpacha/martian-components/body/elastic-search/modifier"
)
func init() {
register.Set("body.ESQuery", []register.Scope{register.ScopeRequest}, func(b [] byte ) (interface{}, error) {
return modifier.FromJSON(b)
})
}
func main() {
}
该github.com/devopsfaith/krakend-martian/register软件包公开了一个setter和一个getter,它将火星模块的注册委托给插件本身,因此火星lib只包含在github.com/devopsfaith/krakend-martian包中一次。每个插件都应该在其init函数中注册其修饰符。
使用插件标志编译所需的包:
$ go build -buildmode=plugin -o krakend-martian_es.so ./krakend-plugin/elastic-search
并将生成的插件放入插件文件夹中,以便KrakenD可以在运行时加载它们。
忠告
这些是我们在实验中发现的警告,但几乎所有情况都可以通过一些解决方法来避免:
依赖版本
应用程序和插件之间的共享库应该具有相同的版本,否则将不会加载插件。使用供应商可能有助于限制摩擦并避免出现问题,但在某些情况下这是不可能的。
冲突
如果插件或其依赖项重新声明应用程序(或其依赖项)使用的标志,则插件将发生混乱。
Vendored packages
如果您的应用程序或插件使用任何类型的依赖性销售系统,则通常会将已销售的软件包重命名为path/to/your_app/vendor/path/to/dependency。因此无法从您的插件访问它。
结论
在这篇文章中,我们已经看到了使用插件的好处以及如何在您的应用程序中使用它们。即使它们仅在Linux中受支持,它也是扩展功能的一种非常方便的方式,特别是那些并非在所有情况下都使用并且更为罕见的功能。