← Back to Kubernetes Deprecated API Check

Why a manifest that applied yesterday fails after a Kubernetes upgrade

The mechanism

Kubernetes' API deprecation policy guarantees a minimum notice window before removing an API version — at least 12 months or 3 releases (whichever is longer) for a version that reached general availability, and at least 9 months or 3 releases for a beta. During that window the old and new versions of an object usually both work, converted automatically by the API server. At the end of it, the API server stops registering the old group/version for that kind entirely.

That is not a warning. kubectl apply, a rolling helm upgrade, or a controller's own reconcile loop against a manifest still written in the old apiVersion gets back no matches for kind "X" in version "Y" and the operation fails outright — nothing partially applies. A manifest that was correct on every prior cluster version becomes wrong the instant the cluster crosses the removal release, with no code change of its own.

Why this bites during an upgrade specifically

Most teams do not hand-write every manifest's apiVersion fresh each time — it gets copied from an older example, generated once by a scaffolding tool years ago, or left untouched inside a Helm chart or Kustomize base nobody has opened since it was written. It works, for years, right up until a managed control plane (EKS, GKE, AKS) is upgraded past the removal release, at which point the very first deploy after the upgrade is the one that fails — often mid-rollout, on a manifest nobody thought to look at because it had not changed.

The well-known removal waves so far: Kubernetes 1.16 removed the extensions/v1beta1 and apps/v1beta1/v1beta2 workload kinds; 1.22 removed a much larger batch spanning Ingress, RBAC, CRDs, admission webhooks, and more; 1.25 removed PodSecurityPolicy, CronJob's old batch version, PodDisruptionBudget's old policy version, and the first Horizontal Pod Autoscaler v2 beta; 1.26 removed the second HPA v2 beta. Each is documented with its exact release number in Kubernetes' own Deprecated API Migration Guide.

What this tool looks for, and why it stops where it does

Rather than trying to predict a future removal, this checks every apiVersion + kind pair in your manifest against a table built only from removals Kubernetes' own guide already documents with a fixed release number. Each entry names the release the old version stopped working and, where one exists, the exact replacement apiVersion to switch to.

It reads plain YAML text — apiVersion: and kind: as top-level keys, metadata.name for the report — and nothing else. It has no kubeconfig and makes no API server call, which is what makes it safe to run in CI against a manifest repository with no cluster credentials granted at all.

What this is not

This is not a Kubernetes admission controller, a cluster-connected scanner, or a guarantee your manifests will apply cleanly on every future release — the table only covers removals already documented with a release number, not every current beta or every proposed future change. It also does not evaluate Helm template expressions or Kustomize patches; run helm template or kustomize build first and check the rendered output, the same manifest your cluster would actually receive. And it says nothing about whether a rollout itself is safe once it applies — see Kubernetes Rollout Safety Check for readiness probes, disruption budgets, and termination grace periods, a separate failure class from a removed API entirely.

Common questions

Why does Kubernetes remove API versions instead of keeping them forever?

Every API version the server has to understand is code it has to keep correct and secure, and a beta API by definition was never meant to be permanent. Kubernetes' own deprecation policy commits to a minimum notice period — a GA API version is supported for at least 12 months or 3 releases (whichever is longer) after deprecation, and a beta is guaranteed at least 9 months or 3 releases — so there is always a documented window to migrate, but the version is not kept indefinitely.

What actually happens when you apply a manifest with a removed apiVersion?

The API server no longer registers that group/version for that kind at all, so kubectl apply, a Helm upgrade, or a controller's own reconcile loop gets back no matches for kind "X" in version "Y" and the operation fails outright — it is not a warning, and nothing partially applies.

Does upgrading kubectl or my CI tooling fix this?

No. The removal happens on the API server side, at the target cluster's Kubernetes version — the manifest's apiVersion field is what has to change, regardless of which kubectl or CI runner applies it.