Day 12 / 共 20 天 · 第 3 周 AI/插件/消费者

Wasm 插件管理

上一站 Higress 教过 Wasm 插件怎么在网关里运行。今天看 Console 怎么"管理"它们:区分"插件定义"和"插件实例",内置插件怎么从 jar 里的资源加载,以及一个反直觉的真相——后端的 JSONSchema 校验其实是空的。

📍 你在整门课的位置 · 第 3 周 AI/插件/消费者(Day 11-15)
D11 AI供应商/路由 D12 插件管理 D13 消费者鉴权 D14 路由/服务源 D15 MCP管理
L01

定义 vs 实例(先分清)

🤔 痛点:不分"定义/实例"会怎样? 假设不区分——每次给一条路由配限流,都把"这个插件长什么样、有哪些配置项、README、图标"整套重复存一遍。43 个插件、几百条配置全是重复信息,想改一处 README 得改几百份。
💡 本质:一份说明书,多台设备的设置 定义 = 应用商店里的"应用介绍页"(这个 App 是什么、要哪些权限),全局只有一份;实例 = 你把这个 App 装到某台手机上做的个性化设置,可以有很多份。Console 用两套 Controller/Service 分别管"介绍页"和"每台设备的设置",正对应生活里"应用 vs 安装"。
📝 举个例子 界面上操作 给路由 order-api 配限流 100 QPS → 生成一个 WasmPluginInstance:引用 rate-limit 这个定义、scope=ROUTE、target=order-api、配置 qps=100。定义没动,只多了一个实例。
  • 插件定义(WasmPlugin):这个插件是什么、有哪些配置项(JSON Schema)、README、图标、镜像地址。像"App 商店里的应用介绍"。
  • 插件实例(WasmPluginInstance):把某个插件配到某个作用域并填好参数。像"你把这个 App 装到某台设备上并做了设置"。
一个定义,多个实例 比如"限流"是一个定义;你在路由 A 上配限流 100 QPS、在路由 B 上配 50 QPS,就是两个实例(都引用同一个定义,参数不同、作用域不同)。Console 分别用两套 Controller/Service 管定义和实例。
L02

两个 Controller

  • WasmPluginsController/v1/wasm-plugins:52)—— 定义:list/query(支持 lang)/add/update(按 builtIn 分流 updateBuiltIn/updateCustom :111-112)/delete、GET /{name}/config(:125 返回 JSON Schema)、GET /{name}/readme
  • WasmPluginInstancesController/v1:57)—— 实例:按 GLOBAL/DOMAIN/ROUTE/SERVICE 四作用域各提供 list/query/put/delete(:93-245),统一委托 4 个私有方法。
读法:实例 Controller 的 queryInstance:254)查不到时返回"空实例"而非 404——因为前端要展示一个"未配置"的空表单让你填,而不是报错。细节里见用心。
L03

内置插件从 classpath 加载

WasmPluginServiceImpl.java@PostConstruct initialize():122-186):

// 读 plugins/plugins.properties(插件名 = OCI 镜像)
// 逐插件读 plugins//spec.yaml(Swagger Yaml.mapper 反序列化成 Plugin)
// 加载三语 README + base64 图标
// 缓存到 builtInPlugins
"内置插件"从哪来? 约 43 个官方插件(ai-*、key-auth、jwt-auth、waf、model-mapper…)的元数据(描述、配置 schema、README、图标)作为资源文件打包在 jar 里sdk/src/main/resources/plugins/)。启动时一次性读进内存缓存。这样前端插件列表不用查 K8s,直接从内存返回,快且稳定。插件的实际 Wasm 二进制则以 OCI 镜像形式存在镜像仓库,运行时由网关拉取。
读法:镜像地址策略 buildPluginImageUrl:196-234)默认 registry higress-registry.cn-hangzhou.cr.aliyuncs.com、namespace plugins,可由环境变量 HIGRESS_ADMIN_WASM_PLUGIN_* 覆盖(离线/私有仓库场景)。
L04

i18n 反射覆盖

内部类 PluginCacheItem:674-822)的 applyI18nResources:777-821)用反射匹配 ^x-(.+)-i18n$ 扩展键,按当前语言覆盖 title/description。

多语言是怎么做到的? 插件 spec 里除了 title,还有 x-title-i18n: {zh-CN: 中文名, en-US: English} 这样的扩展字段。后端按用户语言,用反射找到对应的 x-xxx-i18n 键,把值覆盖到 title/description 上。这套 x-*-i18n 约定第 4 周(Day 18)前端动态表单还会用到——前后端共用同一套多语言约定。
L05

实例映射 CRD

插件定义rate-limit(1 份) 实例 · GLOBAL 实例 · DOMAIN 实例 · ROUTE 实例 · SERVICE WasmPlugin CRGLOBAL → spec.defaultConfig其它作用域 → matchRules[]
一个定义派生多个作用域实例;setWasmPluginInstanceToCr 把它们写进同一个 WasmPlugin CR——GLOBAL 进 defaultConfig,其余进 matchRules。

WasmPluginInstanceServiceImpl.addOrUpdateAll:159-256)核心写路径:校验 → 按 (plugin,version,internal) 分组 → 查/建 WasmPlugin CR → 解析 rawConfigurations(YAML) → 取 schema → setWasmPluginInstanceToCr → create/replace CR。

转换在 KubernetesModelConverter.setWasmPluginInstanceToCr:765-814):

// GLOBAL 作用域  → 写 spec.defaultConfig + defaultConfigDisable = !enabled
// 非 GLOBAL     → 构造 MatchRule(config/configDisable + domain/ingress/service 列表)
//                 按 scope 优先级排序
读法:回想 Day 09 的四作用域——GLOBAL 写进 CRD 的 defaultConfig(默认对所有流量生效),其他作用域写进 matchRules(按域名/路由/服务匹配才生效)。"启用/禁用"用 configDisable 表达,保留配置值但让它不生效。
L06

JSONSchema 校验的"真相"

👶 小白 vs 👨‍🏫 老师 👶:既然叫 JSONSchema 校验,后端肯定会拦住我填错的配置吧?
👨‍🏫:并不会。validateAndCleanUp 现在是空实现,原样返回你填的东西。真正用到 schema 的是前端——它读 schema 画表单、限制你能填什么(Day 18 详讲)。
👶:那我乱填岂不是也能写进去?
👨‍🏫:能写进 CRD,但最终执行插件的网关 Wasm 运行时会按自己的逻辑处理——后端这层只是"预留了校验位、还没填"。这正是你能贡献代码的 TODO。
一个反直觉的发现 你可能以为后端会用 JSON Schema 严格校验插件配置——但仓库根本没引入任何 jsonschema 校验库(没有 networknt / json-schema-validator)。WasmPluginConfig.java:31)只有一个 Swagger 的 Schema 字段,它的 validateAndCleanUp:33-36是个空实现(TODO),原样返回配置。唯一调用点在 WasmPluginInstanceServiceImpl.java:227-229
结论:schema 当前主要驱动前端表单渲染(Day 18 会看到前端拿 schema 生成表单),后端"校验"是预留扩展点自定义插件的 queryConfig/queryReadme 也是 TODO(返回空)。——这些都是极好的"动手改进"练习。
L07

内置插件元数据存储

sdk/src/main/resources/plugins/plugins.properties + 每插件目录 spec.yaml / README*.md / icon.pngspec.yaml 结构:

info:                 # category / name / title / x-title-i18n /
                      # description / iconUrl / readmeUrl / version
spec:
  phase:              # 插件执行阶段
  priority:           # 优先级 0-1000
  configSchema:
    openAPIV3Schema:  # ← 前端据此渲染配置表单
读法:约 43 个插件目录:ai-*、basic-auth、jwt-auth、key-auth、waf、mcp-server、model-mapper、model-router 等。BuiltInPluginName.java 列了所有内置插件名常量(AI/Auth/Security/Transformation/Traffic 各类)。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • "插件定义"和"插件实例"的区别?
  • 内置插件的元数据从哪加载?为什么缓存在内存?
  • 插件实例怎么映射成 CRD 的 defaultConfig / matchRules?
  • 后端 JSONSchema 校验的真相是什么?schema 主要给谁用?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/sdk/src/main/java/com/alibaba/higress/sdk
sed -n '122,186p' service/WasmPluginServiceImpl.java
sed -n '31,36p' model/WasmPluginConfig.java     # 空 TODO 校验
sed -n '765,814p' service/kubernetes/KubernetesModelConverter.java
ls src/main/resources/plugins/ | head -30
明天预告 · Day 13消费者与鉴权——消费者其实是 key-auth 插件的全局实例、allow list 机制、以及"目前只支持 API Key 一种鉴权"的现状与扩展点。
← Day 11 Day 13 · 消费者鉴权 →