跳到正文

Blog

Blueprint 不是 Marketplace:Appaloft 的一键部署为什么先生成计划

Appaloft 如何把 Blueprint 建模为可版本化、可实例化的应用拓扑,而不是普通市场卡片,并用 dry-run install plan 守住部署边界。

AppaloftBlueprint部署控制面MarketplaceAI

“一键部署”听起来像一个按钮问题。用户点一下,平台把应用跑起来,最好还能自动生成访问地址。

但真正难的地方不在按钮。难的是按钮背后到底发生了什么:它有没有先生成计划?依赖资源是新建还是复用?哪些 secret 会被生成,哪些必须由用户提供?健康检查靠什么证明应用已经启动?如果 AI agent 也能触发这次部署,它调用的是同一条产品路径,还是绕过去直接拼 Docker 命令?

这也是 Appaloft 把 BlueprintMarketplaceListing 分开的原因。Blueprint 不是市场卡片,也不是代码脚手架。它是一份 portable、versioned、instantiable 的应用或服务拓扑定义。Marketplace 只是让用户发现、筛选、查看和安装 Blueprint 的产品入口。

这篇文章是上一篇面向 AI 时代的部署控制面的继续:如果部署要能被 CLI、Web Console、GitHub workflow、AI skill 和 MCP 工具共同调用,那么“一键部署”的第一步不应该是执行,而应该是计划。

按钮只是入口

很多部署产品会把复杂性收进一个按钮里。这对初次使用很友好,但如果按钮背后只是一段不可见脚本,团队很快会遇到几个问题:

  • 用户不知道平台准备创建什么资源
  • 数据库、缓存、对象存储和 volume 容易被混成“应用”
  • 连接串和密码可能被当成普通环境变量传来传去
  • 健康检查只证明容器在跑,不证明应用可用
  • AI agent 很容易为了完成任务绕过产品边界

Appaloft 想要的不是把这些细节藏起来,而是把它们变成可检查的结构化计划。

一个 Blueprint 描述的是应用拓扑:components、runtime strategy、ports、routes、health checks、parameters、secrets、dependency resources、profiles、variants 和 upgrade metadata。它不关心自己最终出现在官网市场页、私有企业 registry,还是本地文件 registry 里。

下面是一个删减后的 PocketBase Blueprint 片段:

schemaVersion: appaloft.blueprint/v1
id: cloud-pocketbase
name: PocketBase
version: 1.0.0

secrets:
  - key: POCKETBASE_ADMIN_PASSWORD
    source: generated
    generation:
      bytes: 32
      encoding: base64url

resources:
  - id: data
    kind: volume

components:
  - id: pocketbase
    kind: service
    runtime:
      strategy: container-image
      image: ghcr.io/muchobien/pocketbase:latest
    ports:
      - name: http
        containerPort: 8090
        protocol: http
        public: true
    healthCheck:
      enabled: true
      type: http
      http:
        path: /api/health
        expectedStatusCode: 200
    usesSecrets:
      - POCKETBASE_ADMIN_PASSWORD
    usesResources:
      - data

这段配置里有几个刻意的选择。

POCKETBASE_ADMIN_PASSWORD 是 generated secret。计划和 UI 可以展示它会被生成、使用什么生成策略、是否必需,但不能把真实值写进计划结果。data 是 volume dependency,不是 Marketplace 里的一张单独应用卡片。HTTP health check 指向 PocketBase 自己的 /api/health,而不是泛泛地相信容器已经启动。

这些小规则加起来,才让“一键部署”变得可解释。

Marketplace 不是领域模型

Marketplace listing 负责发现体验。它可以有标题、副标题、分类、icon、官网链接、文档链接、截图、推荐理由和本地化文案。Cloud 官方 catalog 里的每个条目通常会有两份文件:

  • listing.yaml:Cloud marketplace discovery metadata
  • blueprint.yaml:portable appaloft.blueprint/v1 manifest

这两个文件不应该合并。Listing 可以说“PocketBase 适合小型产品 API 和内部后台”,但真正决定部署计划的是 Blueprint。Listing 可以排序、精选、翻译、审核;Blueprint 仍然要通过中性的 schema、semantic validation 和 planner。

这个边界看起来有点啰嗦,但它避免了一个常见陷阱:把产品展示层的概念塞进部署模型。

如果 featured: true、category、publisher review、计费策略或团队分享权限都混进 Blueprint,本地 Community 用户会被迫理解 Cloud 产品语义。反过来,如果 Marketplace listing 直接拥有 ports、secret、dependency readiness 和 runtime projection,部署引擎就会被展示层牵着走。

Appaloft 的规则更窄:MarketplaceListing 可以指向 BlueprintVersion,但它不是 Blueprint。

先 dry-run,再执行

Blueprint 安装的第一步是 dry-run install plan。它接收 Blueprint manifest、profile、variant 和用户输入,然后生成确定性的计划。这个计划可以包含 create project、create environment、create resource、configure runtime、set variable、bind dependency、create deployment 等中性 operation。

在 Cloud Marketplace v1 里,dry-run plan 明确返回 createsExternalResources: false。这句话很重要:查看计划不会写数据库状态,不会创建外部 provider resource,不会触发部署,不会发出 billing event,也不会偷偷把 secret material 暴露在响应里。

这让 UI、API 和 AI 工具都可以安全地先问:“如果我要安装这个 Blueprint,会发生什么?”

计划结果也给 review 留出了空间。用户能看到哪些组件会变成 Appaloft Resource,哪些 dependency 会被绑定,哪些 secret 需要自己提供,哪些可以由平台生成,哪些 health check 会用来判断应用就绪。之后真正的 accepted install command 才进入持久化、执行、readback、失败处理和 rollback 边界。

我们不想让“预览”变成“顺手执行”。这个纪律对人和 AI 都很有用。

依赖资源不应该伪装成应用

Blueprint 里最容易混淆的是 dependency resource。

Postgres、Redis、MySQL/MariaDB、ClickHouse、object storage、OpenSearch、volume 都可以是应用需要的资源,但它们不是 Marketplace 里的应用卡片。它们应该作为 DependencyResourceRequirement 进入 Blueprint,并在计划里产生 dependency binding。

例如,一个分析应用可能需要 Postgres 和 ClickHouse。正确的做法不是把 ClickHouse 做成“可部署应用”,然后让用户手工复制连接串。更好的方式是让 Blueprint 声明 ClickHouse requirement、readiness、标准 outputs 和 component dependencyEnv。计划阶段只携带输出字段、secret metadata、readiness 和绑定意图;真实 password、URL 和 provider credential 只能在 accepted execution 边界通过 secret ref 解析。

这也是为什么 readiness 要贴近应用实际使用的协议。ClickHouse HTTP 客户端需要 HTTP /ping 可用;Postgres 需要能接受连接;Redis 至少要能 PING。容器状态不是同一件事。

多组件应用也还是一个应用

有些 Blueprint 只有一个 service,比如 PocketBase。有些会包含多个 components,比如 gateway、worker、可选模型 runtime、后台任务和若干 dependency bindings。

Appaloft 不希望这类应用在 Marketplace 里被拆成几张互不相关的卡片。用户安装的是一个应用拓扑,不是随机挑几个容器。公共的 BlueprintApplicationBundlePlan 负责把一个 Blueprint install plan 中的 components、deployment intents 和 dependency-resource bindings 分组为同一个 application-level plan。

Cloud 里的 InstalledApplication 是另一个边界。它不是 Blueprint definition,也不是 Marketplace listing。它是在用户接受安装之后形成的持久生命周期:记录 Blueprint version、variant、profile、component resource/deployment refs、dependency bindings、用户配置、readback、失败状态和 rollback 入口。

这听起来像 DDD 文章里的 aggregate root。确实是。我们在Aggregate Root 那篇里讲过,InstalledApplication 负责生命周期规则,而不是让 provider adapter 或 repository 随意改状态。

AI 部署需要这个边界

AI agent 很擅长把一个模糊请求拆成动作:“帮我部署一个 n8n”,“给这个项目加 Postgres”,“看看为什么访问地址打不开”。如果部署系统没有结构化计划,agent 很容易退回到猜命令、读日志、直接 SSH 或调用 provider API。

Appaloft 的目标是让 AI 调用同一条操作路径:

  1. 读取 Blueprint 或 repository deployment config
  2. 生成 install/deploy plan
  3. 展示会创建或绑定的资源
  4. 明确 secret 和权限缺口
  5. 由用户或策略接受后再执行
  6. 通过 readback、health check 和事件观察结果

AI skill 负责告诉 agent 应该按什么顺序思考。MCP/tool surface 负责让 agent 稳定调用控制面。Blueprint plan 则负责把“一键部署”变成可检查的中间事实。

这三者不应该互相替代。

代价

这种设计不如脚本直接。

Blueprint schema 要维护,official catalog 要校验,dependency contract 要覆盖常见资源,health check 要尽量贴近真实应用,runtime smoke 还会发现一些很烦的小错:镜像 tag 拉不下来、根路径不是产品 UI、公开端口其实没有 HTTP 服务、应用启动后还要等迁移完成。

但这些麻烦正是部署平台应该面对的。如果 schema 和 dry-run plan 都看不出一个应用会怎么启动,用户和 AI 更不可能在生产问题里猜对。

Appaloft 对 Blueprint 的判断很朴素:它不是市场卡片,也不是 Cloud-only 商品。它是一份可复用的部署拓扑定义。Marketplace 让它可被发现,install plan 让它可被 review,accepted command 让它进入真实执行边界,readback 让它不止停在“命令成功”。

一键部署可以从一个按钮开始,但不能只剩一个按钮。