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:
77 files changed
+5224
-73
No files matched your search
+31
-17
@@ -1,7 +1,9 @@
|
||||
package restore
|
||||
|
||||
import (
|
||||
"felis.lolicon.best/internal/archivetransfer"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
@@ -34,22 +36,24 @@ const (
|
||||
// archive ref + Config by the Restorer. jobspec is a pure function of them so
|
||||
// the security-critical Job shape is unit-tested without a cluster.
|
||||
type JobParams struct {
|
||||
Server string
|
||||
WorldPVC string
|
||||
BackupPVC string
|
||||
BackupRef string
|
||||
ArchiveStore string
|
||||
Namespace string
|
||||
ServiceAccount string
|
||||
Image string
|
||||
BackupRoot string
|
||||
WorldsRoot string
|
||||
Deadline time.Duration
|
||||
CPULimit string
|
||||
MemLimit string
|
||||
RunAsUser int64
|
||||
RunAsGroup int64
|
||||
FSGroup int64
|
||||
SourceURL, Token, SHA256 string
|
||||
MaxBytes int64
|
||||
Server string
|
||||
WorldPVC string
|
||||
BackupPVC string
|
||||
BackupRef string
|
||||
ArchiveStore string
|
||||
Namespace string
|
||||
ServiceAccount string
|
||||
Image string
|
||||
BackupRoot string
|
||||
WorldsRoot string
|
||||
Deadline time.Duration
|
||||
CPULimit string
|
||||
MemLimit string
|
||||
RunAsUser int64
|
||||
RunAsGroup int64
|
||||
FSGroup int64
|
||||
|
||||
TTLAfterFinished time.Duration
|
||||
}
|
||||
@@ -92,7 +96,7 @@ func RestoreJob(p JobParams) (*batchv1.Job, error) {
|
||||
if p.Image == "" {
|
||||
return nil, fmt.Errorf("restore: image is empty")
|
||||
}
|
||||
if p.WorldPVC == "" || p.BackupPVC == "" {
|
||||
if p.WorldPVC == "" || (p.BackupPVC == "" && p.SourceURL == "") {
|
||||
return nil, fmt.Errorf("restore: world and backup PVC names are required")
|
||||
}
|
||||
limits, err := resourceLimits(p.CPULimit, p.MemLimit)
|
||||
@@ -192,6 +196,16 @@ func RestoreJob(p JobParams) (*batchv1.Job, error) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if p.SourceURL != "" {
|
||||
if p.Token == "" || p.SHA256 == "" || p.MaxBytes <= 0 {
|
||||
return nil, fmt.Errorf("restore: remote archive credentials and bounds required")
|
||||
}
|
||||
c := &job.Spec.Template.Spec.Containers[0]
|
||||
c.Args = append(c.Args, "--source-url", p.SourceURL, "--sha256", p.SHA256, "--max-bytes", strconv.FormatInt(p.MaxBytes, 10))
|
||||
c.Env = []corev1.EnvVar{{Name: archivetransfer.TokenEnv, Value: p.Token}}
|
||||
c.VolumeMounts[1] = corev1.VolumeMount{Name: "tmp", MountPath: "/tmp"}
|
||||
job.Spec.Template.Spec.Volumes[1] = corev1.Volume{Name: "tmp", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}
|
||||
}
|
||||
return job, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"felis.lolicon.best/internal/naming"
|
||||
"felis.lolicon.best/internal/placement"
|
||||
)
|
||||
|
||||
// ErrAlreadyExists is returned by a Jobs implementation when a restore Job for a
|
||||
@@ -75,6 +76,7 @@ type Jobs interface {
|
||||
// either is empty the caller leaves the API's Restorer nil so the endpoint
|
||||
// reports 503 rather than enqueuing a Job that cannot run.
|
||||
type Config struct {
|
||||
ResolveWorld placement.Resolver
|
||||
// Namespace is where the world PVCs live and the restore Job runs (the
|
||||
// minecraft namespace). The Job is intentionally co-located with the world it
|
||||
// restores; it never runs in the felis control-plane namespace.
|
||||
@@ -199,7 +201,15 @@ type Restorer struct {
|
||||
// keeps the handler's 202 honest in both directions — not a 500 for a genuine
|
||||
// duplicate, and not a false "restoring" for a retry after a failure.
|
||||
func (r *Restorer) Restore(ctx context.Context, serverName, backupRef string) error {
|
||||
if err := r.Jobs.CreateRestoreJob(ctx, r.jobParams(serverName, backupRef)); err != nil {
|
||||
p := r.jobParams(serverName, backupRef)
|
||||
if r.Config.ResolveWorld != nil {
|
||||
w, err := r.Config.ResolveWorld(ctx, serverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.WorldPVC = w.Claim
|
||||
}
|
||||
if err := r.Jobs.CreateRestoreJob(ctx, p); err != nil {
|
||||
if errors.Is(err, ErrAlreadyExists) {
|
||||
return nil // already enqueued — idempotent
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user