feat(distributed): 支持单主控多节点部署和停服迁移

复用现有 k3s 调度和 Job 生命周期,增加 worker 接入与批准、受保护节点身份、归档传输、持久迁移锁及活动 PVC 切换;同步管理员 API、CLI、面板和隔离规则。分布式模式默认关闭,保持单机兼容。

验证:Go 全量测试与 vet;面板 874 个测试、lint/build;Linux VM 安装器测试、清单服务端 dry-run、网络命名空间防火墙实测。A/B/C 三机 WireGuard、Velocity 和迁移验收仍待完成。
This commit is contained in:
Lemon-miaow committed 2026-10-01 19:48:37 +08:00
1 parent 26e817f2c9
commit adf563ffe3
77 files changed
+5224 -73

No files matched your search

+199
View File
@@ -468,11 +468,47 @@ components:
description: MinecraftServer lifecycle phase (internal/apis/felis/v1alpha1).
enum: [Unknown, Stopped, Starting, Running, Stopping, Failed]
ExecutionNode:
type: object
required: [name, role, ready, approved, addresses, architecture]
properties:
name: { type: string }
role: { type: string }
ready: { type: boolean }
approved: { type: boolean }
addresses: { type: array, items: { type: string } }
architecture: { type: string }
WorldMigration:
type: object
required: [id, server, state, stage, sourceNode, targetNode, sourcePVC, targetPVC, backup, started, updated, switched, attempt]
properties:
id: { type: string }
server: { type: string }
state: { type: string, enum: [backing_up, restoring, switching, succeeded, failed] }
stage: { type: string }
sourceNode: { type: string }
targetNode: { type: string }
sourcePVC: { type: string }
targetPVC: { type: string }
backup:
type: object
required: [ref, size, sha256]
properties:
ref: { type: string }
size: { type: integer, format: int64 }
sha256: { type: string }
started: { type: string, format: date-time }
updated: { type: string, format: date-time }
switched: { type: boolean }
attempt: { type: integer }
error: { type: string }
ServerInfo:
type: object
description: Status projection of one server (internal/api/cluster.go ServerInfo).
required: [name, subdomain, phase, ready, playersOnline, playersMax, idleStopSeconds]
properties:
nodeName: { type: string, description: Execution node; legacy servers report the observed node. }
name: { type: string }
subdomain: { type: string }
phase: { $ref: '#/components/schemas/Phase' }
@@ -1106,6 +1142,168 @@ components:
on. Absent otherwise.
paths:
/api/v1/nodes:
get:
operationId: executionNodes
tags: [admin-servers]
summary: List execution nodes (administrator).
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
responses:
'200':
description: Accepted operation or current state.
content:
application/json:
schema:
type: object
required: [nodes]
properties:
nodes: { type: array, items: { $ref: '#/components/schemas/ExecutionNode' } }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/servers/{name}/migrations:
get:
operationId: latestWorldMigrationStatus
tags: [admin-servers]
summary: Read durable migration progress.
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
responses:
'200':
description: Accepted operation or current state.
content:
application/json:
schema:
$ref: '#/components/schemas/WorldMigration'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
post:
operationId: startWorldMigration
tags: [admin-servers]
summary: Migrate an already stopped world; persistent lock survives controller restart.
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [targetNode]
properties:
targetNode: { type: string }
responses:
'202':
description: Accepted operation or current state.
content:
application/json:
schema:
$ref: '#/components/schemas/WorldMigration'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/servers/{name}/migrations/{id}:
get:
operationId: worldMigrationStatus
tags: [admin-servers]
summary: Read durable migration progress.
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200':
description: Accepted operation or current state.
content:
application/json:
schema:
$ref: '#/components/schemas/WorldMigration'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/servers/{name}/migrations/{id}/retry:
post:
operationId: retryWorldMigration
tags: [admin-servers]
summary: Retry a failed migration, retaining the source and stopped state.
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'202':
description: Accepted operation or current state.
content:
application/json:
schema:
$ref: '#/components/schemas/WorldMigration'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
# ----------------------------------------------------------------- health ---
/healthz:
get:
@@ -1272,6 +1470,7 @@ paths:
type: object
required: [name, subdomain]
properties:
nodeName: { type: string, description: Required approved worker in distributed mode; administrator only. }
name: { type: string }
subdomain: { type: string }
displayName: