集群状态生命周期:Status 与 InitStatus 联动¶
背景¶
当前集群有两个独立的状态字段:
| 字段 | 类型 | 值 | 谁控制 |
|---|---|---|---|
Status |
运维状态 | active / inactive / maintenance |
管理员手动切换 |
InitStatus |
生命周期 | "" / pending / running / completed / failed |
系统自动 |
问题:创建集群时 autoTest 通过即设 Status = active,不等待 InitStatus。导致一个初始化失败(无 Operator、无法承载租户)的集群在列表页显示为「运行中」,误导管理员。
列表页只展示 Status,不展示 InitStatus,加剧了信息断层。
设计原则¶
- 不合并两个字段 — 它们代表正交维度(运维状态 vs 生命周期),合并会丢失表达力
- InitStatus 联动 Status — 初始化结果自动影响运维状态,无需管理员手动干预
- 管理员操作优先 — 管理员主动设置的 Status 不被系统自动回退覆盖
- 列表页单字段可读 — 只看 Status 列就能判断集群是否可用
状态模型¶
Status 新增值:initializing¶
Status 枚举值:
active — 就绪,可承载租户
initializing — 创建后正在初始化(新增)
maintenance — 维护中(管理员手动 或 初始化失败自动)
inactive — 已停用(管理员手动)
对应数据库 check 约束和 Go binding 的 oneof 需要新增 initializing。
状态转换图¶
┌──── 管理员手动 ────┐
▼ │
创建 ──→ initializing ──→ active ←──────→ maintenance
│ │ ▲
│ └──→ inactive │
│ │
└──── init failed ───────────────┘
重新初始化:
maintenance(init_failed) ──→ initializing ──→ active / maintenance
完整组合矩阵¶
| Status | InitStatus | 列表展示 | 含义 | 可执行操作 |
|---|---|---|---|---|
initializing |
running |
🔄 初始化中 | 刚创建,正在部署 Operator | 查看进度 |
initializing |
pending |
🔄 初始化中 | 任务排队中 | 查看进度 |
active |
completed |
🟢 运行中 | 就绪,可承载租户 | 切换状态、创建租户 |
active |
"" |
🟢 运行中 | 未开启 autoInit 或历史集群 | 切换状态 |
maintenance |
failed |
🟠 维护中 | 初始化失败,需排查 | 重新初始化、查看日志 |
maintenance |
"" |
🟠 维护中 | 管理员手动维护 | 恢复为运行中 |
inactive |
任意 | ⚫ 已停用 | 管理员手动下线 | 恢复为运行中 |
不合法组合(应杜绝)¶
| Status | InitStatus | 为什么不合法 |
|---|---|---|
active |
running |
还在初始化,不应标记为可用 |
active |
failed |
初始化失败,不应标记为可用 |
active |
pending |
还未初始化,不应标记为可用 |
inactive |
running |
已停用的集群不应在初始化 |
涉及文件¶
| 文件 | 变更类型 | 说明 |
|---|---|---|
internal/shared/db/models/k8s_cluster.go |
修改 | Status 枚举新增 initializing |
internal/admin/api/dto/cluster_dto.go |
修改 | binding 新增 initializing;响应新增 initStatus 展示 |
internal/admin/api/handlers/cluster_handler.go |
修改 | 创建逻辑调整初始 Status |
internal/admin/service/cluster_service.go |
修改 | 创建流程 Status 初始化逻辑 |
internal/admin/service/cluster_init_executor.go |
修改 | updateClusterInitStatus 联动 Status |
| 数据库迁移 | 新增 | status 字段 check 约束新增 initializing |
实现细节¶
1. 创建集群时的 Status 初始化¶
当前逻辑(cluster_service.go 创建流程):
新逻辑:
autoInit=true → Status = "initializing"(无论 autoTest 结果如何)
autoInit=false → 走当前逻辑(autoTest 决定 active/inactive)
// cluster_service.go CreateCluster 方法中
func (s *ClusterService) CreateCluster(ctx context.Context, req *dto.CreateClusterRequest, adminID uint) (*dto.ClusterResponse, error) {
// ... 创建集群记录 ...
// 确定初始 Status
if autoInit {
cluster.Status = "initializing"
cluster.InitStatus = "pending"
} else if autoTest {
// autoTest 通过则 active,失败则 inactive(当前逻辑不变)
}
// ... 保存并启动异步任务 ...
}
2. InitStatus 联动 Status¶
修改 cluster_init_executor.go 的 updateClusterInitStatus:
func (e *ClusterInitExecutor) updateClusterInitStatus(ctx context.Context, clusterID uint, initStatus string) {
cluster, err := e.clusterRepo.GetByID(ctx, clusterID)
if err != nil {
logger.GetLogger().Warn("更新集群状态失败:获取集群失败",
zap.Uint("cluster_id", clusterID), zap.Error(err))
return
}
cluster.InitStatus = initStatus
// 联动 Status(仅在系统可控状态下联动,不覆盖管理员操作)
switch initStatus {
case "running":
// 仅当 Status 为 initializing 或 pending 时联动
// 如果管理员已手动设为 maintenance/inactive,不干预
if cluster.Status == "initializing" || cluster.Status == "" {
cluster.Status = "initializing"
}
case "completed":
// 初始化完成 → 升级为 active
// 除非管理员已手动设为 inactive(尊重管理员意图)
if cluster.Status != "inactive" {
cluster.Status = "active"
}
case "failed":
// 初始化失败 → 降级为 maintenance
// 除非管理员已手动设为 inactive
if cluster.Status != "inactive" {
cluster.Status = "maintenance"
}
}
if err := e.clusterRepo.Update(ctx, cluster); err != nil {
logger.GetLogger().Warn("更新集群状态失败",
zap.Uint("cluster_id", clusterID),
zap.String("initStatus", initStatus),
zap.String("status", cluster.Status),
zap.Error(err))
}
}
联动规则总结:
| InitStatus 变更 | 当前 Status | 联动后 Status | 说明 |
|---|---|---|---|
→ running |
initializing |
initializing |
保持 |
→ running |
maintenance |
maintenance |
管理员已介入,不覆盖 |
→ completed |
initializing |
active |
初始化成功,升级 |
→ completed |
maintenance |
active |
重新初始化成功 |
→ completed |
inactive |
inactive |
尊重管理员停用意图 |
→ failed |
initializing |
maintenance |
初始化失败,降级 |
→ failed |
active |
maintenance |
重新初始化失败,降级 |
→ failed |
inactive |
inactive |
尊重管理员停用意图 |
3. 管理员手动切换 Status 的约束¶
管理员通过 API 切换 Status 时,新增校验:
// cluster_handler.go UpdateClusterStatus
func (h *ClusterHandler) UpdateClusterStatus(c *gin.Context) {
// ... 参数解析 ...
// 不允许手动设为 initializing(系统专用状态)
if req.Status == "initializing" {
c.JSON(400, gin.H{"error": "initializing 为系统状态,不可手动设置"})
return
}
// 如果集群正在初始化中,警告但不阻止
// (管理员可能需要在初始化卡住时手动切换到 maintenance)
cluster, _ := h.service.GetCluster(ctx, clusterID)
if cluster.InitStatus == "running" && req.Status == "active" {
c.JSON(400, gin.H{"error": "集群正在初始化中,初始化完成后将自动切换为运行中"})
return
}
// ... 执行更新 ...
}
4. 重新初始化支持¶
初始化失败后,管理员应能触发重新初始化。当前 initCluster API 需要支持:
// 重新初始化前置校验
if cluster.InitStatus == "running" {
return errors.New("集群正在初始化中,请等待完成或取消")
}
// 重置状态
cluster.Status = "initializing"
cluster.InitStatus = "pending"
// 启动新的初始化任务...
5. DTO 和 API 响应¶
ClusterResponse 和列表 API 中补充 initStatus 字段(当前已有但列表未展示):
6. 数据库迁移¶
-- 新增 initializing 到 status 枚举
-- 如果使用 check 约束:
ALTER TABLE k8s_clusters DROP CONSTRAINT IF EXISTS k8s_clusters_status_check;
ALTER TABLE k8s_clusters ADD CONSTRAINT k8s_clusters_status_check
CHECK (status IN ('active', 'inactive', 'maintenance', 'initializing'));
-- 历史数据修复:将 status=active 但 init_status=failed 的记录修正
UPDATE k8s_clusters
SET status = 'maintenance'
WHERE status = 'active' AND init_status = 'failed';
-- 将 status=active 但 init_status=running 的记录修正
UPDATE k8s_clusters
SET status = 'initializing'
WHERE status = 'active' AND init_status IN ('running', 'pending');
7. binding 校验更新¶
// cluster_dto.go
// CreateClusterRequest - Status 字段(一般不在创建时指定,由系统决定)
// 无需改动
// UpdateClusterRequest
Status string `json:"status" binding:"omitempty,oneof=active inactive maintenance"`
// 注意:不包含 initializing,管理员不可手动设置
// UpdateClusterStatusRequest
Status string `json:"status" binding:"required,oneof=active inactive maintenance"`
// 同上,不包含 initializing
// ListClustersRequest
Status string `form:"status" binding:"omitempty,oneof=active inactive maintenance initializing"`
// 查询过滤包含 initializing,以便管理员筛选
前端适配¶
列表页¶
在集群列表表格中,Status 列的展示需要适配新状态:
| Status 值 | 展示 | 颜色 |
|---|---|---|
active |
运行中 | 绿色 |
initializing |
初始化中 | 蓝色(带 loading 动画) |
maintenance |
维护中 | 橙色 |
inactive |
已停用 | 灰色 |
可选:当 Status=maintenance 且 InitStatus=failed 时,展示为「初始化失败」而非「维护中」,帮助管理员快速区分人工维护和系统故障。
详情页¶
已有初始化进度组件,无需改动。
状态切换下拉¶
移除 initializing 选项(系统专用),其余不变。
向后兼容¶
- 历史集群(
InitStatus为空):Status不受影响,行为与当前一致 - API 消费者:
initializing是新增值,不破坏现有active/inactive/maintenance的消费逻辑。但如果消费者对 Status 做了穷举匹配(switch/case without default),需要适配 - 数据库:需要迁移脚本扩展 check 约束 + 修复历史脏数据
测试计划¶
| 场景 | 预期 Status | 预期 InitStatus |
|---|---|---|
| 创建集群(autoInit=true) | initializing |
pending → running |
| 初始化成功 | active |
completed |
| 初始化失败 | maintenance |
failed |
| 初始化失败后管理员手动切 inactive | inactive |
failed |
| 重新初始化 | initializing |
pending → running |
| 重新初始化成功 | active |
completed |
| 创建集群(autoInit=false, autoTest=true) | active |
"" |
| 管理员尝试手动设 initializing | 400 错误 | — |
| 初始化中管理员尝试设 active | 400 错误 | — |