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

operator

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: 87 Imported by: 0

Documentation

Overview

corednsspread.go — move a CoreDNS replica off a node that holds all of them. The anti-affinity is preferred, so it is only consulted at scheduling time and never rebalances. Eviction, not delete, so a PodDisruptionBudget is honoured.

Kubelet serving-cert CSR approver.

A kubelet that runs serverTLSBootstrap requests a `kubernetes.io/kubelet-serving` CSR — every kubelet under `stratum server --cis`, and also every token-joined worker, which has no local PKI to pin and so bootstraps its serving cert this way. Kubernetes deliberately does NOT auto-approve that CSR (unlike the kubelet CLIENT CSR), because serving-cert SANs (node IPs/DNS) are spoofable and must be validated against the node's real addresses. An unapproved kubelet has no serving cert at all, so the apiserver cannot complete a TLS handshake with it → cluster-wide kubectl logs/exec/top + metrics outage. This reconciler is what makes serverTLSBootstrap safe: it approves a serving CSR ONLY when the requester is the node itself and every SAN it asks for is an address that node legitimately owns.

departednodes.go — remove the Node of a worker that `stratum uninstall` took off the cluster. The worker's own credential cannot delete its Node (the Node authorizer does not let system:node:<name> delete nodes), so it marks the Node as it leaves and this deletes it once it is gone. Only the node itself can write the mark: the stratum-node-uninstall-mark admission policy (internal/addons) refuses it from anyone else.

grants.go — the operator's pass over the stratum-certs grant entries. The Secret carries one ciphertext entry per live control-plane join grant, keyed by bootstrap token ID, beside the legacy certs entry. Owner references, not data entries, are what garbage collection acts on, so an expired or revoked grant's entry stays until something drops it. Every mint drops them inside its own update (ha.UploadCerts); this pass drops them as well, so they go even when no one mints. Which entries are grants, and when a grant is gone, is internal/ha's rule (ha.PruneExpiredGrants), the package that writes the entries. The pass rides the operator's reconcile loop as part of the cleanup step Run binds with cleanupStepFor, beside departed-worker removal.

inventory.go — the prune inventory in cluster state, and the desired bundle generation recorded beside it.

The prune record is a per-node disk file: <manifests dir>/.applied.json, written by whichever operator instance ran the manifest pass. On a multi-controller cluster that record is node-local, so a leader change read whatever the node holding the record had last applied and pruned against a fraction of what the cluster once applied — and an object only another node's record remembered was orphaned forever. The inventory moves the record into cluster state: a ConfigMap in kube-system, stratum-addon-inventory, holds one entry per operator instance under its node's name, each entry the map from stack to object references that instance's manifests directory applied.

Migration: entries are published by union, never rewritten wholesale. Run drives syncInventory on every operator instance — leader or not, on its own background interval beside the CSR approvers — and syncInventory unions the instance's local .applied.json into its node's entry with resourceVersion conflict retry: a conflict re-reads the ConfigMap, re-unions, and retries, so a concurrent writer's entry survives instead of being clobbered and a lost race never fails the pass. The migration is complete when every control-plane node in cluster state has contributed an entry, or when an explicit recovery entry closes it by hand — the escape hatch for a decommissioned node whose contribution can no longer be expected. The recovery entry is a hand edit of the ConfigMap's inventory document (set "recovery": true beside the nodes; there is deliberately no CLI for it), so closing the migration without a missing node is a human decision, never something the operator does on its own. Until then the manifest pass suspends pruning: pruning before the last contribution could delete the objects only that node's record remembers. Once the migration closes, the union of the cluster inventory — not any local file — is the record pruning is driven by, and syncInventory retires the local file, so a stale record can never re-introduce an object the cluster inventory already pruned.

Desired generation: the same ConfigMap also holds the desired bundle generation the staging path (writeBundles in internal/cluster) records whenever it renders a changed bundle set — {generation, digest, provenance}, written through the apiserver, so RBAC is the integrity boundary and the record is separate from the operator binary version. The deployer reads it to tell what it is about to apply apart from what was staged: a staged tree whose digest does not match the record is refused for the pass, and an explicitly recorded empty set — the desired-empty generation — drives pruning of the last stack's objects (see manifests.go).

leaderelect.go — single-active-instance leader election via a Lease.

runLeaderElected campaigns for a coordination.k8s.io Lease (kube-system/stratum-operator) and only runs the reconcile loop while this instance holds it. This lets multiple control-plane hosts run stratum-operator for HA while guaranteeing exactly one actively reconciles (the HelmChart and manifests controllers make mutating cluster changes — two concurrent writers would fight).

On losing leadership we stop the reconcile loop and return an error. The standalone CLI exits nonzero so its supervisor can restart it; importers retain control of their own process and cleanup.

manifests.go — auto-deploy plain Kubernetes YAML from a host directory.

reconcileManifests server-side-applies every *.yaml/*.yml file under a directory (default /etc/stratum/manifests) so operators can drop manifests on the control-plane node and have them applied without a kubectl round-trip — the manifest equivalent of the HelmChart controller. This mirrors the SSA applier in stratum/internal/addons/apply.go: a discovery-backed RESTMapper and a per-object ApplyPatch with FieldManager "stratum-operator", Force.

Stacks and pruning: a first-level subdirectory of the manifests dir is a STACK. Every object applied from a stack is labeled stratum.ciq.com/stack=<name> and recorded as that stack's object references; objects the record remembers but no file of a fully successful pass declares are DELETED on that pass. The keep-set is the whole pass's union, matched by canonical identity (the RESTMapper's GroupResource, the effective namespace and the name), so an object a release moved between stacks, re-versioned or made namespace-explicit survives the pass that applies it under its new shape. Loose files directly in the manifests dir keep the old apply-only contract — their objects join the keep-set but are never recorded, so removing a loose file deletes nothing — the operator drop-a-file-and-go path stays safe.

The record pruning is driven by is the prune inventory in cluster state (see inventory.go): a stratum-addon-inventory ConfigMap in kube-system with one entry per operator instance, whose union every instance of the cluster published from its own manifests directory. Until the migration to that inventory has closed — every control-plane node contributed an entry, or an explicit recovery entry closed it — pruning suspends, and a pass with no inventory ConfigMap at all still prunes against the per-node <dir>/.applied.json earlier releases wrote.

To avoid hot-looping the apiserver, a file is re-applied only when its bytes change OR when the resync window (5m) has elapsed since its last apply.

Leader-only note: this applies the LEADER's local directory. With leader election on and multiple control planes, put identical files on every CP (or just use kubectl) — a follower never applies its copy. The bundled stacks are identical by construction: anything cluster-specific in them is a sentinel filled in here, from cluster state every leader sees the same — Cilium's apiserver endpoint, and the four cluster inputs the staging records in the inventory's cluster-inputs document (the DNS address, the cluster domain, dual-stack and the storage default, resolved through manifestSentinels). A sentinel the cluster state cannot resolve skips its file rather than guess: the file keeps no apply state and retries next pass. The prune inventory is the one piece of pass state that outlives a leader change: the entries live in the cluster, not in the leading node's directory.

Kubelet client-cert CSR approver for bootstrap-token joins: a join token may claim a new node name, never one already registered or claimed by a certificate.

Package operator is the in-binary SKE operator: the HelmChart / HelmChartConfig controller, the manifest deployer — leader-elected so a multi-controller cluster runs one active reconciler — and the kubelet CSR approvers, which run on every controller.

It runs as a SUPERVISED HOST PROCESS (`stratum operator`, a child of `stratum up`), not as an in-cluster Deployment: no operator image to pull keeps the airgap story one-artifact.

Rolling node upgrades are NOT here: `stratum upgrade --cluster` (internal/upgrade) is the one implementation, driven from a control-plane node rather than from a CR.

Index

Constants

View Source
const CoreDNSStack = "coredns"

CoreDNSStack is the stack the bundled CoreDNS deploys under: the name of the shipped coredns bundle's first-level directory, which stackOf turns into the stratum.ciq.com/stack label stamped on every object the deployer applies from it. The CoreDNS spreader gates on the same constant, so the deployer that stamps the name and the spreader that consumes it cannot drift apart.

Variables

View Source
var ErrStagingRefused = errors.New("staged generation activation refused")

ErrStagingRefused distinguishes a terminal activation decision from an API failure whose commit outcome may be unknown to the caller.

Functions

func DigestBundles

func DigestBundles(files map[string][]byte) string

DigestBundles digests a bundle set — a map from a bundle's path under the manifests directory to its rendered bytes — the one algorithm the staging path records and the deployer checks the staged tree against, so the digest a `stratum up` writes and the digest a pass computes can never be two. The digest is a pure function of the set: each bundle's path is hashed in sorted order with its byte length, so a map's walk order cannot move the digest, and both a changed byte and the same bytes under a different path change it. The empty set — the desired-empty generation — digests to its own stable value, so an explicitly staged empty set is recordable and matchable like any other.

func RecordClusterInputs

func RecordClusterInputs(ctx context.Context, cs kubernetes.Interface, in ClusterInputs) error

RecordClusterInputs records the cluster-inputs document a staging produced under its own data key in the inventory ConfigMap, beside the prune inventory and the desired generation: the values every control plane's deployer resolves the apply-time sentinels from. The last recording staging wins — the record is the cluster's intent, not one node's — and the write re-reads on a resourceVersion conflict like writeInventory, so a concurrent writer's entry and records survive. A missing ConfigMap is created.

func RecordDesiredGeneration

func RecordDesiredGeneration(ctx context.Context, cs kubernetes.Interface, digest, provenance string, stacks []string) (int, error)

RecordDesiredGeneration records the desired bundle generation the staging path produced: the digest of the rendered bundle set, the provenance of the staging, and the names of the stack directories the digest covers — the staging's own namespace, which the deployer's gate digests to the exclusion of hand-dropped stack directories (see desiredGeneration.Stacks). The generation opens at 1 and moves only when the digest changes — recording an unchanged digest returns the recorded generation and leaves its provenance, the staging that produced it, exactly as it was — and it has no monotonic rule: a digest an earlier generation held is a changed digest like any other, so an authorized rollback records the next generation. The write goes through writeInventory, so a resourceVersion conflict re-reads the ConfigMap, re-applies the record onto the fresh state, and retries: a concurrent publisher's entry survives beside the new record and a lost race never fails the staging. Returns the generation the record now holds.

func Run

func Run(ctx context.Context, o Options) error

Run reconciles until ctx is cancelled.

Types

type ClusterInputs

type ClusterInputs struct {
	// DNSAddress is the cluster DNS Service IP — the tenth address of the
	// service CIDR, netcfg.DNSIPFor — where the kube-dns Service lands.
	DNSAddress string `json:"dnsAddress"`
	// ClusterDomain is the cluster domain the Corefile serves.
	ClusterDomain string `json:"clusterDomain"`
	// DualStack says whether the cluster runs IPv4 and IPv6 pods.
	DualStack bool `json:"dualStack"`
	// StorageDefault names the cluster's default storage backend —
	// addons.StorageLocalPath when local-path holds the default claim,
	// anything else (StorageNone, an unset config) when it does not.
	StorageDefault string `json:"storageDefault"`
}

ClusterInputs is the cluster-inputs document the staging path records in the inventory ConfigMap: the four remaining per-control-plane inputs the bundled bundles carry as named sentinels instead of the staging node's local values. Every control plane resolves its sentinels from this one record, so the applied bytes do not depend on which node staged the bundles or which of them leads.

type Options

type Options struct {
	Kubeconfig   string
	ManifestsDir string
	HelmCache    string
	LeaderElect  bool
	Interval     time.Duration
	// PrivilegedNamespaces are the namespaces whose HelmCharts may install into a
	// DIFFERENT targetNamespace. A HelmChart in any other namespace is confined to
	// its own namespace, so a namespaced tenant CR cannot use the operator's
	// cluster-admin install creds to reach across namespaces.
	PrivilegedNamespaces []string
	// AllowInsecureHelmRepos permits a HelmChart's spec.insecureSkipTLSVerify to be
	// honored. Default false: a tenant CR cannot disable repo TLS verification on
	// its own.
	AllowInsecureHelmRepos bool
	// NodeIP is this control-plane node's address, used as the admission webhook's
	// EndpointSlice endpoint so the apiserver can reach the (hostNetwork) webhook
	// server. Empty disables the webhook on this instance — the reconcile
	// backstop still refuses unstamped non-privileged installs, so this fails
	// closed rather than silently dropping requester scoping.
	NodeIP string
	// APIServerVIP is the cluster VIP, when there is one. With it, the deployer
	// points Cilium at the VIP; without it, at every control plane.
	APIServerVIP string
	// ClusterDomain is the cluster's DNS domain, for the dashboard's and the
	// admission webhook's Service names.
	ClusterDomain string
}

Options for Run, mirroring the `stratum operator` flags.

func ParseFlags

func ParseFlags(args []string) (Options, error)

ParseFlags builds Options from `stratum operator` args.

type StagedGeneration

type StagedGeneration struct {
	// PublicationID is fresh for each changed local staging request and stable
	// across retries of that request. Content fingerprints cannot identify epochs.
	PublicationID string        `json:"publicationID"`
	Digest        string        `json:"digest"`
	Provenance    string        `json:"provenance"`
	Stacks        []string      `json:"stacks"`
	Inputs        ClusterInputs `json:"inputs"`
	// Unacknowledged names earlier locally persisted publications whose API
	// commit may have succeeded before the local success stamp was written.
	// Entries are publication IDs, never content fingerprints or known refusals.
	Unacknowledged []string `json:"unacknowledged,omitempty"`
}

StagedGeneration carries one local render and its cluster-wide inputs.

func (StagedGeneration) Fingerprint

func (s StagedGeneration) Fingerprint() string

Fingerprint includes the inputs because changing a sentinel's value does not change the staged bundle bytes. Provenance identifies the producer and takes no part in determining whether its intent changed.

type StagingPublication

type StagingPublication struct {
	Generation int
	ID         string
}

StagingPublication acknowledges the exact cluster publication a node joined.

func RecordStagedGeneration

func RecordStagedGeneration(ctx context.Context, cs kubernetes.Interface, staged StagedGeneration, previous string) (StagingPublication, error)

RecordStagedGeneration publishes bundles and inputs in one inventory update. previous is the node's last acknowledged cluster publication ID: an unchanged service restart cannot replace another node's newer intent. A changed local render can activate an upgrade or rollback; a first-time participant joins existing intent without replacing it.

Jump to

Keyboard shortcuts

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