diff --git a/_bmad-output/planning-artifacts/epics/epic-001-spot-strategy-annotation.md b/_bmad-output/planning-artifacts/epics/epic-001-spot-strategy-annotation.md new file mode 100644 index 0000000..07f4954 --- /dev/null +++ b/_bmad-output/planning-artifacts/epics/epic-001-spot-strategy-annotation.md @@ -0,0 +1,207 @@ +# Epic 001: 抢占式实例注解支持 + +**状态**: 📋 待规划 +**优先级**: P1 - 高优先级 +**创建日期**: 2026-01-01 + +--- + +## 概述 + +当前 vk-eci 项目中,抢占式实例策略(`SpotStrategy`)被硬编码启用,所有 Pod 都会使用抢占式实例。本史诗旨在通过 Pod 注解(Annotation)让用户**自主选择是否启用抢占式实例**,提升灵活性和可用性控制。 + +--- + +## 问题陈述 + +### 当前问题 + +在 [eci.go:212](../../eci.go#L212) 中: +```go +request.SpotStrategy = "SpotAsPriceGo" // 硬编码启用抢占式实例 +``` + +**影响**: +- 所有 Pod 都强制使用抢占式实例 +- 抢占式实例可能被回收,导致核心服务中断 +- 无法为不同工作负载选择合适的实例类型 +- 缺乏对服务稳定性的控制能力 + +### 用户痛点 + +| 用户角色 | 痛点描述 | +|---------|---------| +| 集群管理员 | 核心服务(如 API、数据库)不应该使用可中断的抢占式实例 | +| DevOps 工程师 | 批处理任务希望用抢占式实例降成本,但在线服务需要稳定实例 | +| 开发团队 | 无法控制自己服务的实例类型,影响服务 SLA | + +--- + +## 业务价值 + +### 预期收益 + +- **稳定性提升**:核心服务可使用按量实例,避免被抢占中断 +- **成本优化**:非关键任务(批处理、CI/CD)可选用抢占式实例节省成本 +- **灵活性**:用户根据业务需求自主选择实例类型 +- **向后兼容**:可配置默认行为,保持现有体验 + +--- + +## 需求分解 + +### 功能需求 + +| ID | 需求描述 | +|----|---------| +| FR-001-1 | 支持通过 Pod 注解 `k8s.aliyun.com/eci-spot-instance` 控制是否启用抢占式实例 | +| FR-001-2 | 注解值为 `"true"` 时启用抢占式实例,使用 `SpotAsPriceGo` 策略 | +| FR-001-3 | 注解值为 `"false"` 或未设置时,不设置 `SpotStrategy`(使用按量实例) | +| FR-001-4 | 支持配置全局默认行为(可选,通过 Provider Config) | + +### 非功能需求 + +| ID | 需求描述 | +|----|---------| +| NFR-001-1 | 注解解析不应影响 Pod 创建性能 | +| NFR-001-2 | 保持与现有 EIP 注解、ECS 规格注解的一致性风格 | + +--- + +## 用户故事 + +### Story 001.1: 抢占式实例开关注解支持 + +**作为** 集群管理员, +**我想要** 通过 Pod 注解控制是否使用抢占式实例, +**以便** 为核心服务使用稳定实例,为批处理任务使用低成本抢占式实例。 + +**验收标准**: + +--- + +**Given** Pod 设置了注解 `k8s.aliyun.com/eci-spot-instance: "true"` +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用抢占式实例创建 +**And** `SpotStrategy` 设置为 `SpotAsPriceGo` + +--- + +**Given** Pod 设置了注解 `k8s.aliyun.com/eci-spot-instance: "false"` +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用按量实例创建 +**And** 不设置 `SpotStrategy` 字段 + +--- + +**Given** Pod 未设置 `k8s.aliyun.com/eci-spot-instance` 注解 +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用按量实例创建(默认行为) +**And** 不设置 `SpotStrategy` 字段 + +--- + +## 技术实现要点 + +### 修改位置 + +**文件**: [eci.go](../../eci.go) 第 211-213 行 + +### 实现建议 + +```go +// 抢占式实例配置 - 支持通过注解控制是否启用 +if spotInstance, exists := pod.Annotations["k8s.aliyun.com/eci-spot-instance"]; exists { + if strings.ToLower(spotInstance) == "true" { + request.SpotStrategy = "SpotAsPriceGo" // 启用抢占式实例,使用按价格竞价策略 + } + // false 时不设置 SpotStrategy,使用按量实例 +} +// 默认行为:不设置 SpotStrategy,使用按量实例 +``` + +### 字符串常量定义 + +建议在 [eci.go](../../eci.go) 顶部定义注解名称常量,保持一致性: + +```go +const ( + AnnotationECISpotInstance = "k8s.aliyun.com/eci-spot-instance" + AnnotationECIEipInstanceId = "k8s.aliyun.com/eci-eip-instanceid" + // ... 其他注解常量 +) +``` + +### 添加测试 + +- 单元测试:验证注解解析逻辑(true/false/未设置/非法值) +- 集成测试:验证 ECI API 调用参数正确传递 + +--- + +## 使用示例 + +### 启用抢占式实例(批处理任务) + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: batch-job + annotations: + k8s.aliyun.com/eci-spot-instance: "true" +spec: + containers: + - name: worker + image: nginx +``` + +### 使用按量实例(核心服务) + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: api-server + annotations: + k8s.aliyun.com/eci-spot-instance: "false" +spec: + containers: + - name: api + image: api-server:v1 +``` + +### 默认行为(不设置注解) + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: default-pod +spec: + containers: + - name: app + image: nginx +# 不设置注解,使用按量实例 +``` + +--- + +## 相关文档 + +- [产品概览](../../docs/product-brief-vk-eci-2025-11-06.md) +- [ECI 抢占式实例文档](../../docs/eci.md) +- [阿里云 ECI Spot 实例文档](https://help.aliyun.com/document_detail/87980.html) + +--- + +## 变更历史 + +| 日期 | 版本 | 变更内容 | 作者 | +|------|------|---------|------| +| 2026-01-01 | 0.1 | 初始版本(复杂设计) | Amelia | +| 2026-01-01 | 0.2 | 简化为布尔开关设计 | Amelia | + +--- + +_下一步:创建 Story 001.1 详细文档并进入开发。_