Skip to content

[Epic] Adopt the kstatus readiness model platform-wide (Flux 2.8 / helm-controller v1.5.0) #2642

Description

@myasnikovdaniil

Epic. Flux 2.8 (helm-controller v1.5.0) flips HelmRelease health assessment to the kstatus model platform-wide. The version bump landed in #2602 (merged), so this epic tracks the consequential work: an API change to ComponentInstall/ApplicationDefinition (healthCheckExprs, waitStrategy) plus per-package adoption across the three readiness classes below.

Status as of 2026-08-13

The plumbing is finished and adoption has barely begun. Task 1 landed in #2602 via Option B, and the typed API plus its threading through both HR builders landed in #3273, but no package on origin/main sets waitStrategy or healthCheckExprs through that typed API, so it is a live and entirely unconsumed surface. Only two HelmRelease templates carry a kstatus gate anywhere in the tree, packages/extra/seaweedfs/templates/seaweedfs-db.yaml and packages/extra/monitoring/templates/helmrelease.yaml, and both got there through unrelated PRs rather than through this epic. Phase 1 of the template work (#3264) is a stalled draft: CHANGES_REQUESTED since 2026-07-20 over dead failed predicates, with five unticked dev-cluster verification boxes and an E2E run that never reached the changed code. Two corrections to the original plan are recorded in Task 2 and Task 5 below. The "add healthCheckExprs to all 12 umbrellas" framing is wrong for umbrellas that wrap only native workloads, and packages/apps/kubernetes plus packages/apps/kubernetes-nodes are structurally excluded from the model as long as they carry release.cozystack.io/helm-install-disable-wait.

Context

PR #2602 (feat(flux): upgrade to v2.8.0 — tenant chart v0.48.0 + management-cluster embed, web UI opt-in) upgrades Flux from v2.7.3 → v2.8.0 across both planes:

  • tenant clusters via flux-operator / flux-instance charts v0.33.0 → v0.48.0 (now at 0.50.0, still tracking distribution.version: 2.8.x in packages/system/fluxcd/values.yaml).
  • management cluster via the embedded internal/fluxinstall/manifests/fluxcd.yaml regenerated from the flux-aio timoni bundle (helm-controller:v1.5.0, internal/fluxinstall/manifests/fluxcd.yaml:8097).

In particular helm-controller goes v1.4.3 → v1.5.0. Beyond the version bump, v1.5.0 flips two defaults to align with Helm v4 (verbatim from the v1.5.0 CHANGELOG):

The controller now uses Helm v4, and, with this change, new default behaviors are being introduced (breaking changes) to keep Flux and Helm aligned:

  • Apply method is now defaulting to server-side apply for new HelmReleases.

  • Health checks now default to using kstatus for assessing readiness and failures of applied resources.

waitStrategy.name now defaults to poller (kstatus polling of every applied resource, including managed HelmReleases), and can be reverted to legacy (Helm v3 wait, core kinds only) via the UseHelm3Defaults controller feature gate. Cozystack does not enable that gate.

The kstatus default makes HelmRelease.Ready mean "every applied resource is healthy" instead of "helm install returned". That is correct for the end-to-end semantics we want between packages, spec.dependsOn becomes a real readiness gate instead of an ordering hint, but it interacts badly with existing umbrella patterns and is currently unreachable for leaf packages.

Symptoms

After applying #2602 on dev10, tenant-root/kubernetes-test HR enters Reconciling and its child HRs report DependencyNotReady. Cycle shape: parent waits on each child via kstatus → each child has dependsOn: [parent] → deadlock. Same shape (different chains) already fixed in 60aecd5f (vpa vpa-for-vpa nested HR) and unrelated yaml-merge bug in a23b5d27 (seaweedfs dependsOn collision). Retrospective write-up in docs/release.md:565-571.

Tasks

1. Unblock the Flux 2.8 bump (Class 3, single package), DONE

packages/apps/kubernetes is the only package in the monorepo whose child HRs carry dependsOn: [{Release.Name}] (guarded by a lookup). Pick one:

  • Option A, operator-side knob. Extend internal/operator/package_reconciler.go::buildHelmReleaseSpec to set spec.waitStrategy.name: legacy (or spec.install.disableWait: true + spec.upgrade.disableWait: true) on the parent HR for cozystack.kubernetes-application. Not taken, superseded by Option B.
  • Option B, child-side, remove the guard. Delete the {{- if lookup … Release.Name }} block from the 19 child HR templates in packages/apps/kubernetes/templates/helmreleases/. Landed in feat(flux): upgrade to v2.8.0 + chart fixes for strict SSA & kstatus (folds #2612) #2602 (f6c737983). No child HR references the bare parent any more, all remaining dependsOn entries point at siblings, and packages/apps/kubernetes/templates/helmreleases/ouroboros.yaml:93 keeps the comment explaining why no render-time guard is wired.

2. Class 2 umbrellas adopt healthCheckExprs (template edits only)

Correction (#3264 readiness audit, unmerged, needs sign-off). The original "add healthCheckExprs to every umbrella" framing is wrong. kstatus already assesses Deployments, StatefulSets and child HelmReleases natively, and a built-in workload has no empty-status race to close, so an expression on such an umbrella is a no-op. A gate is required only where the HR's manifest is a custom resource. Rows below are annotated with that verdict, and the 8 marked no-op are candidates for closing rather than pending work.

3. Class 1 leaves, operator support for per-package knobs

These packages render the underlying CR directly, the wrapping HR is built in Go. Adopting Model B requires plumbing new fields through the operator and through the apiserver. The epic originally named only the operator, but every catalog app's HR is built by pkg/registry/apps/application/rest.go::convertApplicationToHelmRelease.

  • Extend cozystack.io/v1alpha1.ComponentInstall (or Package) in api/v1alpha1/ with healthCheckExprs, optionally waitStrategy and disableWait. Landed in feat(api): typed waitStrategy/healthCheckExprs for kstatus readiness (#2642) #3273: api/v1alpha1/packagesource_types.go:154,164 on ComponentInstall and api/v1alpha1/applicationdefinitions_types.go:97,111 on ApplicationDefinitionRelease, both typed as kustomize.CustomHealthCheck and gated by a poller;legacy enum. Prerequisite go.mod bump of helm-controller/api v1.4.3 → v1.5.1 landed in the same PR (83f0d3bc9, go.mod:11). disableWait was not added, the pre-existing release.cozystack.io/helm-install-disable-wait annotation (pkg/config/config.go:59) already covers it.
  • Thread them into internal/operator/package_reconciler.go::buildHelmReleaseSpec. Landed in feat(api): typed waitStrategy/healthCheckExprs for kstatus readiness (#2642) #3273 at internal/operator/package_reconciler.go:100-106, plus the apiserver builder at pkg/registry/apps/application/rest.go:1645-1646 fed from pkg/cmd/server/start.go:274-275. Shared coupling helper config.ResolveWaitStrategy (pkg/config/config.go:220-228) defaults to poller whenever expressions are set without an explicit strategy. Tests: internal/operator/package_reconciler_test.go:164, pkg/registry/apps/application/rest_helmrelease_spec_test.go:140, pkg/config/waitstrategy_test.go:30.
  • Adopt per-package via values. Zero packages consume the typed API today. No ApplicationDefinition in packages/system/*-rd/cozyrds/ and no PackageSource in packages/core/platform/sources/ sets either field. Candidate leaves and their natural readiness signals:
    • packages/apps/postgres → CNPG Cluster.status.phase == 'Cluster in healthy state'
    • packages/apps/kafka → Strimzi Kafka Ready condition
    • packages/apps/mongodb → Percona PerconaServerMongoDB.status.state
    • packages/apps/mariadb → mariadb-operator MariaDB Ready
    • packages/apps/clickhouse → ClickHouseInstallation.status.status
    • packages/apps/redis, rabbitmq, foundationdb, opensearch (valkey dropped, no valkey-rd package exists on main)
    • packages/apps/vm-instance, vm-disk → KubeVirt VirtualMachine.status.printableStatus

4. Out of scope (explicit)

  • Do not enable --feature-gates=UseHelm3Defaults=true on helm-controller. The global flip would undo every Model B gain from tasks 2 and 3 and force the project back onto Helm v3 wait semantics. Verified holding: UseHelm3Defaults appears on main only inside CRD description text in internal/fluxinstall/manifests/fluxcd.yaml, and the only feature gates actually set there are ExternalArtifact=true (:8014, :8079).

5. Follow-ups discovered after the epic was written

References

Activity

  1. myasnikovdaniil commented on Jun 2, 2026

    @myasnikovdaniil
    ContributorAuthor

    Status update (as of main @ f0f1431)

    Base PR #2602 (Flux v2.7.3 → v2.8.0) is merged. Per-package migration status below.

    ✅ Task 1 — Unblock the Flux 2.8 bump (kubernetes package) — DONE

    Resolved via Option B (child-side, remove the guard) in f6c737983
    (fix(kubernetes): drop lookup-guarded parent-HR dependsOn on tenant addon child HRs).

    • No kubernetes child HR carries dependsOn: [{Release.Name}] against the parent anymore.
    • Remaining dependsOn entries point to sibling children (e.g. cilium → -gateway-api-crds).
    • The lookup/Release.Name guard is gone; ouroboros.yaml retains only an explanatory
      comment on why no render-time guard is wired.

    Option A (operator-side knob) was not taken.

    ⚠️ Task 2 — Class 2 umbrellas adopt healthCheckExprs — ~1 of 12

    • packages/extra/seaweedfs — has healthCheckExprs + waitStrategy: poller
      (targets its backing CNPG Cluster; landed incidentally via 29c6afc84)
    • packages/apps/openbao
    • packages/apps/qdrant
    • packages/apps/bucket
    • packages/apps/nats
    • packages/apps/harbor
    • packages/apps/tenant (seaweedfs, ingress, etcd, monitoring, info)
    • packages/extra/external-dns
    • packages/extra/ingress
    • packages/extra/monitoring
    • packages/system/bootbox
    • packages/system/cozystack-basics

    ❌ Task 3 — Class 1 leaves, operator support — NOT STARTED

    • ComponentInstall/Package in api/v1alpha1/ — no healthCheckExprs/waitStrategy/disableWait fields.
    • buildHelmReleaseSpec in internal/operator/package_reconciler.go — no readiness knobs threaded.
    • No candidate leaf (postgres, kafka, mongodb, mariadb, clickhouse, redis, rabbitmq,
      foundationdb, opensearch, valkey, vm-instance, vm-disk) adopted anything.

    ✅ Task 4 — Out of scope guard respected

    --feature-gates=UseHelm3Defaults=true is not enabled anywhere (only appears in CRD
    description text in the embedded fluxcd.yaml).

    Remaining work: Task 2 (11/12 umbrellas) and all of Task 3 (operator plumbing + every leaf).

  2. added
    kind/cleanupCategorizes issue or PR as related to cleanup of code, process, or technical debt
    area/platformIssues or PRs related to platform infrastructure (bundle, flux, talos, installer)
    epicA large development increment that brings definite value to Cozystack users
    on Jun 4, 2026
  3. changed the title [-]Flux 2.8 readiness model migration: per-package work[/-] [+][Epic] Adopt the kstatus readiness model platform-wide (Flux 2.8 / helm-controller v1.5.0)[/+] on Jun 8, 2026
  4. added
    kind/api-changeCategorizes issue or PR as related to adding, removing, or otherwise changing an API
    and removed
    kind/cleanupCategorizes issue or PR as related to cleanup of code, process, or technical debt
    on Jun 8, 2026
  5. added
    triage/acceptedIndicates an issue is ready to be actively worked on
    priority/important-longtermImportant over the long term, but may not be staffed and/or may need multiple releases to complete
    on Jul 1, 2026
  6. myasnikovdaniil commented on Aug 14, 2026

    @myasnikovdaniil
    ContributorAuthor

    Re-audited this epic against origin/main at 09dc7f47f and rewrote the body to match the tree. Six of the 25 checkboxes are now checked: Task 1 Option B landed in #2602 (f6c737983), extra/seaweedfs in #2601 (29c6afc84), extra/monitoring in #3215 (f9afc7309), and both Task 3 plumbing items in #3273, which also bumped helm-controller/api to v1.5.1 and threaded the apiserver HR builder the epic never named. The typed API is a live but unconsumed surface: no ApplicationDefinition under packages/system/*-rd/cozyrds/ and no PackageSource under packages/core/platform/sources/ sets waitStrategy or healthCheckExprs, so every Task 3 leaf is still at zero. Task 2 needed a correction. Per the readiness audit in #3264, 8 of the 12 umbrellas wrap only native workloads or child HRs that kstatus already assesses, so an expression there is a no-op, and the rows are annotated rather than deleted because that audit lives in an unmerged draft. #3264 itself is blocked rather than idle: CHANGES_REQUESTED since 2026-07-20 over failed predicates that never fire, one of which is already merged in seaweedfs-db.yaml:34 and now has its own issue, while the unmerged EtcdCluster one stays in #3264's scope. A new Task 5 records the follow-ups the original body predates, including that packages/apps/kubernetes and kubernetes-nodes are structurally excluded from the model while they carry release.cozystack.io/helm-install-disable-wait. #3263 is closed as implemented by #3273, and the valkey leaf is dropped from Task 3 because no valkey-rd package exists on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area/platformIssues or PRs related to platform infrastructure (bundle, flux, talos, installer)epicA large development increment that brings definite value to Cozystack userskind/api-changeCategorizes issue or PR as related to adding, removing, or otherwise changing an APIpriority/important-longtermImportant over the long term, but may not be staffed and/or may need multiple releases to completetriage/acceptedIndicates an issue is ready to be actively worked on

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions