Backstage 后端服务发现机制详解:HostDiscovery 公共 API、discovery.endpoints 配置与 SRV 服务发现实现 Backstage 后端服务发现机制详解HostDiscovery 公共 API、discovery.endpoints 配置与 SRV 服务发现实现【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstagebackstage/backend-defaults的 discovery 特性是 Backstage 后端体系中负责插件间地址解析的核心基础设施它把每个后端插件的 internal服务间调用与 external前端/外部服务可达基址统一收敛到一套可热更新的配置上并支持按插件覆盖、{{pluginId}}模板和 SRV 记录动态解析。本文以 discovery 特性的 API 报告 为骨架结合 HostDiscovery 源码实现 与 测试用例完整讲清这组公共 API 的语义、配置参数、优先级规则与底层原理。一、API 报告总览discovery 特性导出了什么API 报告文件 report-discovery.api.md 由 API Extractor 自动生成文件头部明确标注 Do not edit this file它定义了该特性的对外契约。报告声明的全部public导出为以下 4 个符号依赖均来自backstage/backend-plugin-apiimport { DiscoveryService } from backstage/backend-plugin-api; import { LoggerService } from backstage/backend-plugin-api; import { RootConfigService } from backstage/backend-plugin-api; import { ServiceFactory } from backstage/backend-plugin-api; // public export const discoveryServiceFactory: ServiceFactory DiscoveryService, plugin, singleton ; // public export class HostDiscovery implements DiscoveryService { // (undocumented) static fromConfig( config: RootConfigService, options?: HostDiscoveryOptions, ): HostDiscovery; // (undocumented) getBaseUrl(pluginId: string): Promisestring; // (undocumented) getExternalBaseUrl(pluginId: string): Promisestring; } // public export interface HostDiscoveryEndpoint { plugins: string[]; target: | string | { internal?: string; external?: string; }; } // public export interface HostDiscoveryOptions { defaultEndpoints?: HostDiscoveryEndpoint[]; logger: LoggerService; }这 4 个导出在 index.ts 中统一再导出构成外部可依赖的完整面符号种类职责discoveryServiceFactory服务工厂将coreServices.discovery绑定到HostDiscovery是默认后端的装配入口HostDiscovery类DiscoveryService的默认实现通过fromConfig从根配置构建HostDiscoveryEndpoint接口描述一条discovery.endpoints路由规则targetpluginsHostDiscoveryOptions接口fromConfig的可选参数日志器与默认端点列表二、DiscoveryService 契约internal 与 external 两类地址HostDiscovery实现的DiscoveryService接口定义在 DiscoveryService.ts它是整套机制的语义基础包含两个方法getBaseUrl(pluginId)返回插件的 internal 基址不带尾斜杠用于部署内部的服务间通信。接口注释明确要求该方法必须在每次发起请求前调用而不是在构造 API 客户端时取一次缓存——因为路由模式可能每次返回不同结果例如 SRV 解析的加权随机选择。getExternalBaseUrl(pluginId)返回 external 基址要求从前端和其他外部服务可达、且保持稳定可用于 callback / webhook URL。接口注释说明了设计动机通过中央配置支持各种部署形态与路由方式而不是让每个插件各自管理端点配置。官方 Discovery Service 文档 给出了插件内消费该服务的标准写法import { coreServices, createBackendPlugin, } from backstage/backend-plugin-api; createBackendPlugin({ pluginId: example, register(env) { env.registerInit({ deps: { discovery: coreServices.discovery, }, async init({ discovery }) { const url await discovery.getBaseUrl(derp); const response await fetch(${url}/hello); }, }); }, });三、discoveryServiceFactory服务的装配方式discoveryServiceFactory.ts 用createServiceFactory完成绑定要点有三export const discoveryServiceFactory createServiceFactory({ service: coreServices.discovery, deps: { config: coreServices.rootConfig, logger: coreServices.logger, }, async factory({ config, logger }) { return HostDiscovery.fromConfig(config, { logger, defaultEndpoints: [], }); }, });声明为singleton级别API 报告中的第三个泛型参数即同一后端实例内只构建一个发现服务供所有插件共享依赖rootConfig与logger两个核心服务分别对应HostDiscoveryOptions中的logger字段与fromConfig的第一个参数默认后端传入的defaultEndpoints为空数组——该参数真正的用途见第六节。四、HostDiscovery.fromConfig配置来源与 fallback 解析器fromConfig是构建HostDiscovery的唯一静态入口构造函数为私有见 HostDiscovery.ts#L190-L200。它从backend配置段读取基线参数并构造两类 fallback 解析器核心逻辑在#updateFallbackResolversInternal fallback由backend.listen与backend.https推导${protocol}://${host}:${listenPort}/api/{{pluginId}}protocol由backend.https配置是否存在决定有则为httpshost 做规范化处理绑定::IPv6 全绑定或空串时改写为localhost0.0.0.0改写为127.0.0.1含冒号的 IPv6 地址自动加方括号固定基路径为/api因此catalog插件默认 internal 地址形如http://localhost:7007/api/catalog。External fallback由backend.baseUrl推导${trimEnd(backend.baseUrl, /)}​/api/{{pluginId}}测试文件 HostDiscovery.test.ts#L58-L83 用参数化用例完整覆盖了 listen 的各种写法可作为行为基准backend 配置片段解析出的 internal 前缀{ listen: :80 }http://localhost:80{ listen: :40, https: true }https://localhost:40{ listen: 127.0.0.1:80 }http://127.0.0.1:80{ listen: 0.0.0.0:40 }http://127.0.0.1:40{ listen: { port: 80, host: :: } }http://localhost:80{ listen: { port: 80, host: ::1 } }http://[::1]:80{ listen: { port: 90, host: ::2 }, https: true }https://[::2]:90此外fromConfig还内建了两条配置校验告警HostDiscovery.ts#L154-L188当backend.baseUrl解析为localhost/127.0.0.1/::1/::且NODE_ENV production时记录 warn 级日志提示localhost URL 在部署环境不可达当backend.baseUrl不是合法 URL 时warn 提示配置值无效。这两条告警的行为在 测试用例backend.baseUrl warnings中被逐字断言可直接用作部署自检参考。五、discovery.endpoints按插件覆盖路由与{{pluginId}}模板discovery.endpoints配置段是覆盖默认行为的正式入口由 parsing.ts 中的getEndpoints解析target为字符串时按internal/external 共用同一地址处理为对象时则分别读取target.internal与target.external两个可选键plugins一律为字符串数组。HostDiscoveryEndpoint接口注释中给出的官方示例是discovery: endpoints: # Set a static internal and external base URL for a plugin - target: https://internal.example.com/internal-catalog plugins: [catalog] # Sets a dynamic internal and external base URL pattern for two plugins - target: https://internal.example.com/secure/api/{{pluginId}} plugins: [auth, permission] # Sets a dynamic base URL pattern for only the internal resolution for all # other plugins, while leaving the external resolution unaffected - target: internal: httpsrv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}} plugins: [*]模板替换规则由#makeResolver实现占位符{{pluginId}}与{{ pluginId }}含空格变体均会被替换替换值经过encodeURIComponent因此带命名空间分隔的插件 ID如plugin/beta会得到.../api/plugin%2Fbeta测试用例 专门验证了这一点plugins数组中的特殊值*会注册为通配解析器查询时按精确插件 ID → * → fallback的顺序回退见 getBaseUrl 实现。测试文件 还验证了两个高频场景单字符串 target 同时生效于 internal 与 externaltarget: http://catalog-backend:8080/api/catalog使两个方法返回同一地址只覆盖一侧时另一侧保持 fallback例如target: { internal: ... }只改变getBaseUrl的结果getExternalBaseUrl仍返回${backend.baseUrl}/api/pluginId。六、端点优先级defaultEndpoints app-config 内置 fallbackHostDiscoveryOptions.defaultEndpoints的设计意图见接口文档注释是当你想为插件开发者提供一套组织默认路由库时可以在不复制每个后端配置的前提下共享端点定义。其优先级在#updatePluginResolvers中体现得很直接// Start out with the default endpoints, if any const endpoints defaultEndpoints?.slice() ?? []; // Allow config to override the default endpoints endpoints.push(...getEndpoints(config));两个来源的端点被合并进同一个 Map 后统一构建解析器app-config 中的discovery.endpoints排在defaultEndpoints之后写入因此对同一插件 ID 的配置具有更高优先级而内置 fallbackbackend.baseUrl/listen推导只在某插件既没有精确解析器也没有*通配解析器时兜底。测试用例accepts default endpoints with lower prio than config用 a~i 共 9 个插件系统性覆盖了仅字符串 target / 仅 internal / 仅 external三种组合下 config 与 default 的相互覆盖关系是验证该规则的权威依据。七、SRV 服务发现httpsrv://动态内网寻址HostDiscovery与 SrvResolvers.ts 协作实现了 DNS SRV 记录驱动的 internal 寻址URL 协议部分写作httpsrv://或httpssrv://时hostname 被当作 SRV 记录名进行解析解析结果拼回为protocol://host:port/path形式的真实地址。关键实现细节语法约束#parseSrvUrlSRV URL 的协议必须以srv:结尾且基础协议只能是http/https不允许携带端口、用户名/密码、查询参数或 hash违反即抛InputError结果缓存SrvResolvers.ts#L127-L151每个 SRV 记录名的解析 Promise 被缓存默认 TTL 1000ms构造参数cacheTtlMillis可调到期后删除再查查询无记录抛NotFoundErrorDNS 失败包装为ForwardedError负载选择#pickRandomRecord先筛出最低 priority 数值优先级最高的一组记录再按 weight 做加权随机选择——这正是DiscoveryService接口要求每次请求前重新取 URL的根因之一作用域限制SRV URL只能出现在 internal target 中。字符串形式的target和target.external都不允许违反会在构建时抛出SRV resolver URLs cannot be used in the target for external endpoints对应测试 覆盖了全部四种写法字符串 target、target.external且分别经由defaultEndpoints与 app-config 传入。这一能力直接服务于多后端部署形态例如为每个插件部署独立的backstage-plugin-id服务用 SRV 记录做内网动态寻址与软负载同时保持 external 地址稳定。该方向的设计背景可参考仓库中的 BEP-0005split backend discovery。八、热更新与前端侧对应物HostDiscovery.fromConfig在初始构建解析器之后会注册配置订阅HostDiscovery.ts#L179-L185config.subscribe?.(() { try { discovery.#updateResolvers(config, options?.defaultEndpoints); } catch (e) { options?.logger.error(Failed to update discovery service: ${e}); } });这意味着discovery.endpoints、backend.baseUrl、backend.listen等配置的热更新会重建全部解析器且构建失败只记 error 日志、不会让旧解析器失效——#updatePluginResolvers也是先构建新 Map、无异常才整体替换的事务式写法保证任何时刻解析器集合都是自洽的。与之对应前端侧存在结构相似的 FrontendHostDiscoverybackstage/core-app-api中的discoveryApi实现为前端组件解析同样的 external 基址。从源码结构看前后端两套实现共享同一份discovery.endpoints配置、internal/external 分离的契约这正是 external 基址被要求稳定、可作 webhook 回调的原因。九、速查与验证路径最小配置只配置backend.baseUrl与backend.listen即获得全部插件的默认解析地址形如${baseUrl}/api/pluginIdexternal与http(s)://listenHost:listenPort/api/pluginIdinternal单插件迁移到独立部署为该插件加一条discovery.endpoints规则即可未列出的插件行为不变仅改内网寻址用target: { internal: ... }对象形式external 保持 fallback行为回归验证所有上述规则均有对应断言集中在 HostDiscovery.test.tsfallback 推导、占位符替换、URL 编码、端点优先级、SRV 限制、baseUrl 告警SRV 解析另有 SrvResolvers.test.ts接口与契约来源DiscoveryService 接口、API 报告、官方 Discovery 文档。需要注意的适用前提SRV 解析依赖运行环境的 DNS 支持底层使用 Node 内置node:dns的resolveSrv且只在 internal 寻址路径生效{{pluginId}}占位符仅替换 URL 中的字面量不做路径级改写。理解这些边界就能在单机、单体多插件、按插件拆分部署等形态间自如切换 Backstage 的后端路由策略。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考