Kubernetes 1.37 turns Storage Version Migration on by default
Maya Okonkwo
Storage Version Migration (SVM) graduated to General Availability in Kubernetes v1.37, and the release notes state it is enabled by default across every v1.37 cluster. The built-in StorageVersionMigration API under storagemigration.k8s.io/v1 and its control-plane controller are now the sanctioned way to rewrite stored objects to the current storage version, without shipping a bespoke Job every time.
The operational read: one fewer script in the DR runbook, one more controller to reason about at 3am.
What actually changed
Per the Kubernetes blog post, the StorageVersionMigration API graduated to v1 and the controller is on by default. Nothing about the resource lifecycle is new to operators who have been running the out-of-tree kube-storage-version-migrator for the last few releases; what is new is that the machinery is now part of core and does not need to be opted into.
The problem the release notes call out is unchanged from the KEP: Kubernetes stores each API object using a specific storage version, and the only way to move an already-stored object to a newer storage version is to mutate it through the API server. Nothing rewrites objects in etcd out of band. That is why "just bump the CRD" or "just rotate the KMS key" never actually moves the bytes already on disk.
Why the API server cares
Two use cases have driven this feature since it was proposed. Both are still the reason it is on by default now.
The first is CRD version cleanup. If a CRD served v1alpha1 last year and now serves v1, every custom object written under v1alpha1 is still on disk in that schema. You cannot drop the old served version until each of those rows has been re-written through the API server. Doing it by hand means kubectl get ... -o yaml | kubectl apply -f - in a loop, and hoping nothing has drifted.
The second is encryption at rest. If you rotate a KMS key, existing rows stay encrypted under the old key until they are re-written. In a compromise scenario where you need the old key material out of circulation, that gap is the thing you care about, and the gap only closes on the next mutation of each object.
SVM is a controller that does the mutation for you. That is the whole idea. Graduating it to GA and turning it on by default means every conformant v1.37 cluster now has that primitive available without an add-on.
Kicking one off
The interaction model is declarative. You create a StorageVersionMigration object naming the resource you want re-written; the controller walks the resource and writes each object back through the API server; status conditions on the migration object tell you when it is done.
apiVersion: storagemigration.k8s.io/v1
kind: StorageVersionMigration
metadata:
name: <migration-name>
spec:
resource:
group: <api-group>
resource: <resource-plural>
version: <storage-version>
The placeholders are on purpose. Check the v1.37 API reference for the exact field names before pasting this into a cluster; the point here is the shape, not the schema. In CI/CD terms, this is another manifest to check into the same GitOps repo that owns your CRDs, gated behind the same review as any other cluster-scoped resource.
The failure modes to keep in your runbook
A migration is a fan-out write against the API server. On a big cluster that means real qps, admission-webhook load and audit-log volume for the duration of the run. If your admission chain is slow, or your audit sink is close to its ceiling, an SVM run is exactly the kind of background traffic that turns "the cluster is fine" into "the cluster is fine except every write is queued".
Two other things worth wiring into the runbook. Webhook-backed conversion for CRDs means every rewrite goes through your conversion webhook; if that webhook is flaky, the migration is flaky. And migrations are not free rollbacks: once an object has been re-written under the new storage version, going back to the old served version means another migration, plus whatever schema loss the round trip implies.
Blast radius, then, is not the SVM controller itself. It is the cluster-wide write amplification the controller creates while it works.
How teams did this before
The out-of-tree kube-storage-version-migrator covered the same ground and will keep working; the GA in-tree flavour just removes the "install this first" step and standardises the resource across distributions. For CRDs specifically, conversion webhooks are still the mechanism that lets old-version reads keep working while a migration is in flight; SVM is not a replacement for them, it is what you run once conversion is in place.
The hand-rolled option, a Job that lists every object of a kind and re-applies it, also still works, and for very small custom resources it is still the least surprising thing to do. What GA closes is the gap where a cluster operator wanted the primitive to just be there and had to justify a chart install to get it.
Nothing on your existing clusters changes on upgrade until you create a StorageVersionMigration object. The switch that flips in v1.37 is availability, not behaviour.
Source: Kubernetes Blog (kubernetes.io)