From 0cb22c1f7c916b8a5e6c47d8e8cf1ca5292ead11 Mon Sep 17 00:00:00 2001 From: D8D Developer Date: Thu, 1 Jan 2026 09:18:25 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(spot-strategy):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=8A=A2=E5=8D=A0=E5=BC=8F=E5=AE=9E=E4=BE=8B=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加故事 1.1 的实现工件文档,定义抢占式实例开关注解功能 - 更新 sprint 状态文件,将故事标记为 ready-for-dev 并开始 epic-1 的开发 --- .../1-1-spot-strategy-annotation.md | 162 ++++++++++++++++++ docs/sprint-status.yaml | 13 +- 2 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/1-1-spot-strategy-annotation.md diff --git a/_bmad-output/implementation-artifacts/1-1-spot-strategy-annotation.md b/_bmad-output/implementation-artifacts/1-1-spot-strategy-annotation.md new file mode 100644 index 0000000..58dfe35 --- /dev/null +++ b/_bmad-output/implementation-artifacts/1-1-spot-strategy-annotation.md @@ -0,0 +1,162 @@ +# Story 1.1: 抢占式实例开关注解支持 + +Status: ready-for-dev + + + +## Story + +作为集群管理员, +我想要通过 Pod 注解控制是否使用抢占式实例, +以便为核心服务使用稳定实例,为批处理任务使用低成本抢占式实例。 + +## Acceptance Criteria + +### AC-001: 启用抢占式实例 +**Given** Pod 设置了注解 `k8s.aliyun.com/eci-spot-instance: "true"` +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用抢占式实例创建 +**And** `SpotStrategy` 设置为 `SpotAsPriceGo` + +### AC-002: 禁用抢占式实例 +**Given** Pod 设置了注解 `k8s.aliyun.com/eci-spot-instance: "false"` +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用按量实例创建 +**And** 不设置 `SpotStrategy` 字段 + +### AC-003: 默认行为(未设置注解) +**Given** Pod 未设置 `k8s.aliyun.com/eci-spot-instance` 注解 +**When** Pod 被调度到 Virtual Kubelet +**Then** ECI 实例使用按量实例创建(默认行为) +**And** 不设置 `SpotStrategy` 字段 + +## Tasks / Subtasks + +- [ ] T-001: 定义注解名称常量 (AC: 全部) + - [ ] 在 eci.go 顶部添加 `AnnotationECISpotInstance` 常量 + - [ ] 确保与其他注解常量命名风格一致 + +- [ ] T-002: 实现注解解析逻辑 (AC: 001, 002, 003) + - [ ] 修改 eci.go 第 211-213 行的硬编码逻辑 + - [ ] 添加注解检查:`pod.Annotations["k8s.aliyun.com/eci-spot-instance"]` + - [ ] 实现字符串比较逻辑(不区分大小写) + - [ ] 仅当值为 "true" 时设置 `SpotStrategy` + +- [ ] T-003: 添加单元测试 (AC: 001, 002, 003) + - [ ] 测试注解值为 "true" → 设置 SpotStrategy + - [ ] 测试注解值为 "false" → 不设置 SpotStrategy + - [ ] 测试注解未设置 → 不设置 SpotStrategy + - [ ] 测试注解值为其他值 → 不设置 SpotStrategy + +- [ ] T-004: 代码审查与提交 (AC: 全部) + - [ ] 运行 `make vet` 检查代码质量 + - [ ] 运行 `make test` 验证测试通过 + - [ ] 更新相关文档 + +## Dev Notes + +### 问题背景 +当前 vk-eci 项目中,抢占式实例策略(`SpotStrategy`)在 [eci.go:212](../../eci.go#L212) 被硬编码启用,所有 Pod 都会使用抢占式实例。这导致核心服务可能被抢占中断,影响服务稳定性。 + +### 修改位置 +**主要文件**: [eci.go](../../eci.go) 第 211-213 行 + +**当前代码**: +```go +// 添加抢占式实例策略配置 +request.SpotStrategy = "SpotAsPriceGo" // 设置抢占式实例策略为按价格竞价 +//request.SpotDuration = 0 // 设置抢占式实例持续时间为0(非定时抢占) +``` + +### 实现方案 + +#### 1. 定义注解常量 +在 eci.go 顶部的常量定义区域(约第 32-36 行附近)添加: + +```go +const ( + serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" + podTagTimeFormat = "2006-01-02T15-04-05Z" + timeFormat = "2006-01-02T15:04:05Z" + + // Pod 注解常量 + AnnotationECISpotInstance = "k8s.aliyun.com/eci-spot-instance" + AnnotationECIEipInstanceId = "k8s.aliyun.com/eci-eip-instanceid" + AnnotationECIUseSpecs = "k8s.aliyun.com/eci-use-specs" + AnnotationECIWithEip = "k8s.aliyun.com/eci-with-eip" +) +``` + +#### 2. 修改 SpotStrategy 设置逻辑 +将第 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,使用按量实例 +``` + +### 现有注解模式参考 +项目已有类似的注解处理模式,请保持一致: + +**EIP 自动创建注解** (eci.go:195-209): +```go +if withEip, exists := pod.Annotations["k8s.aliyun.com/eci-with-eip"]; exists { + if withEip == "true" { + request.AutoCreateEip = requests.Boolean(strconv.FormatBool(true)) + } +} +if eipInstanceId, exists := pod.Annotations["k8s.aliyun.com/eci-eip-instanceid"]; exists { + request.EipInstanceId = eipInstanceId +} +``` + +**ECS 规格注解** (eci.go:190-193): +```go +if specs, exists := pod.Annotations["k8s.aliyun.com/eci-use-specs"]; exists { + request.InstanceType = specs +} +``` + +### Project Structure Notes + +**项目路径结构**: +``` +/mnt/code/vk-eci/ +├── eci.go # 主文件 - 需要修改 +├── eci/ # ECI API 客户端包 +├── cmd/virtual-kubelet/ # CLI 入口 +├── docs/ # 项目文档 +└── _bmad-output/ # BMAD 工作流输出 +``` + +**代码风格对齐**: +- 遵循 Go 标准命名规范 +- 注解值比较使用 `strings.ToLower()` 不区分大小写 +- 布尔值字符串比较使用 `== "true"` 模式(与现有 EIP 注解一致) +- 保持与其他注解处理相同的代码结构 + +### References + +- [Epic 001: 抢占式实例注解支持](../planning-artifacts/epics/epic-001-spot-strategy-annotation.md) +- [eci.go:212 - 当前硬编码位置](../../eci.go#L212) +- [eci.go:190-209 - 现有注解处理模式](../../eci.go#L190-L209) +- [ECI 抢占式实例文档](../../docs/eci.md) +- [项目概览](../../docs/project-overview.md) + +## Dev Agent Record + +### Agent Model Used + +BMad Scrum Master (sm) - YOLO Mode + +### Debug Log References + +### Completion Notes List + +### File List diff --git a/docs/sprint-status.yaml b/docs/sprint-status.yaml index 521a4c0..9d165c4 100644 --- a/docs/sprint-status.yaml +++ b/docs/sprint-status.yaml @@ -5,15 +5,10 @@ generated: "2025-11-26" project: "vk-eci" sprint: "Sprint-1" -stories: - - id: "STORY-001" - title: "实现 ECI 抢占式实例 SpotToBeReleased 事件处理" - file: "docs/stories/spot-instance-event-handling.md" - status: "TODO" - priority: "高" - effort: "中等" - assignee: "" - created: "2025-11-26" +# BMAD 故事状态跟踪 +development_status: + "1-1-spot-strategy-annotation": "ready-for-dev" + "epic-1": "in-progress" # 故事状态定义 # TODO - 待开始