Zero-Downtime Helm Chart Upgrades - Transferring Ownership Between ClusterProfiles
The Problem
Some Helm releases, a CNI, an ingress controller, a service mesh, should never be fully removed and reinstalled just to move to a new version. A brief network outage while the CNI is uninstalled and reinstalled is often unacceptable.
Teams frequently keep ClusterProfile resources immutable in Git and roll out changes by creating a new ClusterProfile rather than editing the existing one. This is done deliberately: editing an existing ClusterProfile in place immediately rolls the change out to every cluster matching its clusterSelector. Sveltos does offer its own controls for that (see Add-on Rollout Strategy and Progressive Rollout Across Clusters, which stage a change across the clusters already matching a single ClusterProfile), but many teams additionally want the coarser, explicit control of deciding cluster by cluster whether it should pick up a new ClusterProfile at all, which is why they create a new ClusterProfile with its own selector rather than editing the existing one in place.
That pattern raises a question. If cluster X currently matches ClusterProfile A (which deploys a CNI Helm chart), and you want to move it to ClusterProfile B (which deploys a newer version of the same chart), what actually happens on the cluster when you make X stop matching A and start matching B?
If you simply relabel the cluster so it stops matching A at the same time it starts matching B, Sveltos's default stopMatchingBehavior (WithdrawPolicies) removes everything A deployed, including uninstalling the Helm release for the CNI, before B installs it again. That uninstall/reinstall cycle is the outage you're trying to avoid.
The Solution: Overlap, Don't Swap
For Helm charts, Sveltos tracks, per release, which ClusterSummary currently owns it. When a ClusterSummary is being torn down (because its cluster stopped matching the parent ClusterProfile/Profile), Sveltos checks whether another ClusterSummary on the same cluster already wants to manage that same Helm release. If one exists, ownership of the release is handed over to that other ClusterSummary in place: the release is upgraded, not uninstalled and reinstalled.
This means the outage isn't caused by the ownership model itself. It's caused by removing the old match and adding the new match in the same step, before the new ClusterSummary has had a chance to exist. The fix is to make the cluster match both ClusterProfiles for a brief overlap window, and only then remove it from the old one:
- The cluster starts matching the new
ClusterProfilewhile still matching the old one. This creates a secondClusterSummaryfor the same Helm release. Sveltos detects the conflict, and the newClusterSummaryreportsFailedNonRetriable, but it does nothing destructive. The chart already deployed by the oldClusterProfileis left untouched. - Only after that second
ClusterSummaryexists do you make the cluster stop matching the oldClusterProfile. Sveltos tears down the oldClusterSummary, sees the new one is waiting to take over the same release, and hands over ownership in place. If the newClusterProfiledeploys a different chart version, Sveltos performs a Helm upgrade to that version, not an uninstall followed by an install.
If, instead, you removed the old match and added the new match at the same time (or removed the old one first), there is no overlap window: the old ClusterSummary is torn down with nothing yet waiting to take over, so WithdrawPolicies uninstalls the release before the new ClusterSummary is even created.
Example: Rolling Out a New CNI Chart Version to a Subset of Clusters
Two ClusterProfiles are created up front, both immutable once committed. The first is already rolled out to every cluster; the second targets only the clusters you're ready to migrate.
apiVersion: config.projectsveltos.io/v1beta1
kind: ClusterProfile
metadata:
name: cni-v1
spec:
clusterSelector:
matchLabels:
cni-version: v1
helmCharts:
- repositoryURL: <cni-repo-url>
repositoryName: cni
chartName: cni/cni-chart
chartVersion: 1.18.12
releaseName: cni
releaseNamespace: kube-system
helmChartAction: Install
---
apiVersion: config.projectsveltos.io/v1beta1
kind: ClusterProfile
metadata:
name: cni-v2
spec:
clusterSelector:
matchLabels:
cni-migrate-v2: "true"
helmCharts:
- repositoryURL: <cni-repo-url>
repositoryName: cni
chartName: cni/cni-chart
chartVersion: 1.19.6
releaseName: cni
releaseNamespace: kube-system
helmChartAction: Install
Note that cni-v2 uses a distinct selector label (cni-migrate-v2) rather than a value change on the same label. This keeps the two ClusterProfiles from ever racing to claim a cluster based on the same key, and makes the migration state of any given cluster visible directly from its labels.
Migrate a cluster by moving its labels through three states:
| Step | Cluster labels | Matches | Result |
|---|---|---|---|
| 0, start | cni-version: v1 |
cni-v1 only |
cni-v1 owns the ClusterSummary. CNI 1.18.12 is deployed. |
| 1, onboard | cni-version: v1, cni-migrate-v2: "true" |
cni-v1 and cni-v2 |
A second ClusterSummary (owned by cni-v2) is created and reports a conflict. Nothing changes on the cluster yet. The CNI keeps running as deployed by cni-v1. |
| 2, cut over | cni-migrate-v2: "true" (drop cni-version: v1) |
cni-v2 only |
cni-v1's ClusterSummary is torn down. Ownership of the Helm release transfers to cni-v2's ClusterSummary in place. Sveltos performs a Helm upgrade from 1.18.12 to 1.19.6. There is no uninstall/reinstall step. |
Step 1 is the important one: apply the cni-migrate-v2: "true" label to a cluster without removing cni-version: v1, verify the second ClusterSummary exists (kubectl get clustersummary -A) and reports the conflict as expected, and only then remove cni-version: v1 in a follow-up change. Doing both label changes in the same commit collapses the overlap window to zero and reintroduces the outage.
Verifying the Handover
# Step 1: confirm both ClusterSummaries exist for the migrating cluster
kubectl get clustersummary -A -o wide
# Step 2, after cutover: confirm only the v2 ClusterSummary remains,
# and that the Helm release was upgraded rather than reinstalled
kubectl get clustersummary -A -o wide
helm history <release-name> -n <release-namespace> --kube-context <managed-cluster>
A successful in-place transfer shows a single Helm release revision incrementing (an upgrade in helm history), never a REVISION 1 with a new install timestamp following an uninstall.
Alternative: Resolving the Conflict with Tiers Instead
The overlap-then-cutover choreography above isn't the only way to hand off a Helm release between two ClusterProfiles. Tiers resolve the same "two ClusterProfiles target the same resource on the same cluster" conflict using priority instead of a timed cutover: give the new ClusterProfile a lower tier value, and it always wins over the old one for that release, for as long as both keep matching the cluster. For raw manifests (policyRefs) and Kustomize output (kustomizationRefs), tiers are not just an alternative, they're the only option, since the in-place handover described above is Helm-specific.
Tiers are the simpler option when you're fine with the cluster matching both ClusterProfiles indefinitely. There's no ordering to get right, since the resolution happens by priority on every reconciliation rather than by which ClusterSummary was there first. The tradeoff: the old ClusterProfile is still silently in the loop. If the new one ever stops matching the cluster (a bad label change, a selector typo), Sveltos falls back to the old ClusterProfile and reverts the release to whatever it manages, which may not be what you want for something like a CNI version.
Use the overlap-then-cutover approach in this page when you want a clean, permanent handoff with the old ClusterProfile fully out of the picture afterward. That's the common case when ClusterProfiles are treated as immutable and a new one is created per upgrade. Use tiers when a lightweight, permanent override is enough and you don't need to fully retire the old match.
Caveats
- This page covers Helm charts (
spec.helmCharts) only. The in-place handover described here relies on Sveltos tracking Helm release ownership perreleaseName/releaseNamespace. There is no equivalent mechanism for raw Kubernetes manifests deployed viapolicyRefsor Kustomize output deployed viakustomizationRefs: moving one of those betweenClusterProfilesfollows the defaultstopMatchingBehaviorand is removed by the oldClusterSummarybefore the new one applies it, regardless of any overlap window. For those resource types, use Tiers instead: tiers resolve ownership by priority rather than by handover, and apply to raw manifests and Kustomize output as well as Helm charts. - This only works if the same Helm release (same
releaseNameandreleaseNamespace) is targeted by bothClusterProfiles. If the newClusterProfilerenames the release or moves it to a different namespace, Sveltos has no way to know it's the "same" release, and the old one is removed independently of the new one being installed. - The overlap window only needs to be long enough for the second
ClusterSummaryto be created and reconciled at least once, seconds, not minutes, but the two label changes must land as separate reconciliations, not a single atomic update. - This pattern applies to any Helm based add-on managed by
ClusterProfile/Profile, not just CNIs. It is the general mechanism Sveltos uses whenever ownership of a Helm release moves from oneClusterSummaryto another. See Custom Resource Ownership for the underlyingClusterProfile/Profile→ClusterSummaryownership model.