Appearance
ADR-0009: 客户集群的 workload 变更由独立 agent 执行,应用授权挂在 License 上
- 日期:2026-08-03 状态:生效
- 相关:ADR-0008(交付编排收拢到 nuwa)
- 主文档:子应用交付-发布安装升级全流程
背景(什么问题)
目标形态是:nuwa 里建客户 → 勾选该客户可用的应用 → 勾选其中哪些参与首次一键部署 → 下载交付包装机;之后客户用 platform 账号登自己的后台,在应用市场里看到为其勾选的那些应用, 自助点安装 / 升级 / 卸载,点了就真的在客户 k8s 里把这个服务部署上去。
2026-08-03 走查代码,和目标之间有两个结构性缺口:
- 没有任何组件能改客户集群里的 workload。 public 的
/platform/app-store/*做的是"逻辑安装"(Feign 调子服务的/install,再拉 OSS 上的 menus/components/pages/admin.json 入库), 全程不碰 k8s;它的前提是那个微服务已经在集群里跑着(AppInstallTask每 60 秒 Feign 探活, 能调通就把CloudApplication.deploy置 1)。/platform/app-store/deploy名字叫 deploy, 实际只是把市场里的应用信息落一条CloudApplication记录,没有部署任何东西。 - "客户可用应用"这层不存在。 nuwa 只有
dep_customer_deployments.selected_services一个列表, 它既是"客户买了什么"又是"这次下发装什么"。而客户后台的应用市场调的是appstore.cloud.adinz.com/api/v1/market/apps—— 匿名、无客户维度,任何客户都能看到全网应用。
决策(怎么定的)
1. workload 变更由独立的 hiapi-agent 执行,public 一行 k8s 权限都不加
- nuwa 持有期望状态(desired state),是唯一真源。
hiapi-agent随底座安装,是客户集群里唯一持有 Deployment/Service/Ingress/Secret 写权限的组件。 它只跟 nuwa 对话:拉期望状态 → apply → 健康判定 → 失败自动回滚 → 回流上报。- 客户后台的"安装/卸载/升级"按钮 = 向 nuwa 提交一个意图,由 nuwa 校验 (授权 / 配额 / 到期 / 依赖 / 镜像是否真存在)后改期望状态,agent 落实。public 不碰 k8s。
- 一次性下载交付包保留,但降级为首次装机 + 救援通道;日常增删改全走 agent。
2. 应用授权(entitlement)挂在 License 上,与部署清单分两层
lic_licenses(已有:配额 / 到期 / 环境 / enforce)
└── lic_license_apps 新增:这把授权码可用哪些应用(商务面)
dep_customer_deployments.selected_services 保留:首装子集,必须 ⊆ entitlement(交付面)
期望状态 desired services 集群里现在该跑哪些(运行面)客户后台的应用市场只看 entitlement;市场接口必须带 installId + licenseKey 鉴权并按 entitlement 过滤。
3. 应用状态是三态,且"已部署"由 agent 回流,不靠 Feign 探活推断
| 状态 | 含义 | 来源 |
|---|---|---|
| 已授权 · 未部署 | 买了,集群里没跑 | nuwa entitlement |
| 已部署 · 未安装 | Pod 在跑,菜单/组件未装 | agent 回流 |
| 已安装 | 可用 | CloudApplication.install |
理由(为什么,包括否掉了哪些备选)
否掉「public 直连 k8s API」 —— 这是最直觉的做法(客户后台点了,后端直接 apply),但有三条致命伤:
- public 自己就是被部署对象。 升级 public 要先把自己杀掉,执行到一半进程没了,状态机停在中间。 这不是边界情况:public 是最常升级的服务。
- 权限扩张的落点错了。 public 现在的 RBAC 刻意停在"本 namespace 建 Job + 读 Pod 日志" (H5 构建用),要装应用就得加 Deployment/Service/Ingress/Secret 的写权限 —— 而 public 是直接暴露在业务流量里的应用(登录、验证码、上传都在它身上)。 它被打穿就等于客户整个集群被打穿。 同样的权限给一个不接业务流量的 agent,攻击面小一个数量级。
- 状态双主。 manifests 的真源在 nuwa;public 自己 apply 出来的东西 nuwa 不知道, 下次 nuwa 下发会覆盖回去。两个写入方都不知道对方写了什么,这类漂移排查成本极高。
否掉「继续人工下载包执行」 —— 零新增组件,但做不到"客户后台点一下就装上", 而且 1200 次/年的升级量级下人工登机器不成立(见 ADR-0008 的规模重估)。
entitlement 挂 License 而不是 Customer —— License 已经是配额/到期/环境(PROD/DEV)的载体。 同一客户的 PROD 与 DEV 可用应用不同是常态,而"授权到期即停用某应用"可以直接复用现有闸门, 不必另写一套到期逻辑。
「已部署」不能靠 Feign 探活 —— 网络抖一下就变 0,而且区分不了"没装"和"装了但挂了"。
影响(约束了什么,谁要遵守)
- public 不得新增任何 k8s workload 权限。
hiapi-chart里 public 的 serviceAccount rules 保持"batch/jobs + pods,pods/log",新增动词需要推翻本 ADR。 - agent 从第一天就必须带健康判定与自动回滚,并且回滚后立刻上报且停止再试 (不能陷入升级-回滚循环)。它不是"30 行 CronJob"。
/v1/market/*不再是匿名公开接口,要按 entitlement 过滤;public 里硬编码的https://appstore.cloud.adinz.com必须改为可配置(内网客户不通)。- 客户集群里任何"我们自己也能改 workload"的旁路都不许再开 —— 一旦有第二个写入方, 期望状态就不再是真源。
- 后端自助升级仍然锁在 Flyway 之后(见 nuwa 交付中心 §M6), 本 ADR 不改变这一点:agent 让"能不能推"成立,Flyway 才让"敢不敢让客户点"成立。