fix(cli): pin the reaper to its storage node; drop the stale uid-1000 note
Two things in the same surface. --reaper-node is the supported multi-node answer: the rendered CronJob's pod gets a kubernetes.io/hostname selector, so it reads the hostPath on the node that actually holds the worlds instead of possibly scheduling where it is empty (naming a node without --worlds-host-path is fail-loud). And the render note still told operators to grant uid-1000 traverse / setfacl after #35 moved every world executor to root+DAC_OVERRIDE — it now states that fact instead of the obsolete ritual.
This commit is contained in:
5 files changed
+112
-26
No files matched your search
@@ -143,6 +143,15 @@ type Params struct {
|
||||
// The rendered CronJob is the correct K8s object; the actual tar depends on the
|
||||
// hosting node, which is not provable without a cluster.
|
||||
WorldsHostPath string
|
||||
// ReaperNode, when non-empty, pins the rendered reaper CronJob's pod to one node
|
||||
// via nodeSelector kubernetes.io/hostname — the multi-node answer to "the
|
||||
// hostPath root exists on every node but a world's directory lives on exactly
|
||||
// one". On a multi-node cluster the operator passes the node that holds the
|
||||
// world volumes (for the local-path starter: the node whose
|
||||
// /var/lib/rancher/k3s/storage carries them); leaving it empty keeps the
|
||||
// single-node behaviour (no selector). It is reaper-only and only meaningful
|
||||
// with WorldsHostPath set.
|
||||
ReaperNode string
|
||||
// ArchiveLocalPath is the path the backup PVC is mounted at inside the reaper
|
||||
// CronJob's pod, and MUST equal felis.toml's [archive] local_path. tarLocal writes
|
||||
// archive refs as absolute paths under [archive] local_path (internal/backup), and
|
||||
|
||||
@@ -38,11 +38,11 @@ import (
|
||||
// (<pv-name>_<ns>_<pvc-name>, read from the live PVC), so pointing
|
||||
// --worlds-host-path at /var/lib/rancher/k3s/storage works on a default install —
|
||||
// see the WorldsHostPath field doc. Whether the tar finds a world still depends on
|
||||
// the hosting node, and is not provable without a cluster. No nodeSelector is set:
|
||||
// the single-node starter pins
|
||||
// the worlds to one node implicitly; a multi-node deployment MUST add one (or the
|
||||
// CronJob could schedule on a node where the hostPath is empty) — a hazard left on
|
||||
// record here until multi-node retention is built.
|
||||
// the hosting node, and is not provable without a cluster. No nodeSelector is set
|
||||
// unless ReaperNode names one: the single-node starter pins the worlds to one node
|
||||
// implicitly, while a multi-node deployment passes --reaper-node (rendered as a
|
||||
// kubernetes.io/hostname selector) or the CronJob could schedule on a node where
|
||||
// the hostPath is empty.
|
||||
const (
|
||||
// configSecretName / serviceTokenSecretName are referenced BY NAME and NEVER
|
||||
// rendered into the bundle: felis.toml carries the database URL (a credential)
|
||||
@@ -605,14 +605,7 @@ func reaperCronJob(p Params) *batchv1.CronJob {
|
||||
ActiveDeadlineSeconds: int64Ptr(reaperActiveDeadlineSeconds),
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: labels},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: SAReaper,
|
||||
PriorityClassName: controlPlanePriorityName,
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
SecurityContext: reaperPodSecurityContext(),
|
||||
Containers: []corev1.Container{container},
|
||||
Volumes: volumes,
|
||||
},
|
||||
Spec: reaperPodSpec(p, container, volumes),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -620,6 +613,25 @@ func reaperCronJob(p Params) *batchv1.CronJob {
|
||||
}
|
||||
}
|
||||
|
||||
// reaperPodSpec is the reaper Job's pod template. It lives apart from the CronJob
|
||||
// literal only so the optional node pin is one visible branch: with ReaperNode
|
||||
// set the pod carries a kubernetes.io/hostname selector, keeping the reaper on
|
||||
// the node that actually holds the worlds hostPath on a multi-node cluster.
|
||||
func reaperPodSpec(p Params, container corev1.Container, volumes []corev1.Volume) corev1.PodSpec {
|
||||
spec := corev1.PodSpec{
|
||||
ServiceAccountName: SAReaper,
|
||||
PriorityClassName: controlPlanePriorityName,
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
SecurityContext: reaperPodSecurityContext(),
|
||||
Containers: []corev1.Container{container},
|
||||
Volumes: volumes,
|
||||
}
|
||||
if p.ReaperNode != "" {
|
||||
spec.NodeSelector = map[string]string{"kubernetes.io/hostname": p.ReaperNode}
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
// controlPlaneDeployment assembles a single-replica control-plane Deployment. The
|
||||
// Deployment, its selector, and the pod template all carry
|
||||
// controlPlanePodLabels(component) so the three agree (a selector mismatch would
|
||||
|
||||
@@ -648,6 +648,22 @@ func TestReaperCronJob_Gating(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReaperCronJob_NodePin proves the optional multi-node pin: no selector by
|
||||
// default (the single-node starter), and exactly the kubernetes.io/hostname
|
||||
// selector when ReaperNode names the node holding the worlds hostPath.
|
||||
func TestReaperCronJob_NodePin(t *testing.T) {
|
||||
ps, _ := cronPodSpec(t, reaperCronJob(reaperParams()))
|
||||
if ps.NodeSelector != nil {
|
||||
t.Errorf("NodeSelector = %v, want none without ReaperNode", ps.NodeSelector)
|
||||
}
|
||||
p := reaperParams()
|
||||
p.ReaperNode = "node-a"
|
||||
ps, _ = cronPodSpec(t, reaperCronJob(p))
|
||||
if got := ps.NodeSelector["kubernetes.io/hostname"]; got != "node-a" {
|
||||
t.Errorf("nodeSelector = %v, want kubernetes.io/hostname=node-a", ps.NodeSelector)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReaperCronJob_Shape pins the rendered CronJob: its scheduling guards, its
|
||||
// run-as identity (felis-reaper WITH an auto-mounted token, because it legitimately
|
||||
// calls the K8s API — unlike the weak Job/registry pods), the hardening, the
|
||||
|
||||
Reference in new issue
Block a user