Skip to content

Plugin

traefik/cmd/traefik/plugins.go

在服务启动初始化的时候, 会通过静态配置加载插件.

插件既可以是 Provider, 也可以是Middleware.

Provider 用于服务发现更新动态配置, Middleware 用于实现HTTP 中间件.

Traefik 支持通过线上的 Pilot, 一个插件市场, 安装插件.

createPluginBuilder

使用initPlugins 实例化 Builder对象, 持有middlewareBuildersproviderBuilders.

后续会利用该 Builder对象, 去导入对应插件的源代码, 编译构建对应的插件模块.

initPlugins

  1. checkUniquePluginNames, 本地插件和远程插件命名不能重复.
  2. 检查本地 Pilot 配置, 加载远程插件
  3. SetupLocalPlugins, 加载本地插件环境
    1. 检查插件重复、命名规范
    2. ReadManifest 读取插件模块配置
    3. 检查插件模块字段值

PluginBuilder

traefik 使用 github.com/traefik/yaegi/interp 动态导入源代码.

NewBuilder 实例持有ProviderBuilderMiddlewareBuilder.

ProviderPlugin

在初始化服务setupServer, 会遍历静态配置 staticConfiguration.Providers.Plugin, 通过newProvider实例化对应的插件.

每个插件

type Provider struct {
    name string
    pp   PP
}

// PP the interface of a plugin's provider.
type PP interface {
    Init() error
    Provide(cfgChan chan<- json.Marshaler) error
    Stop() error
}

MiddlewarePlugin

对于 Middleware Plugin, 需要对应basePkg的包实现 NewCreateConfig 方法

func newMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*middlewareBuilder, error) {
    if basePkg == "" {
        basePkg = strings.ReplaceAll(path.Base(imp), "-", "_")
    }

    fnNew, err := i.Eval(basePkg + `.New`)
    if err != nil {
        return nil, fmt.Errorf("failed to eval New: %w", err)
    }

    fnCreateConfig, err := i.Eval(basePkg + `.CreateConfig`)
    if err != nil {
        return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
    }

    return &middlewareBuilder{
        fnNew:          fnNew,
        fnCreateConfig: fnCreateConfig,
    }, nil
}

注意事项

插件不能重复

静态配置Experimental下的 PluginsLocalPlugins 不能有重复的 key.

对于远程插件 Plugins, 插件存在冲突或下载插件失败, 都会终止插件初始化.

对于本地插件 LocalPlugins, 如果有重复的插件, 会跳过后出现的插件, 但是会报告相应的错误.

LocalPlugin

本地插件要将源代码存储在 traefik 对应的目录.

本地插件模块文件路径按{goPath}/{goPathSrc}/{moduleName}/{pluginManifest}.

  • goPath, ./plugins-local/
  • goPathSrc, src
  • pluginManifest, .traefik.yml

插件模块配置必须为 yaml 格式.

每个配置文件需要按照以下字段进行反序列化.

并且每个字段值必须有效.

type Manifest struct {
    DisplayName   string                 `yaml:"displayName"`
    Type          string                 `yaml:"type"`
    Import        string                 `yaml:"import"`
    BasePkg       string                 `yaml:"basePkg"`
    Compatibility string                 `yaml:"compatibility"`
    Summary       string                 `yaml:"summary"`
    TestData      map[string]interface{} `yaml:"testData"`
}

一些想法

checkLocalPluginManifest

加载本地插件的时候, 首次反序列化配置文件后的对象, 并没有存储, 而是只对配置文件内容做基本检查.

后面实际使用的时候, 又序列化了一次, 那么在这个期间, 如果文件发生了变化, 就会出现不可预期的问题.

在测试环境中, 这种问题也是有不小的概率出现, 并且难以排查.

如果直接将对应的Manifest挂载在服务对象上, 也是一种办法.