TT Lab
开始
学习 学习路径 课程

CBA — Backstage 认证助理

登记到目录里的服务悄悄消失了

在 TT Lab 中继续学习

目标

在 VM 中启动一个真实的目录后端,亲手制造实体无法进入目录的四种原因,并找出痕迹留在哪里。通过真实的 API 响应,确认静态 location 的自动采集、通过 API 注册的 location、孤儿实体的清理,以及 location 的删除。

为什么重要

“我提交了 catalog-info.yaml,但门户里看不到”是 Backstage 运维中最常见的咨询。只用验证库检查一个 YAML 文件是不够的。在运行中的目录里,失败有时既不体现在 API 响应中,也不体现在默认日志中,一个实体的问题还可能让同一个文件的全部内容都进不来。注册 API 即使返回 201,也可能在处理阶段失败。只有知道该看哪里,才能修复。

VM 首次启动大约需要 4 分钟,第 1 步的安装需要从互联网下载,约 30 秒。扫描 GitHub 等外部系统的发现(discovery)provider 需要外部账号,因此不涉及。这里的“自动采集”指目录定期重新读取配置中写明的 location。

步骤

  1. 在 /usr/local/cba-ingest 中以固定版本安装目录后端软件包。
  2. 确认配置中写明的文件 location 被自动采集。
  3. 记录缺少 owner 的实体不留痕迹地被丢弃。
  4. 接入日志模块,把处理错误输出为 warn 日志。
  5. 观察不被允许的 kind 会阻塞整个文件,并用按 location 设置的规则解决。
  6. 通过 API 注册 url location,并修复读取白名单的问题。
  7. 观察从文件中删掉的实体先成为孤儿,随后被清除。
  8. 删除 location,并编写采集故障报告。

参考

以固定版本下载运行目录所需的材料

在 /usr/local/cba-ingest 中创建 npm 项目,并以不带范围符号的精确版本安装四个软件包(package.json 中也不带 ^)。npm 缓存设为 npm_config_cache=/usr/local/cba-ingest/.npmcache。四个软件包是 @backstage/backend-defaults@0.17.8、@backstage/plugin-catalog-backend@3.9.1、@backstage/plugin-catalog-backend-module-logs@0.1.25 和 better-sqlite3@12.4.1。

请使用 npm install --save-exact。日志模块只安装,到第 4 步再接入。better-sqlite3 13.x 没有适用于这个 Node 20 的预编译二进制文件,会尝试编译并失败。

配置中写明的文件被自动采集

在 /usr/local/cba-ingest/app-config.yaml 中写入下面的内容,并在 7007 端口启动只包含 catalog 插件的后端(index.js)(输出写入 /usr/local/cba-ingest/backend.log)。backend.baseUrl: http://localhost:7007、backend.listen.port: 7007,数据库为 better-sqlite3、':memory:',backend.auth.externalAccess 中写入 type 为 static、token 为 ingest-lab-token-7f3a9c、subject 为 ingest-cli 的条目,catalog.processingInterval: { seconds: 5 },catalog.rules: [{allow: [Component, Group, Location]}],catalog.locations 中写入 type: file、target: ./catalog/team.yaml。catalog/team.yaml 中放入 Group team-search,以及该团队拥有的 Component search-api 和 search-indexer。用 Authorization: Bearer ingest-lab-token-7f3a9c 调用 /api/catalog/entities,能看到三个实体即可。

目录 API 位于默认认证策略之后,不带令牌调用会返回 401。静态令牌在生产中应通过 ${환경변수}(占位符为环境变量名)传入,但本实验为了观察流程,直接写在文件中。请确认进入目录的实体的 backstage.io/managed-by-location 注解指向哪个文件。

缺少 owner 的实体不留痕迹地被丢弃

在 team.yaml 末尾追加一个没有 spec.owner 的 Component search-ui(type 为 website,lifecycle 为 production)。不重启,等待几秒后查看目录和日志,并在 /root/cba-ingest/silent.txt 中写三行——search_ui_in_catalog=(yes/no)、search_api_in_catalog=(yes/no)和 log_lines_mentioning_search_ui=(此刻 backend.log 中出现 search-ui 的行数)。直到本实验结束,都不要修改 search-ui(后面的步骤和评分会使用这个状态)。

处理周期已缩短为 5 秒,修改文件后很快就会被重新读取。请观察:一个实体验证失败时,同一文件中的其余内容会怎样;以及这个失败留在哪里(或者没有留下)。用 grep -c search-ui backend.log 统计行数。

把处理错误输出到日志

在 index.js 中 add @backstage/plugin-catalog-backend-module-logs,并重启后端。当 backend.log 中打印出关于 component:default/search-ui 的 warn 行时,把去掉颜色控制字符后的这一行保存到 /root/cba-ingest/owner-error.txt。不要修改 team.yaml。

目录只是把处理错误作为事件发出,把它写进日志的是另一个模块。接入模块之后,也要等处理周期运行一次才会出现这一行。用 sed 's/\x1b\[[0-9;]*m//g' backend.log | grep search-ui 查找。events backend not found 警告是因为没有 events 插件而出现的,与本任务无关。

因为一个 API,整个文件都进不来

在 /usr/local/cba-ingest/catalog/billing.yaml 中放入 Component billing-api(owner 为 team-search,providesApis 为 [billing-openapi])和 API billing-openapi(type 为 openapi,owner 为 team-search),在 catalog.locations 中追加 type: file、target: ./catalog/billing.yaml,然后重启。确认两个实体都没有进来,并把一条关于 api:default/billing-openapi 的 warn 行保存到 /root/cba-ingest/kind-error.txt。接着保持全局的 catalog.rules 不变,只在 billing.yaml 这个 location 上加上 rules: [{allow: [API]}] 并重启,让 billing-api 和 billing-openapi 都进入目录。

不被允许的 kind 并不是只丢弃那一个实体,而是让该 location 的整个处理结果失败——请与缺少 owner 的情况对比。如果把 API 加进全局规则,任何文件中的 API 都会被放行。请查看文档中按 location 设置的 rules。

注册返回 201,却什么都没有进来

在 /usr/local/cba-ingest/incoming/data.yaml 中放入 Component etl-runner 和 etl-scheduler(两者的 owner 都是 team-search),并用 python3 -m http.server 8088 --bind 127.0.0.1 在后台提供该目录的服务。① 尝试通过目录 API 注册 type: file、target 为 /usr/local/cba-ingest/incoming/data.yaml,观察响应(400);② 用 type: url、target 为 http://localhost:8088/data.yaml 发送 POST /api/catalog/locations,确认返回 201 但实体没有进来,然后把关于该 url 的一条 warn 行保存到 /root/cba-ingest/reading-error.txt。③ 在 backend.reading.allow 中加入 host: localhost:8088 并重启,然后对同一个 url 重新注册,让两个实体进入目录。把最后一次注册的 POST 响应 JSON 保存到 /root/cba-ingest/location.json。

通过 API 注册的 location 只接受 url 形式。读取 url 的是 UrlReader,没有集成(integration)的主机必须在允许列表中才能读取——这项检查发生在处理时而不是注册时,所以注册响应是成功的。数据库是 :memory:,重启后之前注册的 location 会消失,请用 GET /api/catalog/locations 确认。http.server 也要像 setsid nohup ... > 로그 2>&1 < /dev/null &(占位符为日志文件名)这样启动,以便 shell 结束后仍然存活。

从文件中删掉的实体先成为孤儿,随后被清除

从 incoming/data.yaml 中删除 etl-runner 文档(保留 etl-scheduler)。在目录中 etl-runner 被加上 backstage.io/orphan: "true" 注解的那一刻,把实体 JSON(GET /api/catalog/entities/by-name/component/default/etl-runner 的响应)保存到 /root/cba-ingest/orphan.json。然后等待,确认该实体被从目录中清除(by-name 返回 404),并在日志中找到 Deleted ... orphaned entities 行。

如果产出某个实体的 location 不再产出它,该实体就成为孤儿。是保留孤儿还是删除,由 catalog.orphanStrategy 决定,默认情况下由清理任务(日志中的 catalog_orphan_cleanup,周期 30 秒)删除。注解只会短暂出现,请以 1 秒间隔轮询,抓住它出现的那一刻。

删除 location,并编写采集故障报告

用 location.json 中的 id 发送 DELETE /api/catalog/locations/<id> 取消注册,并确认 etl-scheduler 从目录中消失。然后在 /root/cba-ingest/report.md 的前五行写入此刻测得的值——registered_location_status=(GET /api/catalog/locations/)、etl_scheduler_status=(by-name 查询)、search_ui_status=(by-name 查询)、api_location_count=(GET /api/catalog/locations 结果的数量——两个静态 location 是否出现在这里)、file_register_status=(尝试注册 type 为 file 时的响应)。在其下整理本实验中采集失败的四种原因,以及每一种的痕迹分别留在哪里。

所有值都是现在用带令牌的 curl 测得的。静态 location 由配置文件管理,所以不能通过 API 删除,也不会出现在列表中。四种原因是:缺少 owner、不被允许的 kind、读取允许列表,以及从文件中消失的实体(孤儿)。