Files
Felis/internal/placement/placement.go
T
Lemon-miaow adf563ffe3 feat(distributed): 支持单主控多节点部署和停服迁移
复用现有 k3s 调度和 Job 生命周期,增加 worker 接入与批准、受保护节点身份、归档传输、持久迁移锁及活动 PVC 切换;同步管理员 API、CLI、面板和隔离规则。分布式模式默认关闭,保持单机兼容。

验证:Go 全量测试与 vet;面板 874 个测试、lint/build;Linux VM 安装器测试、清单服务端 dry-run、网络命名空间防火墙实测。A/B/C 三机 WireGuard、Velocity 和迁移验收仍待完成。
2026-10-01 19:48:37 +08:00

85 lines
2.3 KiB
Go

// Package placement resolves worlds and checks the protected node admission labels.
package placement
import (
"context"
"fmt"
"felis.lolicon.best/internal/apis/felis/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
LabelRole = "felis.node-restriction.kubernetes.io/role"
LabelIdentity = "felis.node-restriction.kubernetes.io/identity"
LabelApproved = "felis.node-restriction.kubernetes.io/approved"
RoleController = "controller"
RoleWorker = "worker"
)
type World struct{ Claim, Node string }
type Resolver func(context.Context, string) (World, error)
func Online(n *corev1.Node) bool {
for _, c := range n.Status.Conditions {
if c.Type == corev1.NodeReady {
return c.Status == corev1.ConditionTrue && n.DeletionTimestamp == nil
}
}
return false
}
// Admitted accepts only A's protected controller identity or an approved worker.
func Admitted(n *corev1.Node, controller string) bool {
if !Online(n) || n.Labels[LabelIdentity] != n.Name {
return false
}
if n.Name == controller {
return n.Labels[LabelRole] == RoleController
}
return n.Labels[LabelRole] == RoleWorker && n.Labels[LabelApproved] == "true"
}
func Worker(ctx context.Context, r client.Reader, name string) error {
var n corev1.Node
if err := r.Get(ctx, types.NamespacedName{Name: name}, &n); err != nil {
return err
}
if !Admitted(&n, "") || n.Spec.Unschedulable {
return fmt.Errorf("node %q is not an online, approved worker", name)
}
return nil
}
func Resolve(r client.Reader, namespace string, controller ...string) Resolver {
return func(ctx context.Context, name string) (World, error) {
var s v1alpha1.MinecraftServer
if err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, &s); err != nil {
return World{}, err
}
node := s.Spec.NodeName
if node == "" {
node = s.Status.NodeName
}
if node == "" && len(controller) > 0 {
node = controller[0]
}
if node != "" {
var n corev1.Node
if err := r.Get(ctx, types.NamespacedName{Name: node}, &n); err != nil {
return World{}, err
}
a := ""
if len(controller) > 0 {
a = controller[0]
}
if !Admitted(&n, a) {
return World{}, fmt.Errorf("node %q is offline", node)
}
}
return World{Claim: s.WorldPVC(), Node: node}, nil
}
}