# ADR-0004:采用可拆分的模块化 Controller 架构 - 状态:Accepted - 日期:2026-09-17 ## 背景 Ayatori 将逐步提供任务执行、虚拟机、数据库、负载均衡、对象存储、托管 Kubernetes 和 人工操作等领域能力。这些能力拥有不同的生命周期、权限、网络位置和后端实现,若直接在 一个 controller 中相互调用并共享内部状态,后续接入新后端时容易形成代码耦合,也难以 独立扩缩容、发布和隔离故障。 另一方面,在首个领域能力完成前就拆分为多个独立服务,会立即引入镜像与部署管理、服务 间认证、版本兼容、分布式观测和故障处理成本,而这些成本尚未由真实运行需求证明。 Job 是首个领域对象。它既是最初的单次任务调度 API,也用于验证领域状态机与 Kubernetes Job、OpenSandbox 和未来执行后端之间的适配边界。 ## 决策 Ayatori 初期采用模块化单体:多个领域 controller 可以编译进同一个 controller manager, 但代码、API 所有权和依赖方向按照未来可独立部署的服务边界组织。 ### 领域所有权 每个领域模块拥有自己的: - CRD 与 API 版本; - reconciler 和状态机; - finalizer、conditions、删除及恢复语义; - backend adapter contract; - 领域测试。 初始领域包括但不限于 execution、compute、database、networking 和 human operations。领域 模块不得导入其他领域的内部实现,也不得直接修改其他领域所拥有对象的 spec 或 status。 ### 跨领域协作 领域间的持久协作通过 Kubernetes API 对象、typed reference、owner reference 和 conditions 完成,而不是通过进程内 service 方法调用。 例如虚拟机完成创建后需要执行 provision,应创建或引用 Run 对象并观察其状态,而不是 直接调用 execution 模块的内部 Go API。更高层的资源组合由专门的领域对象或 GitOps 声明 完成,不引入统一包装所有底层能力的 Application controller。 该约束使 controller 即使暂时位于同一进程,其通信、失败和恢复行为仍与未来分进程部署 一致。 ### Backend adapter 领域状态机只依赖本领域定义的最小 adapter contract,不依赖 Kubernetes Job、Crossplane、 OpenTofu、OpenSandbox 或具体厂商 SDK 类型。Adapter 负责: - 幂等地确保期望外部资源存在; - 观察并翻译外部状态; - 执行取消、删除或 orphan 策略; - 返回稳定的外部引用、能力和分类错误。 不同领域分别定义 adapter contract,不建立能包装所有资源类型的万能 Provider 接口。 后端不具备的能力必须显式报告,不通过虚假的统一语义隐藏差异。 Crossplane、OpenTofu 和 Ansible 等系统是可替换的 backend 实现或执行机制,不构成 Ayatori 面向用户的稳定 API。它们的 ProviderConfig、Workspace、playbook 等实现细节不得直接成为 领域 API 的必填契约。 ### 共享代码 默认不建立跨领域的万能 service、repository 或 util 层。共享并非禁止,但必须来自已经 存在的真实重复,并同时满足: 1. 至少有两个真实调用者; 2. 重复的行为和语义一致,而不只是代码形状相似; 3. 调用方对其生命周期和预期演化方向一致; 4. 共享包不依赖任何具体领域的内部包。 适合共享的通常是机制,例如 conditions 操作、typed reference、重试退避、Secret 引用 读取、观测初始化和测试环境。领域状态机、资源策略、错误含义及后端选择不得为了消除少量 重复而抽取到共享层。 共享包使用表达具体职责的窄名称,初期保留在 `internal/shared/`。不建立内容持续膨胀的 通用 `util` 包,也不在实现稳定前承诺公共 Go API。 ### 部署与拆分 controller manager 应支持按领域或 controller 集合选择性启用。初期可以使用一个二进制和 一个 Deployment;需要隔离时,优先使用同一制品部署为多个 Deployment。只有独立版本和 依赖关系成为真实需求后,才进一步拆分二进制或仓库。 出现下列任一情况时,应评估拆分部署: - 需要独立扩缩容或显著不同的 reconcile 并发; - 权限边界要求独立 ServiceAccount 与 RBAC; - 后端只能从特定网络或节点访问; - 沉重、不可信或冲突的 SDK 需要隔离; - 一个领域的故障不应影响其他控制循环; - 发布节奏或维护责任已经明确分离。 拆分不得改变领域 API,也不应将原本通过 API 对象完成的协作改为同步 RPC 链路。 ## 结果 - 首个 Job 实现需要同时建立 execution 状态机和 adapter 边界,不能把 Kubernetes Job 细节写入领域模型。 - 初期避免承担不必要的微服务运维成本,同时保留按权限、网络位置和故障域拆分的路径。 - Kubernetes API 成为领域间异步协作和恢复边界,领域 controller 必须正确处理最终一致性。 - 部分机械代码会有意保留重复,直到共享语义被至少两个真实实现证明。 - 代码评审需要检查跨领域 import、对象写入所有权和后端类型泄漏。 - 当 Job 同时拥有 Kubernetes Job 与 OpenSandbox adapter 后,应复审 adapter contract,确认 它来自真实后端差异而非单一实现假设。