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

+17
View File
@@ -52,6 +52,7 @@ import (
"time"
"felis.lolicon.best/internal/naming"
"felis.lolicon.best/internal/placement"
)
// Errors the Editor returns, which internal/api maps onto HTTP status codes
@@ -106,6 +107,7 @@ type Runner interface {
// deployment-specific, and when it is empty cmd/felis leaves the API's FileEditor
// nil so the endpoints report 503 rather than creating a Job that cannot run.
type Config struct {
ResolveWorld placement.Resolver
// Namespace is where the world PVCs live and the Job runs (the minecraft
// namespace), co-located with the world it edits.
Namespace string
@@ -338,6 +340,14 @@ func (e *Editor) run(ctx context.Context, server string, p JobParams) (Result, e
return Result{}, err
}
if cfg.ResolveWorld != nil {
world, err := cfg.ResolveWorld(ctx, server)
if err != nil {
return Result{}, err
}
p.WorldPVC, p.NodeName = world.Claim, world.Node
}
// Bound the wait here rather than trusting the caller's context: this is an HTTP
// handler's goroutine and the Pod it waits on may never become ready (an
// unschedulable node, an unpullable image). The Job's own activeDeadlineSeconds
@@ -438,6 +448,13 @@ func (e *Editor) start(ctx context.Context, server string, p JobParams) (OpState
if err != nil {
return OpState{}, err
}
if cfg.ResolveWorld != nil {
world, err := cfg.ResolveWorld(ctx, server)
if err != nil {
return OpState{}, err
}
p.WorldPVC, p.NodeName = world.Claim, world.Node
}
p.Async = true
p.Deadline, p.TTLAfterFinished, p.CPULimit = cfg.AsyncDeadline, cfg.AsyncTTL, cfg.AsyncCPULimit
if err := e.Runner.Start(ctx, p); err != nil {