The B2B Podcast Index
Index
All categories
MarketingSalesSaaSFinanceHROpsLeadershipCustomer SuccessAI & DataProductStartups & FoundersRevOpsEngineering & DevTools
MethodologySubmit
Best of:MarketingSalesSaaSFinanceHROpsLeadershipCustomer SuccessAI & DataProductStartups & FoundersRevOpsEngineering & DevTools
An independent project byFame
SearchBest episodesGuestsInsightsMethodologySubmit a podcast
Index/Engineering & DevTools/DevOps Daily with Fexingo
DevOps Daily with Fexingo artwork

How Kubernetes CRD Versioning Breaks Controller Upgrades

DevOps Daily with Fexingo · 2026-07-02 · 8 min

0:00--:--

Key moments - from our scoring

Substance score

70 / 100

Five dimensions, 20 points each

Insight Density16 / 20
Originality13 / 20
Guest Caliber14 / 20
Specificity & Evidence15 / 20
Conversational Craft12 / 20

Kubernetes CRDs present a subtle operational trap: changing the storage version - the version Kubernetes uses to persist objects in etcd - can break controllers during upgrades if conversion webhooks or backward compatibility guarantees aren't in place. Lucas walks through a real incident at Cloudlytics, where a PipelineRun operator added a timeoutSeconds field in v1alpha2, marked it as the new storage version, and caused the old operator to crash on startup when it encountered v1alpha2 objects in etcd. The team had no conversion webhook, forcing a four-hour manual remediation using a one-off script to downgrade objects back to v1alpha1 schema. The episode covers why built-in conversion only works for structurally identical schemas, the risks of changing served versions without updating all controllers, and how preserveUnknownFields behavior can mask version incompatibilities. Luna and Lucas emphasize testing CRD changes with kubectl apply - dry-run=server, generating conversion webhooks early via Operator SDK, and auditing served versions regularly. They also discuss the trade-off between multi-version rigor and simplicity: projects like Kubernetes Gateway API invest heavily in conversion webhooks and testing, while most teams should consider single-version schemas with backward-compatible field evolution.

Key takeaways

  • →Never change a CRD's storage version without a conversion webhook or a tested migration plan - existing objects in etcd will become unreadable by older controllers and cause crashes.
  • →Test CRD schema changes using kubectl apply - validate=strict - dry-run=server against real data before production rollouts to catch conversion failures early.
  • →If you add a new served version, ensure all controllers watching the CRD are redeployed to handle the new version, or you risk missed updates and stale informer caches.
  • →Deleting a CRD removes all its custom resources from etcd permanently - use conversion webhooks or explicit migration scripts instead of relying on graceful deletion.
  • →Consider using a single evolved version with backward-compatible defaults rather than managing multiple versions, unless you're investing heavily in conversion webhook infrastructure like the Kubernetes Gateway API does.

Guests

Luna

Topics in this episode

Kubernetes CRD versioningStorage version migrationConversion webhooksOperator SDKPipelineRun custom resourcekubectl dry-run validationpreserveUnknownFieldsclient-go code generationKubernetes Gateway APIetcd serialization

Questions this episode answers

What happens when you change the storage version of a Kubernetes CRD without a conversion webhook?

Existing objects in etcd remain in the old storage version format, and controllers updated to the new CRD expecting the new schema will crash on deserialization. If you roll back, the old controller can't read objects stored in the new format, creating a stuck state that requires manual migration scripts or downtime to fix.

How do you safely test a CRD version change before production deployment?

Use kubectl apply - validate=strict - dry-run=server against a test cluster with realistic custom resource objects. The API server will report if any existing resources fail conversion or schema validation before you apply changes to production.

What's the difference between adding a served version versus changing the storage version in Kubernetes CRDs?

Adding a served version lets clients use that version but doesn't change where objects are persisted in etcd; changing the storage version changes where all objects are written. You can have multiple served versions with one storage version, but changing storage version requires conversion webhooks or manual migration.

Why does a CRD change cause controllers to miss updates after a rollout?

If a controller's informer cache watches a specific version and a new version is served without updating the controller's client, the watch may break or become stale, causing the controller to miss updates to custom resources until it's redeployed with a client that understands the new version.

Can you roll back a CRD to an older storage version after upgrading to a new one?

Kubernetes does not allow directly changing the storage version on an existing CRD without a conversion webhook. Rollback requires either a conversion webhook, a manual script to convert objects back to the old schema, or re-creating the CRD entirely - all of which involve downtime or data loss risk.

What our scoring noted

Our reviewer’s read on each dimension, with quotes from the episode.

Insight Density

16 / 20

The episode delivers concrete technical problems (storage version conflicts, deserialization errors, conversion webhooks, preserveUnknownFields behavior) that operators actually face. Most claims are substantive rather than platitudes, though some padding exists around the call-to-action and transition statements. The real-world Cloudlytics example grounds the discussion, and the four-hour outage consequence makes the stakes clear.

They had a CRD called PipelineRun with v1alpha1 as the storage version. They wanted to add a timeoutSeconds field, so they created v1alpha2, marked it as storage, and rolled out the new operator.
The old operator expected v1alpha1 schema, but etcd had v1alpha2 objects. The controller manager crashed on startup with a deserialization error.

Originality

13 / 20

The episode covers a real gotcha that is underexposed in typical DevOps discourse, focusing on the gap between Kubernetes documentation and practical failures. However, the core concepts (storage versioning, conversion webhooks, schema compatibility) are standard Kubernetes internals; the originality lies mainly in surfacing the problem rather than reframing it. The advice is sound but largely distills existing best practices.

CRD versioning. Specifically, what happens when you update a Custom Resource Definition and your controllers can't keep up.
Most teams just think, 'I'll add a new version, mark it as storage, and the API server handles conversion.' That's only true if the versions are structurally identical - same schema, same field types.

Guest Caliber

14 / 20

Luna and Lucas speak from hands-on operator/controller experience (Luna has seen real crashes, Lucas references the Cloudlytics production incident and multi-project exposure). They demonstrate depth in CRD semantics and failure modes that only practitioners face regularly. However, neither guest is identified by title or affiliation, making it hard to assess their actual seniority or whether they are primarily podcasters.

I've definitely seen controllers crash after a CRD update.
I saw this happen at a company - let's call them Cloudlytics - that runs a Kubernetes operator for managing data pipelines.

Specificity & Evidence

15 / 20

The episode provides a named company (Cloudlytics), a specific CRD name (PipelineRun), concrete schema changes (adding timeoutSeconds field, changing storage version from v1alpha1 to v1alpha2), and a quantified downtime consequence (four hours). The kubectl command ( - validate=strict - dry-run=server) is precise. Some discussion remains conceptual (e.g., conversion webhook overhead, informer cache staleness) without metrics.

They had a CRD called PipelineRun with v1alpha1 as the storage version. They wanted to add a timeoutSeconds field, so they created v1alpha2, marked it as storage, and rolled out the new operator.
It took about four hours of downtime.

Conversational Craft

12 / 20

The dialogue is natural and Luna poses clarifying questions ("And then something went wrong...") that move the narrative, but follow-ups are mostly confirmatory rather than challenging. Host does not push back on claims or explore trade-offs deeply; the 'simpler is safer' philosophy at the end is stated but not tested. The conversation lacks the tension and genuine dispute that sharpens thinking.

And that's where teams get tripped up. They update the CRD, add a field, mark the new version as storage, and suddenly old objects lose data or become unreadable by older controllers.
Wait, so they were stuck? How did they fix it?

Conversation analysis

Computed from the transcript - who did the talking, and the words that came up most.

Most-used words

version25lucas14luna14storage14conversion11alpha9objects8field8versions7kubernetes6controller6change6schema6operator6back6server6

Episode notes

Episode 86 of DevOps Daily with Fexingo dives into a subtle but painful Kubernetes trap: Custom Resource Definition (CRD) versioning. When you update a CRD's schema to add a new field or change a validation rule, existing Custom Resources stored in etcd may not automatically convert to the new version - and controllers that watch the old version can crash or miss updates. We walk through a real incident at a mid-sized SaaS company where a rollback attempt actually made things worse, because the old controller couldn't parse the new CRD storage version. You'll learn why the `storage` flag matters, how conversion webhooks can save you, and one simple check to add to your CI/CD pipeline before deploying CRD changes. #Kubernetes #CustomResourceDefinitions #CRDVersioning #ControllerUpgrades #etcd #KubernetesOperators #ConversionWebhooks #DevOps #SiteReliabilityEngineering #KubernetesRollback #CI/CD #CloudNative #KubernetesStorage #ControllerManager #APIExtensions #KubernetesTroubleshooting #FexingoBusiness #BusinessPodcast Keep every episode free: buymeacoffee.com/fexingo

Full transcript

8 min

Transcribed and scored by The B2B Podcast Index.

Lucas: Luna, I want to talk about a Kubernetes trap that almost never gets attention until it breaks a production rollout: CRD versioning. Specifically, what happens when you update a Custom Resource Definition and your controllers can't keep up. Luna: I've definitely seen controllers crash after a CRD update. Usually the team just restarts the controller and hopes it works.

But the underlying issue is deeper, right? Lucas: Much deeper. The problem starts with how CRDs handle multiple API versions. You can define v1, v1beta1, v1alpha2 - but only one version can be the 'storage version' at any time.

That's the version Kubernetes uses when it writes objects to etcd. When you change the storage version, existing objects get converted only if you have a conversion webhook or if all versions share a common schema. Luna: And that's where teams get tripped up. They update the CRD, add a field, mark the new version as storage, and suddenly old objects lose data or become unreadable by older controllers.

Lucas: Exactly. I saw this happen at a company - let's call them Cloudlytics - that runs a Kubernetes operator for managing data pipelines. They had a CRD called PipelineRun with v1alpha1 as the storage version. They wanted to add a timeoutSeconds field, so they created v1alpha2, marked it as storage, and rolled out the new operator.

Luna: Classic. And then something went wrong, they tried to roll back, and the old operator couldn't read the PipelineRun objects because they were now stored as v1alpha2? Lucas: Exactly that. The old operator expected v1alpha1 schema, but etcd had v1alpha2 objects.

The controller manager crashed on startup with a deserialization error. The team had to manually edit the CRD to change the storage version back - but Kubernetes doesn't allow changing the storage version on an existing CRD without a conversion webhook or a full migration. Luna: Wait, so they were stuck? How did they fix it?

Lucas: They ended up writing a one-time script that queried all PipelineRun objects from etcd directly, unmarshalled them as v1alpha2, dropped the new field, and re-encoded them as v1alpha1. Then they patched the CRD to set v1alpha1 back as storage. It took about four hours of downtime. Luna: That's a nightmare.

And it's not a rare edge case - any team that iterates on CRDs for operators or controllers is at risk if they don't understand storage version semantics. Lucas: Right. And the Kubernetes docs do cover this, but it's buried in the CRD versioning section. Most teams just think, 'I'll add a new version, mark it as storage, and the API server handles conversion.'

That's only true if the versions are structurally identical - same schema, same field types. If you add or remove fields, the built-in conversion won't work. Luna: So the golden rule is: never change the storage version unless you have a conversion webhook or you can guarantee backward compatibility. But what about the common pattern of adding a field with a default value?

Does that break? Lucas: It can, if the field is in the new version spec but not in the old. When the old controller reads the object, it gets an unknown field - if the CRD has preserveUnknownFields set to false, the API server will strip it. But if preserveUnknownFields is true, the old controller might ignore it.

The safer path is to use conversion webhooks from the start if you expect schema evolution. Luna: And conversion webhooks come with their own risks - latency, failure handling, resource usage. But they're better than a four-hour outage. Lucas: Absolutely.

And there's another angle: even if you don't change the storage version, simply adding a new served version can cause issues if your controller's informer cache is watching a specific version. If the watch breaks or the cache gets stale, you might miss updates. Luna: I've seen a related problem where a controller was using a client-go code-generated client that only understood one version. When a new version was served, the client couldn't list or watch the resources at all.

They had to regenerate the client and redeploy. Lucas: If today's tech conversation gave you something usable - maybe a way to avoid your next rollback nightmare - the way this show stays ad-free and focused is through listener support. You can find that at buy me a coffee dot com slash fexingo. Just a simple way to keep these conversations going without interrupting them.

Luna: Yeah, it really does make a difference. And it keeps us independent, so we can dig into exactly these kinds of traps without worrying about sponsorship constraints. Lucas: So back to practical advice. One thing every team should do: before deploying a CRD version change, run a dry-run apply against a test cluster with real objects.

The API server will tell you if any existing resources would fail conversion. There's a kubectl command - kubectl apply - validate=strict - dry-run=server - that catches schema mismatches. Luna: And add that to your CI/CD pipeline. I'd also recommend auditing your CRDs regularly to check for unused served versions.

Each served version adds overhead to the API server, and old versions can confuse operators. Lucas: Good point. And if you're building an operator with the Operator SDK, it generates conversion webhooks for you if you define multiple versions. But many teams skip that because it adds complexity.

They just delete and recreate the CRD - which is fine for development, but not for production with existing custom resources. Luna: Right. Deleting a CRD removes all its custom resources from etcd. That's a hard delete, not a graceful migration.

I've seen teams accidentally wipe out months of configuration data that way. Lucas: So let's summarise the key takeaways. One: never change the storage version without a conversion webhook or a migration plan. Two: if you add a served version, ensure all controllers that watch the CRD are updated to handle the new version.

Three: test CRD changes with - dry-run=server against realistic data. And four: if you must roll back, have a script ready to convert objects back to the old storage version. Luna: And one more: consider using a single version and evolving it with backward-compatible fields and defaults. That avoids the whole multi-version headache, at the cost of less schema rigor.

Lucas: That's a valid trade-off. Some projects like the Kubernetes Gateway API use multiple versions extensively, but they invest heavily in conversion webhooks and testing. For most teams, simpler is safer. Luna: Alright, next time we should talk about the interaction between CRD pruning and defaulting - there's a subtle bug when you set a default value on a field that gets pruned.

Lucas: That's a great topic. We'll get into how defaulting and pruning interact, and why your custom resource might end up with nil values where you expected defaults. For now - thanks for listening, and we'll see you next time.

Related episodes across the Index

Other episodes covering the same guests and topics, from across The B2B Podcast Index.

  • How Idempotency-Key Design Prevents Payment DisastersThe Developer Tools Podcast with Fexingo · features Luna98 / 100
  • Why Pipeline Velocity Trumps Deal Size Every TimeThe Growth Operator with Fexingo · features Luna95 / 100
  • Why Enterprise Software Deals Now Include a Vendor AI Model Explainability MandateB2B SaaS Talks with Fexingo · features Luna94 / 100
  • How B2B Brands Wreck Pipeline with Unsyncroned CRM DataThe Marketing Operator Podcast with Fexingo · features Luna92 / 100
  • Why Marketing Attribution Misses the Seasonality PatternMarketing Analytics with Fexingo · features Luna91 / 100
  • How to Sell Against a Competitor Already in the BuildingSales Leadership with Fexingo · features Luna85 / 100

More from DevOps Daily with Fexingo

All episodes →
  • How Kubernetes StatefulSet PVC Resizing Causes Node Disk Failures94 / 100
  • How Kubernetes Topology Spread Constraints Create Scheduling Hotspots95 / 100
  • How Kubernetes Audit Logging Causes etcd Performance Degradation91 / 100
  • How Kubernetes Volume Snapshots Cause Storage Backend Data Corruption85 / 100
  • How Kubernetes Custom Resource Definitions Cause Controller Memory Leaks100 / 100
Explore the best B2B Engineering & DevTools podcasts →
All DevOps Daily with Fexingo episodes →