如何使用Golang插件和KrakenD

二年前發佈的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中受支持,它也是擴展功能的一種非常方便的方式,特別是那些並非在所有情況下都使用並且更為罕見的功能。


分享到:


相關文章: