Help us shape the future of Go! Take the 2026 Go Developer Survey

cluster

package
v1.37.1-stratum.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 60 Imported by: 0

Documentation

Overview

Package cluster assembles a SKE node: the declarative cluster config, the persisted per-node state, the component processes the supervisor runs, and the bring-up/join flows around them.

The shape is one API-versioned config file rather than flag soup, component configs generated from it, and the control plane as kubelet-managed static pods rendered by internal/staticpod, with the host-process roster in processes.go.

Index

Constants

View Source
const DefaultConfigPath = "/etc/stratum/config.yaml"

DefaultConfigPath is the declarative config file `stratum up` reads.

Variables

View Source
var (
	SaveState = nodestate.SaveState
	LoadState = nodestate.LoadState
	StatePath = nodestate.StatePath
)
View Source
var ImporterStacks func(StackContext) (map[string][]byte, error)

ImporterStacks, when set, returns an importer's stacks as "stack/file.yaml" to contents; writeBundles stages them beside the bundled add-ons. Unset, a start removes the files an earlier start staged for an importer.

Functions

func ImportImages

func ImportImages(ctx context.Context, dir string) error

ImportImages is importPayloadImages pointed at an explicit archive dir — the per-node upgrade (internal/upgrade) imports the upgrade binary's staged images from it before re-rendering the static pod manifests.

func ImportImagesWithWriter

func ImportImagesWithWriter(ctx context.Context, w io.Writer, dir string) error

ImportImagesWithWriter imports payload images and reports progress to w.

func LoadSnapshotConfig

func LoadSnapshotConfig() (etcdsnap.Config, error)

LoadSnapshotConfig is the effective snapshot config for the CLI and the MCP server, resolved the way the scheduled loop resolves it: from the cluster config the controller was started with (`stratum up --config`), with snapshot.conf winning when it exists. The resolved settings carry the directory too (Config.Dir): spec.snapshots.dir, else the default, so both front ends act in the directory the scheduled loop owns. An unreadable cluster config, or a recorded one that has since been deleted, falls back to the built-in defaults, as these callers did before the cluster config was consulted at all; that is warned about on stderr, since it can make a manual save prune to the default retention or `schedule` persist a local-only config.

func LoadSnapshotDir

func LoadSnapshotDir() string

LoadSnapshotDir is the directory the scheduled loop writes to, for the commands that read or prune what it wrote. It reads the same cluster config as LoadSnapshotConfig; an unreadable one yields the default dir, and LoadSnapshotConfig has already warned about it.

func NodeSELinuxOptions

func NodeSELinuxOptions() (selinux.Options, error)

NodeSELinuxOptions reads this node's SELinux settings from disk, for the upgrade preflight: whether its profile requires SELinux and whether the node opted out of SELinux labelling. Each is resolved the way the node's own next start resolves it: a control plane reads the config file its start recorded and the profile that config names; a worker keeps the profile its join recorded, and the opt-out it saved unless its config file sets spec.selinux now. An unreadable node state or config, or a profile this binary does not register, is an error: the caller cannot tell what the node runs with and warns instead of deciding.

func Processes

func Processes(st *NodeState, operator bool) []supervisor.Process

Processes renders the supervised host-process roster for this node. Since the control plane runs as kubelet-managed static pods (internal/staticpod), so the supervisor's roster is down to what has to be a host process: containerd, kubelet, and the operator on controllers. Ordering/readiness is expressed with WaitFor gates rather than After=/Requires=.

operator is the cluster's spec.operator switch, the only cluster setting the roster reads. It is a bool rather than the whole ClusterConfig because a nil spec.operator means on: a caller that passed an empty config would have run the operator on a cluster that turned it off. A worker never runs the operator, whatever the switch.

func RunController

func RunController(ctx context.Context, cfg *ClusterConfig, st *NodeState) error

func RunWorker

func RunWorker(ctx context.Context, st *NodeState) error

RunWorker supervises a worker node (containerd + kubelet).

func UpArgv

func UpArgv(exe string, worker bool, configPath string, profileName string) []string

UpArgv is the steady-state `stratum up` command line run from exe: what the reboot-persistence unit runs, and what a supervisor re-execs into after an upgrade. It never carries --join/--token/--certificate-key: those are one-shot join credentials, valid only for the join that consumed them. A worker's restart needs nothing but --worker, because PrepareWorker's restart path resumes from the persisted NodeState, its profile included. A control plane's carries its profile, so a restart keeps the one the node was brought up with.

The cis profile is spelled --cis, not --profile cis: an upgrade rollback restores the previous binary and re-execs it with this argv, and a binary that predates --profile rejects the flag it does not know, which would leave a rolled-back CIS control plane unable to start. Every binary reads --cis. Any other profile is spelled --profile <name>; no earlier binary could have run it.

func WorkerSELinux

func WorkerSELinux(path string) (optOut, set bool, err error)

WorkerSELinux reads spec.selinux from a worker's config file on a restart, which reads nothing else from it. set is false when the file is missing or does not set spec.selinux, and the worker keeps the setting it saved. Other fields are not checked here: a worker's join already refused a file that set them.

Types

type Cilium

type Cilium struct {
	MTU                   int      `json:"mtu,omitempty"`
	Devices               []string `json:"devices,omitempty"`
	BGPControlPlane       *bool    `json:"bgpControlPlane,omitempty"`
	RoutingMode           string   `json:"routingMode,omitempty"` // tunnel (default) or native
	IPv4NativeRoutingCIDR string   `json:"ipv4NativeRoutingCIDR,omitempty"`
	IPv6NativeRoutingCIDR string   `json:"ipv6NativeRoutingCIDR,omitempty"`
	AutoDirectNodeRoutes  *bool    `json:"autoDirectNodeRoutes,omitempty"`
}

Cilium is spec.network.cilium: the cluster's Cilium options. Every field is optional, and an absent block is the shipped configuration. It is cluster-wide, so every control plane's file must carry the same block.

func (*Cilium) CheckStack

func (c *Cilium) CheckStack(dualStack bool) error

CheckStack checks the parts of the block that depend on the cluster's network: native routing on a dual-stack cluster needs the IPv6 range too, and a single-stack one has no use for it.

func (*Cilium) Settings

func (c *Cilium) Settings() addons.CiliumSettings

Settings is the block as the render and the join check take it; a nil block is the shipped configuration.

type ClusterConfig

type ClusterConfig struct {
	APIVersion string `json:"apiVersion,omitempty"`
	Kind       string `json:"kind,omitempty"`
	Spec       Spec   `json:"spec"`
	// contains filtered or unexported fields
}

ClusterConfig is the user-facing declarative config (/etc/stratum/config.yaml). CRD-shaped even though it is consumed locally, so the same document can later live in-cluster.

func LoadConfig

func LoadConfig(path string) (*ClusterConfig, error)

LoadConfig reads the cluster config; a missing file yields pure defaults, so `stratum up` with no config at all still brings up a sane single node.

func (*ClusterConfig) CertRenewEnabled

func (c *ClusterConfig) CertRenewEnabled() bool

func (*ClusterConfig) DashboardEnabled

func (c *ClusterConfig) DashboardEnabled() bool

DashboardEnabled reports whether the cluster runs its dashboard: on unless spec.dashboard.enabled is false or the operator is off.

func (*ClusterConfig) DashboardSettings

func (c *ClusterConfig) DashboardSettings() addons.DashboardSettings

DashboardSettings is spec.dashboard as the render takes it.

func (*ClusterConfig) OperatorEnabled

func (c *ClusterConfig) OperatorEnabled() bool

OperatorEnabled/CertRenewEnabled default to on; a *bool distinguishes "unset" from "explicitly false".

func (*ClusterConfig) ProfileName

func (c *ClusterConfig) ProfileName() string

ProfileName is the node profile the config selects: spec.profile, or the legacy spec.cis: true as "cis"; empty for none.

func (*ClusterConfig) SELinuxOptOut

func (c *ClusterConfig) SELinuxOptOut() bool

SELinuxOptOut reports spec.selinux: false.

func (*ClusterConfig) SetProfile

func (c *ClusterConfig) SetProfile(name string)

SetProfile selects a profile from outside the file (the --profile flag) and keeps spec.profile and the legacy spec.cis in step.

func (*ClusterConfig) SnapshotBase

func (c *ClusterConfig) SnapshotBase() etcdsnap.Config

SnapshotBase is the snapshot config this cluster config asks for: what applies when no snapshot.conf exists. Credentials never live in config.yaml; they come from the environment (stratum.env) via etcdsnap.

func (*ClusterConfig) SnapshotDir

func (c *ClusterConfig) SnapshotDir() string

SnapshotDir is the local directory this cluster config writes scheduled snapshots to: spec.snapshots.dir, else defaultSnapshotDir. A relative dir is ignored, not resolved: it would name a different place for every process with a different working directory, and one starting with "-" would reach etcdutl as a flag. A dir with a ".." element is ignored too: the kernel resolves ".." after symlinks and filepath.Clean before them, so the callers would not agree on which directory it names. Other unclean forms ("/a//b", "/a/./b", "/a/") mean the same to both and are cleaned.

type Dashboard

type Dashboard struct {
	Enabled         *bool  `json:"enabled,omitempty"`
	NodePort        *int   `json:"nodePort,omitempty"` // default 30443; 0 serves on a ClusterIP only
	PublicURL       string `json:"publicURL,omitempty"`
	TLSSecretName   string `json:"tlsSecretName,omitempty"`
	EnableNodeShell *bool  `json:"enableNodeShell,omitempty"` // the privileged host shell (default on, off under cis)
	TokenSecrets    *bool  `json:"tokenSecrets,omitempty"`    // admin and viewer sign-in tokens as Secrets (default on)
}

Dashboard is spec.dashboard: the dashboard the cluster runs for itself. It is on by default whenever the operator is.

type Network

type Network struct {
	PodCIDR       string  `json:"podCIDR,omitempty"`     // default 10.245.0.0/16; "v4,v6" for dual-stack; set at first boot, a join must match
	ServiceCIDR   string  `json:"serviceCIDR,omitempty"` // default 10.96.0.0/12; set at first boot, a join must match
	ClusterDomain string  `json:"clusterDomain,omitempty"`
	NodeIP        string  `json:"nodeIP,omitempty"` // override autodetection
	Cilium        *Cilium `json:"cilium,omitempty"`
}

Network is spec.network. A control plane never reads its pod and service ranges or cluster domain from flags: the cluster's first controller boot takes podCIDR, serviceCIDR and clusterDomain from this block and records them, a joining control plane inherits the recorded values and refuses a different one set here, and every later start keeps the recorded values. A worker reads only nodeIP and takes the cluster's network from --pod-cidr, --service-cidr and --cluster-domain, which `stratum up` refuses without --worker.

type NodeState

type NodeState = nodestate.NodeState

Aliases: the state types live in internal/nodestate (a leaf package shared with internal/ha); cluster is their primary consumer.

func PrepareController

func PrepareController(cfg *ClusterConfig, nodeIPFlag string, releaseDataLock func()) (*NodeState, error)

PrepareController does everything a fresh first controller needs before the supervisor starts: dirs, prereqs, PKI, kubeconfigs, component configs, node state. Idempotent — a restarted controller re-runs it cheaply.

The common node steps run through the ONE shared assembly the HA join runs (assembleNode, internal/ha/join.go): the controller-specific steps hang off it at the points the two paths' pinned orders fix — the netconfig resolution as ResolveNet, the IP-move refusal and node-state completion as BeforeCerts.

nodeIPFlag is --node-ip from the command line ("" when not given); it outranks spec.network.nodeIP (host.ResolveNodeIP has the whole precedence). releaseDataLock is the release of the etcd data lock the caller holds across preparation (`stratum up` takes it before it calls this and releases it after); the node-state save writes under that held lock. A caller not holding the lock passes nil, and the save refuses rather than write unlocked.

func PrepareControllerContext

func PrepareControllerContext(ctx context.Context, cfg *ClusterConfig, nodeIPFlag string, releaseDataLock func()) (*NodeState, error)

PrepareControllerContext stops preparation between steps when its caller cancels: the entry check refuses a run that arrived already stopped, and the shared assembly checks the context before each step, so a step in flight finishes before the next mutation is considered. releaseDataLock is as PrepareController's: the release of the held etcd data lock; nil makes the final node-state save refuse.

func PrepareControllerWithLease

func PrepareControllerWithLease(ctx context.Context, cfg *ClusterConfig, nodeIPFlag string, lease *k8sutil.MaintenanceLease, releaseDataLock func()) (*NodeState, error)

PrepareControllerWithLease carries the CLI's CIS-key maintenance fence through host preparation so the eventual key write can confirm ownership again. releaseDataLock is as PrepareController's: the release of the held etcd data lock; nil makes the final node-state save refuse.

func PrepareWorker

func PrepareWorker(ctx context.Context, o WorkerOptions) (*NodeState, error)

PrepareWorker runs the worker join flow: discover + pin the CA, write the bootstrap kubeconfig, persist the node state. RunWorker, which follows, writes the kubelet and containerd configs. The kubelet does TLS bootstrap; the operator's CSR approver signs its serving cert. A worker that already has worker state and is given no Server takes the restart fast path instead, which reuses that state.

ctx is the run's interrupt context, created by the caller before its preflight: a signal received before the join began stops it here, before any host write, and the caller stops the run at its boundary once the join returns.

type S3

type S3 struct {
	Bucket   string `json:"bucket"`
	Prefix   string `json:"prefix,omitempty"`
	Region   string `json:"region,omitempty"`
	Endpoint string `json:"endpoint,omitempty"`
	// PathStyle selects path-style addressing, which MinIO, Ceph and most
	// non-AWS endpoints need.
	PathStyle bool `json:"pathStyle,omitzero"`
}

type Snapshot

type Snapshot struct {
	Disabled      bool   `json:"disabled,omitempty"`
	IntervalHours int    `json:"intervalHours,omitempty"` // default 12
	Retain        int    `json:"retain,omitempty"`        // default 5
	S3            *S3    `json:"s3,omitempty"`
	Dir           string `json:"dir,omitempty"`
}

type Spec

type Spec struct {
	Network   Network    `json:"network,omitempty"`
	VIP       VIP        `json:"vip,omitempty"`
	Storage   Storage    `json:"storage,omitempty"`
	Operator  *bool      `json:"operator,omitempty"`  // HelmChart + manifest-deployer controller (default on)
	Profile   string     `json:"profile,omitempty"`   // the node profile, by name; "" for none
	CIS       bool       `json:"cis,omitempty"`       // legacy spelling of profile: cis; LoadConfig folds it into Profile
	Snapshot  Snapshot   `json:"snapshots,omitempty"` //
	CertRenew *bool      `json:"certRenew,omitempty"` // rotate expiring leaves automatically (default on)
	SELinux   *bool      `json:"selinux,omitempty"`   // label Stratum's files for container-selinux (default on)
	Dashboard *Dashboard `json:"dashboard,omitempty"`
}

type StackContext

type StackContext struct {
	ClusterDomain string
	ServiceCIDR   string
}

StackContext is the cluster an importer's stacks are rendered for.

type Storage

type Storage struct {
	// Default StorageClass backend: "local-path" (default) or "none".
	Default string `json:"default,omitempty"`
}

type VIP

type VIP = nodestate.VIP

Aliases: the state types live in internal/nodestate (a leaf package shared with internal/ha); cluster is their primary consumer.

type WorkerConfig

type WorkerConfig struct {
	NodeIP        string // spec.network.nodeIP
	SELinuxOptOut bool   // spec.selinux: false
}

WorkerConfig is what a --worker reads from its config file.

func LoadWorkerConfig

func LoadWorkerConfig(path string) (WorkerConfig, error)

LoadWorkerConfig is the config a --worker honours: spec.network.nodeIP and spec.selinux, and nothing else. The file is parsed as strictly as a control plane parses it, and any other field that is set is an error naming it: a worker has no use for it, and a setting that appears to apply and does not is worse than a refusal. A missing file yields the zero WorkerConfig.

type WorkerOptions

type WorkerOptions struct {
	// Server is the control-plane or VIP URL to join (--server); empty on a
	// restart.
	Server string
	// Token is the bootstrap token the join authenticates with.
	Token string
	// CAHash is the "sha256:<hex>" pin of the cluster CA
	// (--discovery-token-ca-cert-hash).
	CAHash string
	// NodeIP is --node-ip; empty leaves spec.network.nodeIP, $NODE_IP and
	// detection to the join, and the saved address, else $NODE_IP and
	// detection, to a restart. A restart given one that differs moves the
	// worker to it and saves it, once it is an address the worker can
	// register: an IPv4 address on a single-stack worker, its v4,v6 pair on a
	// dual-stack one.
	NodeIP string
	// ConfigNodeIP is spec.network.nodeIP from the config file, which only a
	// join reads. It is kept apart from NodeIP, which outranks it, so the join
	// names the source its address came from.
	ConfigNodeIP string
	// ServiceCIDR, PodCIDR and ClusterDomain describe a cluster that does not
	// use the defaults. A restart reads only PodCIDR, to record the range a
	// legacy worker, or one joined without --pod-cidr, never saved, or to
	// replace the default an earlier release saved for such a join.
	ServiceCIDR   string
	PodCIDR       string
	ClusterDomain string
	// Profile selects the named node profile on a join.
	Profile string
	// SELinuxOptOut is spec.selinux: false from the config file, nil when a
	// restart's file does not set it; a restart saves a changed setting.
	SELinuxOptOut *bool
	// DualStack registers a v4,v6 node IP pair on a join (--dual-stack).
	DualStack bool
}

WorkerOptions are the inputs of a worker start, as `stratum up --worker` resolves them from its flags and config file. A restart passes no join credentials: it resumes from the saved node state.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL