Unverified Commit 659127b2 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(platform): 渲染 k3s 内的 felis-postgres 及其入站隔离

parent a20840e7
Loading
Loading
Loading
Loading
+25 −1
Changes for cmd/felis/manifests.go: 25 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -35,6 +35,11 @@ func (m *multiFlag) Set(v string) error {
// --velocity-cidr records the proxy host addresses allowed by the game NetworkPolicy.
// Kubernetes permits resident-node traffic regardless, but remote proxy deployments
// need an explicit CIDR, so the renderer refuses to guess.
//
// --only postgres renders just the control-plane database (platform.PostgresObjects),
// which the installer brings up before migrations, before it has anything else
// to render the full bundle with; it needs neither --felis-image nor
// --velocity-cidr.
func cmdManifests(args []string, stdout, stderr io.Writer) int {
	fs := flag.NewFlagSet("manifests", flag.ContinueOnError)
	fs.SetOutput(stderr)
@@ -46,6 +51,8 @@ func cmdManifests(args []string, stdout, stderr io.Writer) int {
	panelNodePort := fs.Int("panel-node-port", int(platform.DefaultPanelNodePort), "NodePort that exposes the built-in HTTPS panel/API origin")
	felisImage := fs.String("felis-image", "", "container image the felis-api/operator Deployments run, also passed through as FELIS_IMAGE (REQUIRED)")
	registryImage := fs.String("registry-image", "", "in-cluster registry image (default: registry 2.8.3, pinned by digest)")
	postgresImage := fs.String("postgres-image", "", "control-plane database image (default: PostgreSQL 18.6, pinned by digest)")
	only := fs.String("only", "", `render one part of the bundle instead of all of it; "postgres" is the control-plane database`)
	backupPVC := fs.String("backup-pvc", "felis-backups", "name of the world-archive PVC this bundle renders in the Minecraft namespace and advertises to the backup/restore executors via FELIS_BACKUP_PVC (default: felis-backups; pass an empty value to render none, leaving backup/restore answering 503)")
	worldsHostPath := fs.String("worlds-host-path", "", "node directory the reaper reads worlds from: each world PVC resolves as <path>/<pvc>, or as the stock local-path directory <path>/<pv-name>_<ns>_<pvc-name> (k3s storage root: /var/lib/rancher/k3s/storage); enables the reaper CronJob (requires --archive-local-path and a non-empty --backup-pvc)")
	archiveLocalPath := fs.String("archive-local-path", "", "path the backup PVC is mounted at in the reaper CronJob; MUST equal felis.toml [archive] local_path. With the backup PVC alone it renders the retention-only CronJob, which deletes backups past their expiry and never touches a world")
@@ -64,6 +71,18 @@ func cmdManifests(args []string, stdout, stderr io.Writer) int {
	if err := fs.Parse(args); err != nil {
		return 2
	}
	switch *only {
	case "":
	case "postgres":
		return renderManifests(stdout, stderr, platform.PostgresObjects(platform.Params{
			ControlNamespace:   *controlNS,
			MinecraftNamespace: *minecraftNS,
			PostgresImage:      *postgresImage,
		}))
	default:
		fmt.Fprintf(stderr, "felis manifests: --only %q: the one part that renders alone is \"postgres\"\n", *only)
		return 2
	}

	// Keep proxy placement explicit. This matters for remote proxies and documents
	// the expected source even when Velocity runs on the resident node.
@@ -165,6 +184,7 @@ func cmdManifests(args []string, stdout, stderr io.Writer) int {
		PanelNodePort:      int32(*panelNodePort),
		FelisImage:         *felisImage,
		RegistryImage:      *registryImage,
		PostgresImage:      *postgresImage,
		BackupPVC:          *backupPVC,
		WorldsHostPath:     *worldsHostPath,
		ReaperNode:         *reaperNode,
@@ -183,7 +203,11 @@ func cmdManifests(args []string, stdout, stderr io.Writer) int {
		fmt.Fprintf(stderr, "felis manifests: %v\n", err)
		return 2
	}
	out, err := platform.RenderYAML(params)
	return renderManifests(stdout, stderr, platform.Objects(params))
}

func renderManifests(stdout, stderr io.Writer, objs []platform.Object) int {
	out, err := platform.RenderObjects(objs)
	if err != nil {
		fmt.Fprintf(stderr, "felis manifests: render: %v\n", err)
		return 1
+34 −0
Changes for cmd/felis/manifests_test.go: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -278,3 +278,37 @@ func TestManifestsStorageSizes(t *testing.T) {
		t.Errorf("--registry-storage lots: exit %d, stderr %q; want a refusal naming the flag", code, errBuf.String())
	}
}

// TestManifestsOnlyPostgres: the installer renders the database before it has
// an image or a proxy address for the rest of the bundle, so --only postgres
// must render without them, and render the database and nothing else (a stray
// Deployment in that apply would start without its identities).
func TestManifestsOnlyPostgres(t *testing.T) {
	var out, errBuf bytes.Buffer
	code := run([]string{"manifests", "--only", "postgres", "--control-namespace", "ctl", "--postgres-image", "example/pg:18@sha256:abc"}, &out, &errBuf)
	if code != 0 {
		t.Fatalf("exit code = %d, want 0; stderr=%q", code, errBuf.String())
	}
	var kinds []string
	for _, doc := range strings.Split(out.String(), "\n---\n") {
		for _, line := range strings.Split(doc, "\n") {
			if strings.HasPrefix(line, "kind: ") {
				kinds = append(kinds, strings.TrimPrefix(line, "kind: "))
			}
		}
	}
	if got, want := strings.Join(kinds, ","), "Namespace,ConfigMap,NetworkPolicy,Deployment,Service"; got != want {
		t.Errorf("rendered kinds %s, want %s", got, want)
	}
	for _, want := range []string{"name: felis-postgres", "namespace: ctl", "image: example/pg:18@sha256:abc"} {
		if !strings.Contains(out.String(), want) {
			t.Errorf("rendered database missing %q", want)
		}
	}

	out.Reset()
	errBuf.Reset()
	if code := run([]string{"manifests", "--only", "registry"}, &out, &errBuf); code != 2 || out.Len() != 0 {
		t.Errorf("--only registry: exit %d with %d bytes of YAML, want 2 and none", code, out.Len())
	}
}
+9 −2
Changes for internal/backupjob/jobspec.go: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -82,14 +82,21 @@ type JobParams struct {
// backup and a restore of the same server never collide.
func BackupJobName(server string) string { return "backup-" + server }

func backupLabels(p JobParams) map[string]string {
// PodSelector matches every world-backup Job pod, whichever server it backs up:
// the peer the database's ingress fence admits (internal/platform/postgres.go).
func PodSelector() map[string]string {
	return map[string]string{
		LabelManagedBy: managedByValue,
		LabelComponent: componentValue,
		LabelServer:    p.Server,
	}
}

func backupLabels(p JobParams) map[string]string {
	labels := PodSelector()
	labels[LabelServer] = p.Server
	return labels
}

// BackupJob renders the on-demand world-backup Job (spec §18/§19 WorldArchiver,
// run on demand rather than on the reaper's daily schedule). Its isolation mirrors
// the restore Job (weak SA, non-root, read-only root fs, drop ALL, one-shot with a
+11 −1
Changes for internal/platform/bundle.go: 11 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -111,6 +111,11 @@ func Objects(p Params) []Object {
	}
	objs = append(objs, RegistryIngressPolicy(p))

	// The database's pg_hba.conf and ingress fence. deploy/bootstrap.sh applies
	// the database alone first (PostgresObjects); rendering it here as well keeps
	// the full apply from drifting it.
	objs = append(objs, postgresHBAConfig(p), PostgresIngressPolicy(p))

	// The running control-plane the fence protects: felis-api/operator Deployments
	// (which bind the SAs to workloads and stamp the RCON-peer labels) and the
	// in-cluster registry (Deployment + Service + PVC) the build egress policy
@@ -124,8 +129,13 @@ func Objects(p Params) []Object {
// `---`-separated form kubectl apply consumes). It is the verifiable source of
// truth a Helm chart would otherwise only re-encode.
func RenderYAML(p Params) ([]byte, error) {
	return RenderObjects(Objects(p))
}

// RenderObjects marshals objs into one multi-document YAML stream, in order.
func RenderObjects(objs []Object) ([]byte, error) {
	var buf bytes.Buffer
	for i, obj := range Objects(p) {
	for i, obj := range objs {
		if i > 0 {
			buf.WriteString("---\n")
		}
+20 −0
Changes for internal/platform/identities.go: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -39,6 +39,11 @@ const (
	// a control-plane identity, so the RCON NetworkPolicy peer (which requires
	// part-of=felis-control-plane) can never select it.
	ComponentRegistry = "registry"

	// ComponentPostgres labels the control-plane database. Like the registry it
	// is a supporting workload and NOT part-of=felis-control-plane, so no
	// control-plane peer selector (RCON, the internal API) can select it.
	ComponentPostgres = "postgres"
)

// Service-account names. The control-plane SAs (api/operator/reaper) are bound to
@@ -70,6 +75,15 @@ const (
	// for humans. deploy/bootstrap.sh's REGISTRY_IMAGE caches and GC-pins this exact
	// ref and must name the same one (TestBootstrapPinsTheRegistryImage).
	defaultRegistryImage = "docker.io/library/registry:2.8.3@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373"

	// defaultPostgresImage is the official PostgreSQL image the control-plane
	// database runs (postgres.go). Pinned by digest for the same reason as the
	// registry, and more: a re-tag that moved the major would start an empty
	// cluster in a fresh <major>/docker directory beside the real one.
	// deploy/bootstrap.sh's POSTGRES_IMAGE caches and GC-pins this exact ref
	// (TestBootstrapPinsThePostgresImage) and refuses to start it over data
	// another major wrote.
	defaultPostgresImage = "docker.io/library/postgres:18.6-trixie@sha256:5a5a84b19854a9ffaa54082c166ff4ec27473a361e496e5ea167f298f2da9722"
)

// Params parameterises the install bundle. Namespaces and the registry location
@@ -126,6 +140,9 @@ type Params struct {
	FelisImage string
	// RegistryImage is the in-cluster registry image. Defaults to registry 2.8.3, by digest.
	RegistryImage string
	// PostgresImage is the control-plane database image. Defaults to PostgreSQL
	// 18.6, by digest.
	PostgresImage string
	// BackupPVC is the name of the world-archive PersistentVolumeClaim. The bundle
	// RENDERS this PVC (backupPVC in workloads.go, Minecraft namespace — where every
	// pod that mounts it runs) and felis-api advertises the name to its backup/restore
@@ -236,6 +253,9 @@ func (p Params) withDefaults() Params {
	if p.RegistryImage == "" {
		p.RegistryImage = defaultRegistryImage
	}
	if p.PostgresImage == "" {
		p.PostgresImage = defaultPostgresImage
	}
	if p.RegistryStorage == "" {
		p.RegistryStorage = registryStorageSize
	}
Loading