Commit Graph
5104 Commits
Author SHA1 Message Date
Timofei Larkin af2b40be9f docs(kubernetes): re-home talos-reconcile comments after split
Three template comments in the control-plane chart still described the
talos-reconcile Job as living here, but the worker split moved it to
the kubernetes-nodes chart (one run per pool release):

- talos-pki.yaml pointed at "the routing note in
  talos-reconcile-job.yaml", a file this chart no longer contains.
- cluster.yaml said "the talos-reconcile main-phase Job re-patches
  certSANs" as if this chart still runs it; it now also states the
  consequence the kubernetes-nodes side already documents -- the
  ClusterIP SAN arrives only through a pool and a pool-less cluster
  keeps DNS SANs only, a best-effort extra that nothing depends on.
- cilium.yaml credited the /etc/hosts seeding to the same Job without
  naming its new home; it also now notes why the entry is always
  present wherever Cilium runs (workers only exist through pools).

Comments only ({{/* */}} blocks, stripped at render time); no manifest
output changes, pinned by the chart's unittest suite staying green.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 02:40:10 +03:00
Timofei Larkin 730d006ce8 fix(platform): skip pinning names no object can have
The pin path composed "kubernetes-<cluster>-<group>" and read it back,
but a group key that cannot form a valid object name (a slash, a space,
an underscore...) provably never had objects applied under it -- the
old chart's apply would have been rejected by the apiserver. Worse,
kubectl rejects a name containing '/' client-side, deterministically,
so pin_keep's fail-closed read classified it as a real failure and the
migration exited 1 on every retry: a permanent fleet-wide pre-upgrade
deadlock over a pool that cannot exist.

Lean on the invariant instead of the read: pin_pool_objects statically
checks the composed name against the DNS-1123 subdomain shape and skips
with a note when it cannot name an object -- nothing to pin, no kubectl
call to misread. Keys that are label-invalid but still valid object
names (e.g. dotted) may have live objects and keep today's pin
behavior; the pin-path transient-error test now uses a dotted key so it
still exercises a genuine read.

The fake kubectl reproduces real kubectl's client-side slash rejection,
and a new bats case pins the skip: a "pool/a" key must warn, pin
nothing, apply nothing, and still reach the version stamp. Red against
the previous script, which deadlocked at the first pin read.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 02:40:10 +03:00
Timofei Larkin a54d5ca4a5 fix(kubernetes-nodes): unpin absence via --ignore-not-found
Same two holes as the migration's read classification, in the unpin
hook. Capturing stderr into $pol (2>&1) contaminated the success path:
kubectl warns on successful reads, so an unannotated object read as
"pinned" and drew a spurious annotate call -- one per named object.
And the *"not found"* pattern classified a transient conversion-webhook
failure as absence, letting the Job report success while the
keep-annotated objects leaked past the uninstall.

--ignore-not-found makes the whole classification unnecessary here:
absence folds into the same empty-output no-op branch as "present but
unannotated", which is exactly the semantics unpin() wants, and any
non-zero exit is a real failure that set -e turns into a failed Job.
The case/esac disappears; stderr stays on the Job log where it is
visible instead of parsed. This also avoids tempfile juggling under the
container's readOnlyRootFilesystem.

The bats suite gains a previously missing failure-path case and
regression tests for both holes, red against the previous commit: a
failed KMT listing must fail the Job (FAKE_LIST_FAIL), a read failure
whose message contains "not found" must fail the Job, and a
success-path stderr warning must not fake a pinned state
(FAKE_WARN_STDERR). Absence is now modeled the way real kubectl
behaves under --ignore-not-found: success with empty output.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 02:07:43 +03:00
Timofei Larkin bdadb3ff04 fix(platform): detect absence via --ignore-not-found
The stderr-based classification this branch introduced had two holes of
its own. Capturing stderr into the data variable (2>&1) contaminates
the SUCCESS path: kubectl prints warnings on successful reads (API
deprecation headers -- deterministic for a deprecated GVK, and CAPI
v1beta1 is one -- plus client-side throttling notices), so the warning
glued onto the owner annotation would trip adopt_one's foreign-owner
refusal on every tenant and permanently deadlock the pre-upgrade hook.
And the *"not found"* pattern over-matches: a transient
conversion-webhook outage fails a read with '... service
"capi-webhook-service" not found', which the classifier would treat as
absence -- skipping the keep pin and re-opening the exact fail-open
prune hazard this branch exists to close.

Detect absence with --ignore-not-found instead: a missing object is
SUCCESS with empty output, so a non-zero exit can only be a real
failure and aborts before the version stamp, with no stderr parsing at
all. adopt_one reads -o json and extracts the owner annotation with jq
(already a dependency), keeping absence distinct from present-but-
unannotated; pin_keep reads -o name. list_kmts still needs stderr for
the one legitimate classification -- the two deterministic missing-CRD
messages -- so it captures stderr to a temp file, never into data, and
no longer accepts NotFound-shaped text for a list.

The fake kubectl honors --ignore-not-found the way real kubectl does,
gains FAKE_WARN_STDERR (success-path stderr warnings) and
FAKE_GET_FAIL_MSG (transient error with arbitrary text). Two new bats
tests, each red against the previous commit: a deprecation warning must
not break adoption or fake a foreign owner, and a failure whose message
contains "not found" must still abort without stamping.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 02:07:28 +03:00
Timofei Larkin 1ca947e2fb fix(kubernetes-nodes): fail unpin reads closed
The pre-delete unpin hook's existence check swallowed every kubectl get
failure (`... >/dev/null 2>&1 || return 0`), four lines below the
comment declaring "Failures are NOT swallowed". kubectl exits 1 for
NotFound and for a transient apiserver error alike, so a timeout during
uninstall read as "object absent": the unpin was skipped, the Job
succeeded, and Helm then skipped the still-keep-annotated
MachineDeployment -- leaking the adopted pool's worker VMs into the
tenant namespace, running and unmanaged, after the pool was deleted.
The KMT-listing path was already hardened for exactly this class; the
existence check was the remaining instance.

Classify by stderr: NotFound (native pool, or already gone) stays a
no-op; any other failure fails the Job, bounded by backoffLimit and
activeDeadlineSeconds, with logs retained. The existence check and the
annotation read fold into a single request.

The embedded script's behavior cannot be asserted from the manifest, so
a new bats suite renders the chart, extracts the Job's script, and
executes it against a fake kubectl: NotFound no-ops and succeeds, a
transient named-get error fails the Job (red against the previous
script, which printed "unpin done" and exited 0), keep-annotated
objects are unpinned with the 6-hex hash anchor respected, and an
unpinned existing object is left untouched.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 01:45:44 +03:00
Timofei Larkin f8258c8859 fix(platform): fail migration 54 reads closed
kubectl exits 1 for NotFound and for transient server failures (timeout,
429, etcd hiccup) alike, and migration 54 treated every non-zero read as
"object absent". A transient error while reading a worker object would
skip the keep pin and the ownership rewrite, let the migration complete
and stamp version 55 (so it never re-runs), and the parent's
control-plane-only upgrade would then prune the un-pinned live
MachineDeployment -- deleting every running worker VM in the pool. The
doubly-masked KubevirtMachineTemplate listings (2>/dev/null plus
`|| true`) had the same shape: a failed list adopted or pinned zero
KMTs, permanently stranding a preserved older-revision KMT still
referenced by an in-flight MachineSet, since the child chart only
re-emits KMTs already annotated with its own release name. The
`cur=$(... || true)` owner read additionally bypassed the foreign-owner
refusal guard.

Classify reads by stderr, the migration-47 precedent: NotFound means
genuinely absent and is skipped; anything else exits 1 so
run-migrations.sh leaves CURRENT_VERSION at 54 and retries on the next
upgrade. adopt_one and pin_keep fold the existence check into the
annotation read (one request instead of two); KMT listings go through a
capture-first list_kmts helper because `for x in $(...)` discards the
substitution's exit status even under set -e, leaving `|| true` to
cover only the empty-grep-match case, mirroring the unpin hook. The
child-HR existence check stays deliberately tolerant: its fallback is
an idempotent apply that itself fails closed. adopt_one now also stamps
meta.helm.sh/release-namespace so an object whose Helm annotations were
stripped out-of-band cannot fail the child's ownership validation.

The fake kubectl now reports absent objects the way the real apiserver
does (NotFound on stderr, exit 1) and gains FAKE_GET_FAIL to inject
transient errors; four new bats tests pin the classification: NotFound
still completes and stamps, while a transient error on the adopt path,
the KMT listing, or the pin path aborts before the version stamp. Each
red against the previous script.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-07-25 01:45:32 +03:00
IvanHunters c15b4bab6f test(kubernetes-nodes): assert unpin hook egress destination
The CiliumNetworkPolicy test verified only that the policy selects the
unpin pod, not spec.egress, so an egress regression that left the
uninstall hook unable to reach the apiserver would still pass. Assert the
kube-apiserver egress entity.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-24 15:33:44 +03:00
IvanHunters 040ad1d20d fix(kubernetes-nodes): fail unpin hook on KMT discovery error, do not leak
The pre-delete unpin hook discovered this pool's hash-suffixed
KubevirtMachineTemplates through `kubectl get ... 2>/dev/null | grep ...
|| true`, which turned an API or RBAC failure into an empty result: the
Job then reported success while the keep-annotated templates stayed
pinned and leaked on uninstall (Helm skips keep-annotated resources).
That contradicts the unpin() contract right above it, where annotate
failures are deliberately propagated.

Split the list out of the pipe so its failure fails the Job (set -e,
bounded by backoffLimit/activeDeadlineSeconds, logs retained), while a
successful-but-empty listing still no-ops the loop.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-24 15:33:36 +03:00
IvanHunters 928df3c2d3 fix(platform): harden migration 54 against non-object and whitespace nodeGroups
The RFC-1123 key guard added in the previous round closed hostile map
keys, but migration 54 still trusted the shape of nodeGroups and of each
group value, and word-split keys on whitespace. Because it runs as the
platform pre-upgrade hook, either gap can deadlock the upgrade for every
tenant off one hand-mangled parent HelmRelease (kubectl edit bypasses the
aggregated API schema, the same vector the key guard addresses).

Shape: a non-object nodeGroups (array/string/number) crashes jq under
set -e (keys[] on an array yields indices, then .[$idx] fails); a
non-object group value aborts the value-mapping jq on `$grp | has(...)`.
Normalize both: skip the cluster for a non-object nodeGroups map, and
route a non-object group value through the same warn + pin + skip path as
an invalid name. {} / null still fall through to the implicit md0 default.

Whitespace: an unquoted `for group in $(... keys[])` word-splits (and
globs) a key like 'my pool' into fragments that each pass the RFC-1123
guard, fabricating garbage child releases. Iterate keys newline-safely so
the intact key reaches the guard. Fail-closed exit semantics are
preserved through the while pipeline.

Covered by three new bats fixtures in migration-54-adopt-path.bats, each
red against the current script and green with this change.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-24 15:33:26 +03:00
IvanHunters 206957610e test(platform): cover implicit-md0 migration path and sync jq comment
Both existing adopt-values cases feed an explicit md0, leaving the DEFAULT_MD0
materialisation (empty nodeGroups) uncovered. Add a case asserting a parent HR
with no nodeGroups yields the child HR kubernetes-nodes-<cluster>-md0 with
minReplicas 0 and the ingress-nginx role. Also correct the header comment: the
jq picks ["version","talos"] and narrows images to {kubectl} separately, not
["version","talos","images"].

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:37:04 +03:00
IvanHunters d90cefbf8d chore(kubernetes): drop orphaned cpuToMillicores doc comment
The PR removed the kubernetes.cpuToMillicores helper but left its documentation
comment dangling at the end of _resources.tpl. Delete it.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:37:04 +03:00
IvanHunters ccd93eacff docs(storage-immutability): record kubernetes-nodes storageClass/cluster as immutable
The doc still claimed the kubernetes-nodes per-pool storageClass is intentionally
not immutable and undefaulted. This branch annotated it immutable (self == oldSelf
XValidation, @immutable in values.yaml) and it defaults to replicated, and the
cluster field is immutable too. Rewrite the paragraph to describe both fields,
add kubernetes-nodes to the covered-apps list, and drop the now-stale
top-level-only caveat on the kubernetes entry.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:35:33 +03:00
IvanHunters c8ba404646 fix(platform): guard migration 54 against RFC-1123-invalid pool names
The 53-char overflow branch exists because exiting the migration deadlocks the
platform pre-upgrade hook for every tenant. The equivalent deadlock via name
validity was unguarded: the old nodeGroups map key was never schema-constrained
(map[string]NodeGroup, no propertyNames pattern), so a stored parent HR can
carry a key like 'My_Pool' whose own render already failed at apply time. The
migration then built child_name=kubernetes-nodes-<cluster>-My_Pool, kubectl
apply rejected the RFC-1123-invalid HelmRelease name, the script exited 1 under
set -e, and every subsequent platform upgrade re-ran and re-failed.

Validate child_name against the RFC-1123 label pattern alongside the length
check and route failures through the same warn + pin + skip path. Factor the
pin-and-skip logic shared by both guards into pin_pool_objects. Cover the new
branch in migration-54-adopt-path.bats (the fake kubectl apply now rejects an
invalid metadata.name, so the case fails without the guard).

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:34:28 +03:00
IvanHunters a1ee339b5a fix(kubernetes-nodes): reap the pre-delete unpin hook Job after it finishes
The unpin Job carried only helm.sh/hook-delete-policy: before-hook-creation,
and its comment claimed a successful run is torn down with the release. Helm
does not track hook resources as release members, so after helm uninstall the
succeeded Job and its pod remained in the tenant namespace indefinitely, one
per deleted pool name per namespace — before-hook-creation only fires on a
next pre-delete of a same-named release, which never comes for a deleted pool.

Add spec.ttlSecondsAfterFinished: 86400 so a finished Job is garbage-collected
(a failed run still keeps its logs for a day), and correct the comment.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:30:52 +03:00
IvanHunters 71a8747d02 fix(kubernetes-nodes): make parent-version skew guard directional
assertParentVersion failed the pool render whenever the pool minor differed
from the parent KamajiControlPlane minor in either direction. A worker minor
lagging the control plane is supported (n-3 upstream) and is the normal state
during a rolling upgrade, where the parent Kubernetes CR is bumped first and
each pool follows. The symmetric check flipped every pool HelmRelease of a
cluster to render-failure the moment the parent was bumped, blocking all pool
operations until each pool version was hand-edited.

Fail only when the pool minor is ahead of the parent minor (semverCompare on
normalized major.minor.0), matching the stated rationale, and cover all four
directions (ahead / lag / equal / no parent) with a helm-unittest suite driven
by a mocked KamajiControlPlane.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-22 18:30:13 +03:00
IvanHunters 8f24cfb56c fix(platform): point migration fixtures and docs at renumbered migration 54
The Phase-2 worker-pool adoption migration was renumbered 52 -> 54 during
the rebase onto main (main added migrations 52 linstor-scheduler and 53
seaweedfs). Rename the adoption bats tests and their testdata directory,
and update the kubernetes-nodes pre-delete hook comment and the
kubernetes README so they reference migration 54. The bats MIG path
pointed at migrations/52 (now the linstor migration), so without this the
adoption unit tests would exercise the wrong migration.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-21 23:01:00 +03:00
Ivan Okhotnikov 49ef26fb80 docs(kubernetes): document Phase 2 split breaking changes and certSAN scope
Address review on #3315:
- README breaking-changes: worker pools move to KubernetesNodes (nodeGroups
  removed, adopted by migration 52); the parent Kubernetes CR's WorkloadsReady
  is now control-plane-only; ingressNginx needs an explicit ingress pool; do
  not edit Kubernetes CRs mid platform-upgrade.
- Note that the apiserver ClusterIP certSAN is a best-effort extra — workers
  dial by DNS, so a pool-less control plane is fine without it; the merge-patch
  array-replace predates the split.
- Confirm the pool VM pod is intentionally attributed to the parent Kubernetes
  application (byte-parity + lineage/quota under the owning cluster).

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:57:22 +03:00
Ivan Okhotnikov 3c204ca251 fix(kubernetes-nodes): immutable cluster/storageClass, validate pool version vs parent
Address review on #3315:
- Mark cluster and storageClass immutable (self == oldSelf): both feed object
  names / the KubevirtMachineTemplate content hash, so editing them on a live
  pool would rename everything and delete the running worker VMs.
- Validate the pool's Kubernetes minor against the parent cluster's
  KamajiControlPlane at render (skipped offline / when the parent is absent),
  so a pool created after the split cannot join workers ahead of the apiserver.
- Note in the snapshot suite header that the instancetype-sized default is not
  pinned offline (lookup nil); the e2e exercises it.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:57:22 +03:00
Ivan Okhotnikov 386053ba8f fix(platform): harden migration 52 (name overflow, images, adoption tests)
Address review on #3315:
- Name-length overflow no longer exits 1 (which would deadlock the platform
  pre-upgrade hook for every tenant). Instead pin the pool's worker objects
  with resource-policy=keep, skip the child HR, warn loudly, and continue.
- Copy only images.kubectl into the child values, not the parent's whole
  images map (waitForKubeconfig/talosCsrSigner are undeclared in the schema).
- Note that the pre-upgrade window is not quiet (a parent HR edit mid-upgrade
  renders the old chart and self-heals once the new artifact lands).
- Cover the adoption path directly: annotate keep + release-name, idempotent
  skip, unexpected-owner refusal, and the 6-hex KMT anchor rejecting a sibling
  pool's template.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:57:22 +03:00
Ivan Okhotnikov c11e3d6682 test(platform): guard migration 52 copies version/talos/images into child HR
Drives migration 52 against a fake kubectl and asserts the adopted
child HelmRelease carries the tenant's non-default talos.version,
version and images (plus group fields and storageClass fallback). Fails
against the pick(o;...) filter-parameter bug that dropped those keys and
rolled live worker VMs; passes against the pick($o;...) fix.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:57:06 +03:00
Ivan Okhotnikov 9d9068163d fix(platform): copy version/talos/images into adopted pool HR values
The adoption jq used a filter-parameter helper `def pick(o; ks)`; inside
the `reduce` the `.` context is the accumulator, so `o | has($k)` tested
the accumulator, not the source object, and `pick(.; ["version","talos",
"images"])` deterministically returned {}. The child HelmRelease was
therefore created without the tenant's talos/version, so Helm rendered
the chart defaults; since talos.version feeds the KubevirtMachineTemplate
content-hash, the KMT name changed and the MachineDeployment rolled every
live worker VM on upgrade — the exact churn this migration must avoid.
Caught by e2e on dev3 (reproduced on 2/2 real clusters). Switch pick to
a $-parameter so it is evaluated once at the call site.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:57:06 +03:00
Ivan Okhotnikov 3a52793483 test(kubernetes-nodes): cover GPU, kubelet-reservation and MHC render branches
The chart split removed the parent pool-render suites and the cross-chart
parity script, leaving the GPU device block, kubelet-reservation math and
maxUnhealthy IntOrString branches (all still in nodegroup.yaml /
talos-reconcile-job.yaml) uncovered. Add GPU and kubelet snapshot cases
so drift renaming the content-hashed KubevirtMachineTemplate fails CI,
and port the behavioral + failure-path assertions (GPU nodeLabels,
kubelet reserved/effective invariants and quantity rejections, MHC
integer/percentage quoting) to the child chart.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov a14413ad6a test(kubernetes): delete KubernetesNodes pools in e2e cleanup paths
The chainsaw harness created a KubernetesNodes CR but never reclaimed it,
leaking the pool CR, its child HelmRelease, release Secret and
WorkloadMonitor into the shared tenant-test namespace and breaking
rerun-in-same-sandbox (the surviving child HR left the MachineDeployment
un-rendered on retry). Delete kubernetesnodeses in all three cleanup
paths, before the parent CR so the pre-delete unpin hook runs.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov e615ca9253 chore(kubernetes): drop remaining nodeGroups residue from docs and fixtures
Remove the orphaned kubernetes.nodeGroups doc comment, the stale kubelet
reservation / nodeGroups storageClass prose in the README, and the inert
nodeGroups blocks in the test value fixtures.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov 4619fb3d0c fix(kubernetes-nodes): make unpin failures visible and scope its RBAC
Check the resource-policy annotation before removing it so a native pool
is a clean no-op while a real annotate failure fails the Job (retries
bounded, logs retained) instead of silently re-introducing the keep
leak. Scope the MD/MHC/WorkloadMonitor Role rules with resourceNames.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov fe958cb9ec test(kubernetes): provision e2e workers via KubernetesNodes
The chainsaw harness created the Kubernetes CR with spec.nodeGroups,
which the control-plane-only chart now ignores, so no MachineDeployment
was created and the suites timed out. Create a KubernetesNodes CR named
<cluster>-md0 (roles: [ingress-nginx]) so the same MachineDeployment is
produced and the MD/ingress waits pass; this also gives the split its
first e2e coverage.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov 3d8617a02a fix(kubernetes): do not hard-fail when the ingress addon lacks a pool
The render-time fail flipped existing degraded clusters from Ready to
Failed on upgrade, blocking all subsequent control-plane changes.
Restore the prior non-breaking behavior (the DaemonSet schedules nothing
if no ingress-roled node exists) and document the pool requirement in
NOTES.txt instead of enforcing it at render time.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov a76d947517 test(kubernetes-nodes): drop parity script from make test
The cross-chart parity script was removed (the parent no longer renders
pools to diff against); the render snapshot under helm unittest is the
drift guard now and already runs in CI.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov 87b41b5fa8 chore(kubernetes): drop stale nodeGroups references from fixtures and docs
Remove dead nodeGroups blocks from the CI values fixtures and point the
storage-immutability doc at the kubernetes-nodes per-pool storageClass.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov 7070cb0fe7 feat(kubernetes-nodes): unpin adopted worker objects on delete
Migration 52 pins adopted worker objects with helm.sh/resource-policy=keep
for the transition window. Left in place it leaks the objects when an
adopted pool is deleted. Add a pre-delete hook that strips the annotation
from the release's own worker objects so Helm deletes them cleanly;
runs on delete only (never on upgrade, so it cannot race the parent
re-render) and is a no-op for natively-created pools.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov 82074cb9db fix(kubernetes): regenerate deepcopy after nodeGroups removal
types.go dropped NodeGroups/NodeGroup/GPU/Kubelet/NodeHealthCheck but
the generated deepcopy still referenced them, so the api module failed
to build. Regenerated with controller-gen.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov a8286a3ac0 fix(platform): harden worker-pool adoption migration
Anchor the KubevirtMachineTemplate sweep to the 6-hex content-hash
suffix so a pool name that is a prefix of a sibling (md0 vs md0-large)
no longer mis-adopts the sibling's KMT and deadlocks the migration.
Adopt the worker objects before creating the child HelmRelease so
helm-controller cannot start the child install into un-adopted objects
and fail Helm ownership validation. Emit the same install/upgrade
strategy (RetryOnFailure) and maxHistory cozystack-api sets, add a
pre-flight release-name length check, and document the rollback path.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:56:43 +03:00
Ivan Okhotnikov e96ea2b0f3 test(kubernetes-nodes): replace cross-chart parity with a render snapshot
The parent no longer renders pools to diff against, so the one-shot
extraction parity check is replaced by a self-contained golden snapshot.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:55:15 +03:00
Ivan Okhotnikov 1e5ba15f6d test(kubernetes): update tests for the control-plane-only chart
Drop pool-render tests; assert no pool objects render; add ingress-nginx
guard tests.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:54:25 +03:00
Ivan Okhotnikov 4bd79942f3 feat(kubernetes): require an ingress-nginx pool when the ingress addon is enabled
With pools moved out of the chart the ingress-nginx addon can no longer
rely on an auto-provisioned md0; fail the render with a clear message
when addons.ingressNginx.enabled and no pool carries roles: [ingress-nginx].

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:54:25 +03:00
Ivan Okhotnikov 31336f0e18 feat(kubernetes)!: remove nodeGroups from the Kubernetes API
Worker pools are now managed via KubernetesNodes resources; existing
pools are adopted by migration 52.

BREAKING CHANGE: the Kubernetes CR no longer accepts spec.nodeGroups.
Create KubernetesNodes resources for worker pools instead.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:54:25 +03:00
Ivan Okhotnikov 92b9785daa refactor(kubernetes): remove worker node pools from the chart
The KubevirtMachineTemplate/MachineDeployment/MachineHealthCheck/worker
WorkloadMonitor, the preserve-old-KMT block, the talos-reconcile Job and
its RBAC now live in the kubernetes-nodes chart. The kubernetes chart
renders the control plane only.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:54:24 +03:00
Ivan Okhotnikov 86ecedb3c0 feat(platform): adopt worker pools into kubernetes-nodes releases (migration 52)
Pre-upgrade migration for the Phase 2 kubernetes chart split. For every
parent kubernetes-<cluster> HelmRelease it creates one child
kubernetes-nodes-<cluster>-<pool> HelmRelease and re-annotates the
existing worker objects onto it with helm.sh/resource-policy=keep, so
the now control-plane-only parent upgrade does not prune the tenant's
running workers. Byte-identical child render (PR-2a golden parity) keeps
the KubevirtMachineTemplate content-hash stable, so no worker VM churns.
Idempotent and strict (exit 1 on any failure). Bumps targetVersion to 53
(contiguous with main; rebase to 53/54 if another 52 lands first).

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:52:05 +03:00
Ivan Okhotnikov b2e1707632 fix(kubernetes-nodes): drop dead ephemeralStorage guard, clarify parity scope
The ephemeralStorage guard could never fire (the pool group is built from a
fixed set of chart values that never includes ephemeralStorage), so it read
as protection that is not there — remove it. Note in the parity script header
that only the four pool objects are compared, not the talos-reconcile Job.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:11 +03:00
Ivan Okhotnikov 326dbe0a5b test(kubernetes-nodes): cover GPU and kubelet pool shapes in render parity
Extend the golden-parity matrix beyond the resources-sized pool to also
diff a GPU pool and a kubelet-override pool against the parent render, so
future drift in those branches (which would roll the content-hash-named
KubevirtMachineTemplate) is caught in CI.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:11 +03:00
Ivan Okhotnikov f21365c4f0 fix(kubernetes-nodes): address review nits in guard comment and NOTES
Reference the adoption migration by role (Phase 2b) instead of a number
that does not exist on this branch, and correct the NOTES operational-note
count.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:11 +03:00
Ivan Okhotnikov 9e2f7cc953 test(kubernetes-nodes): run render parity in make test
Wire tests/render-parity.sh into the chart's test target so CI verifies
the pool objects stay byte-identical to the parent render.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov 3d10a1c431 fix(kubernetes-nodes): keep applied TalosConfigTemplate byte-identical to the parent
Restore the inline machineconfig comments (nameservers, GPU node labels)
inside the reconcile Job's data block so the applied TalosConfigTemplate
matches the parent's, avoiding drift on adoption.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov 73be7032a3 fix(kubernetes-nodes): drop unbacked cluster option source
The cluster field referenced x-cozystack-options source 'kubernetes',
which has no provider (cozystack-api returns NotFound), so the dashboard
dropdown 404s. Drop the annotation so the field is a plain text input;
a real provider listing Kubernetes CRs is a follow-up.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov fe32fd8e58 fix(kubernetes-nodes): scope KMT preservation by release and guard foreign pool names
Replace the collision-prone name-prefix match (pool 'gpu' matched
sibling 'gpu-large-<hash>') with a release-name annotation scope, and
add a render-time guard that fails cleanly when a pool's MachineDeployment
already exists under a different release (e.g. naming a pool after a
parent nodeGroup such as the default md0). Document the naming caveat in
NOTES.txt.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov 1e8d44995c fix(kubernetes): scope worker KMT preservation to the release
The preserve-old-KubevirtMachineTemplate block re-emitted any KMT owned
by the CAPI Cluster and referenced by a MachineSet. A kubernetes-nodes
pool's KMT lives in the same namespace under the same Cluster, so the
parent would re-emit it with a foreign meta.helm.sh/release-name and its
next upgrade would fail Helm ownership validation. Restrict the block to
KMTs whose release-name annotation matches the parent release.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov e7947dd0e7 feat(platform): register kubernetes-nodes application
Add the kubernetes-nodes-rd package (ApplicationDefinition), the
cozystack.kubernetes-nodes-application PackageSource (kubevirt variant,
kubernetes-nodes + kubernetes-nodes-rd components), and wire it into the
iaas bundle. Mirrors the vm-instance/vm-disk registration precedent.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov 277b32306c test(kubernetes-nodes): add unit tests and golden-parity check
Unit tests cover naming/selectors, clusterName wiring, autoscaler
annotations, omitted replicas, bootstrap TCT reference, KMT hash name,
worker WorkloadMonitor, and the empty-cluster / mis-prefixed-release
render guards. render-parity.sh diffs the pool objects against the
parent kubernetes chart and asserts byte-identity.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov f842c0a17e feat(kubernetes-nodes): generate API types for KubernetesNodes
Generated by cozyvalues-gen + controller-gen from values.yaml.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00
Ivan Okhotnikov fd14126bea feat(kubernetes-nodes): add worker node pool chart
Introduce the kubernetes-nodes application chart: one HelmRelease per
worker node pool, attached to a parent Kubernetes cluster by name
convention (kubernetes-<cluster>). Renders the KubevirtMachineTemplate,
MachineDeployment, MachineHealthCheck, worker WorkloadMonitor and the
talos-reconcile Job with its RBAC, extracted from the monolithic
kubernetes chart. Pool objects are byte-identical to the parent's render
for the same group, so a later adoption never churns live worker VMs.

This is the additive half of the Phase 2 split (community#8); the parent
kubernetes chart is left unchanged.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>
2026-07-21 22:35:10 +03:00