✨ feat(spot-strategy): 新增抢占式实例注解支持
- 添加故事 1.1 的实现工件文档,定义抢占式实例开关注解功能 - 更新 sprint 状态文件,将故事标记为 ready-for-dev 并开始 epic-1 的开发
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# Story 1.1: 抢占式实例开关注解支持
|
||||
|
||||
Status: ready-for-dev
|
||||
|
||||
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
||||
|
||||
## 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
|
||||
@@ -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 - 待开始
|
||||
|
||||
Reference in New Issue
Block a user