- publish-app: convert/add Gitea Actions deploy job to publish TickShield apps to the marketplace via aigrego-release (Go static binary) — --store auto-publish + --wait review gate; local-first CLI fetch (ensure-cli.sh) with minIO platform-matrix download + sha256 verify; workflow template and 12-item check script - rename dev-app to app-dev per app-centric naming convention (no tickshield- prefix), documented in AGENTS.md and README - app-dev boundary routes CI publish workflow work to publish-app; both skills are project-level installs only
7.0 KiB
7.0 KiB
排障对照表(症状 → 原因 → 怎么办)
die 消息统一三段式:是什么 + 为什么(后果) + 怎么办/绕过参数。 重点收录「构建全绿、命令返回 ✓、线上却坏」类成因。
注册 / 心跳
| 症状 | 原因 | 怎么办 |
|---|---|---|
401 missing/invalid app credentials |
凭证未签发或对错应用 | 平台 console 建应用签凭证;X-App-Key 必须 == listingKey |
403 credential listingKey mismatch |
拿 A 应用的凭证注册 B | 凭证与 listingKey 一一对应,换对 |
403 unknown app |
应用不存在且未携带 manifest | 带完整 manifest 重注册,或平台侧先建应用 |
| 429 | 同 IP+listingKey 10min 内连错 10 次(BUG-49 限流) | 等锁定期过;先修凭证再试,别循环猛打 |
400 host must equal listingKey |
容器名/网络别名 ≠ listingKey | compose container_name 与 networks alias 都改成 key(BUG-45 契约) |
400 port must be between 1 and 65535 |
显式 port 越界 | 缺省 8080 或给合法值 |
400 manifest listingKey mismatch |
manifest 内 key 与 body 不一致 | 对齐两处 |
400 invalid manifest json / 各字段校验错 |
字段违契约 | 对照 contract.md §2 字段表逐条修 |
| 心跳 404 | 实例行丢失(重启/清理) | 正常自愈信号——重走 register(基础镜像已内置,不用管) |
| 心跳 409 | 管理面停服(FR-26 语义优先) | 保持心跳等恢复,绝不重注册;恢复后自动回 active |
| 实例被标 dead 摘流 | 心跳 >90s 未到账 | 查容器是否僵死/网络分区;心跳间隔默认 30s 允许连丢 2 次 |
| 部署后应用根本没注册、无任何报错 | ScanManifests 对非法 manifest 静默跳过(只 error 日志) | 看 portal 日志 invalid manifest, skipped,对照字段表修 |
| 旧版本容器重启后版本号没回拨 | latest_version 仅在 semver 不减时拨动(防回拨) | 这是保护不是 bug;回滚 = 显式发新版本号 |
网关 / 令牌
| 症状 | 原因 | 怎么办 |
|---|---|---|
/svc/<key>/... 401(带 WWW-Authenticate) |
网关本地验签失败:签名错 / 过期 / aud ≠ 路径里的 key | 令牌 aud 必须等于目标 listingKey;服务态 1h/用户态 2h 过期重取 |
直连容器 403 gate=audience |
应用侧 guard 再验 aud ≠ appKey | 同上——说明 guard 在工作 |
401 gate=identity |
introspect 不 active(吊销/过期/伪造) | 重取令牌;查 /api/tokens/introspect |
403 gate=scope |
claims.scopes 不含路由 spec.scope | 确认 manifest scopes 已生效(治理变更要审核);exchange 收窄过了头 |
403 gate=role |
角色不符;服务态令牌无 role | 服务态令牌天然过不了 role 闸——写端点用 role:'admin' 挡 M2M 是特性 |
403 gate=acl |
permissions 不含 spec.pid | 平台角色矩阵里给角色勾选该应用的页面/动作权限点 |
403 tenant_context_missing |
无租户上下文(如无租户服务态令牌走了 tdb) | 服务态调用须带 tenant_id,或该端点本就不该碰租户数据 |
403 tenant_unknown |
租户无数据源映射 | 查 /api/v1/tenants/datasources;新租户等 60s 轮询或检查 redis 订阅 |
503 + Retry-After: 2(写请求) |
FR-30 热迁移冻结窗口 | 可重试——客户端按 Retry-After 退避 |
503 portal_unreachable |
guard 自省调不到 Portal | 查 PORTAL_API_URL 与容器网络 |
跨应用调用 401 EXCHANGE_NOT_ALLOWED |
目标不在调用方 manifest 的 exchangeTargets | manifest 补 exchangeTargets(治理变更,要审核) |
403 SCOPE_NOT_ALLOWED |
exchange 的 scope 收窄超出目标 manifest scopes | 收窄集必须是目标 scopes 子集 |
/api/v1/* 拒收令牌 |
带 act 的代理令牌被平台公共服务拒收(BUG-51) |
代理令牌只走 /svc/<aud> 通道 |
租户隔离 / 数据
| 症状 | 原因 | 怎么办 |
|---|---|---|
| 跨租户看到对方数据 | 直连 pg 或 JOIN/子查询绕过了改写器 | die 级事故:业务 SQL 全收口 tdb.query;复杂查询用 queryUnscoped + 显式 tenant_id |
tdb 抛 JOIN/CTE/subquery not allowed |
改写器 fail-closed 白名单 | 改 queryUnscoped(sql, params, tenantId) 并显式写租户过滤 |
启动即退出 missing tenant_id column |
assertTenantTables 发现表缺 tenant_id 列 | 迁移补列;这是保护,别绕过 |
某租户全部查询 500 tenant_datasource_error |
该租户 migrate 钩子失败(fail-closed) | 修该租户库迁移;其余租户不受影响 |
商店 / 部署
| 症状 | 原因 | 怎么办 |
|---|---|---|
403 image registry not registered |
镜像 host 不在 registries 白名单(空表=全拒) | 平台侧 console「仓库管理」登记 host;私有库配凭证 |
400 env key reserved |
env 覆盖保留键 | APP_KEY/APP_SECRET/PORTAL_API_URL/PORT 由平台注入,别传 |
| 409 安装/启停被拒 | 应用未上架 / 有进行中 op(单应用单并发) | 先上架;等 op 结束 |
503 runtime_disabled |
portal 未挂 docker.sock | 平台侧部署形态问题,找管理员 |
| 安装 op failed:30s 未自注册 | 镜像没内嵌注册循环 / 凭证未注入 | 必须 FROM tickshield-node-runtime:base;查容器日志 register 重试报错 |
容器在跑但 /svc/<key>/ 不通 |
容器没接入网关所在网络 / 别名不对 | 网络别名 == listingKey,接入路由承载网 |
| 安装后租户看不到应用 | 可见性 = 已授权 ∧ 已上架 ∧ 运行中 缺一 | 三条件逐个查(授权即安装;draft 对租户不可见) |
micro 前端
| 症状 | 原因 | 怎么办 |
|---|---|---|
| PROD 白屏,Console 报 CSP 违例 | 内联 <script>(严格 default-src 'self') |
JS 全部外置文件 |
直开 /apps/<key>/_/ 抛「must run inside the portal host iframe」 |
正常——桥只在 iframe 内可用 | 降级友好页(照 sdk.ts 范式 catch) |
| 页面显隐不对 | acl 没接(旧宿主无 acl 字段时不设防) | 握手取 init.acl 驱动路由守卫;真闸在后端四道闸 |
| 调后端跨域报错 | 直接 fetch /svc/... |
一律 sdk.callService(appKey, path) 走宿主代发 |
| 刷新丢路由 | 没接 ?r= 深链 |
initialRoute() 恢复 + routeSync 上报 |
publish --dist 被拒 |
包根没有直接含 index.html / 超 64MB / 含 symlink | tar czf -C <dir> . 形态打包 |
发布 / CI
| 症状 | 原因 | 怎么办 |
|---|---|---|
| publish 403(凭证没错) | --key 与凭证 listingKey 不一致;或 --store 时 auto_publish 未开 |
应用只能操作自己;找平台管理员开 auto_publish |
| publish 返回 202 但「没生效」 | 治理字段变更进审核队列 | 这是设计——等平台管理员批准,或 --wait 轮询 |
| 409 已上架 | --store 重复上架 |
视为跳过(幂等) |
CI 里 whoami 401 |
secrets 没配/配错 | 仓库 secrets:APP_KEY/APP_SECRET/REGISTRY_USER/REGISTRY_TOKEN |
| APP_SECRET 写进了配置文件被忽略并告警 | CLI 只从配置文件读 LISTING_KEY | 密钥走 env/CI secrets,不落盘 |