Kubernetes API Deprecations and Manifest Migration

Find deprecated apiVersion strings in manifests, map legacy APIs to supported replacements, migrate Ingress from v1beta1 to v1, validate with server-side dry-run, and apply corrected YAML on any cluster.

Published

Updated

Read time 10 min read

Reviewed byDeepak Prasad

Kubernetes API deprecations and migrating legacy Ingress YAML to networking.k8s.io/v1
Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Deprecated versus removed APIs, cluster version checks, api-resources and api-versions discovery, kubectl explain for schema checks, searching manifests, common API migration table, Ingress v1beta1 to v1 walkthrough, server-side dry-run, diff, apply and verify, optional kubectl convert, checklist, and troubleshooting. Does not cover control-plane upgrades, full per-release removed-API catalogues, runtime-config, etcd storage migration, CRD conversion webhooks, or GitOps pipelines.

Before a cluster upgrade or when copying YAML from older tutorials, manifests may still reference API versions the server no longer serves. This guide shows how to discover supported APIs on your cluster, find stale apiVersion lines, migrate one legacy Ingress manifest to networking.k8s.io/v1, and validate the result without treating the topic as a full upgrade runbook.


What Is a Deprecated Kubernetes API?

Kubernetes API groups are versioned independently. A Deployment uses apps/v1 while a Pod uses v1; each group can evolve on its own schedule.

Status Meaning
Supported Served normally by the cluster
Deprecated Still served but scheduled for removal
Removed No longer accepted by the API server

A deprecated API may still work today. A removed API rejects create and update requests. Migration often requires more than swapping the apiVersion string because field names, required fields, and defaults can change between versions.


Discover and Detect Deprecated APIs

Check client and server versions

API availability depends on the Kubernetes server version, not only the kubectl client on your workstation.

bash
kubectl version

Sample output:

output
Client Version: v1.36.3
Kustomize Version: v5.8.1
Server Version: v1.36.3

API availability is determined by the server version. Keep kubectl within one minor version of the control plane, then check the APIs served by the target server before deployment. The supported skew is one minor version in either direction.

For additional version checks on nodes and control-plane components, see check Kubernetes cluster version.

List served resources and API versions

List resource types discovered from the server:

bash
kubectl api-resources -o wide

Sample output (trimmed):

output
NAME                                SHORTNAMES   APIVERSION           NAMESPACED   KIND
configmaps                          cm           v1                   true         ConfigMap
deployments                         deploy       apps/v1              true         Deployment
ingresses                           ing          networking.k8s.io/v1 true         Ingress
pods                                po           v1                   true         Pod

List served group/version pairs:

bash
kubectl api-versions

Sample output (trimmed):

output
apps/v1
batch/v1
networking.k8s.io/v1
policy/v1
autoscaling/v2

api-resources lists the resource types discovered from the server. api-versions lists the group/version pairs the server currently serves. Use these commands to detect unsupported or removed APIs, but use API warnings and the official deprecation guide to identify versions that are still served but deprecated.

A deprecated API can still appear in both commands until it is removed. Neither command labels a served API as deprecated.

If a group/version is absent from kubectl api-versions, the server does not serve it. If the group/version exists but the required kind is missing, check kubectl api-resources and confirm that any required CRD or aggregated API is installed.

Full discovery workflows with kubectl explain live in Kubernetes API resources and kubectl explain.

Detect deprecation warnings

On a cluster that still serves a deprecated API, the API server returns a deprecation warning. Convert that warning into a failing check with:

bash
kubectl apply --dry-run=server --validate=strict --warnings-as-errors -f manifest.yaml

--warnings-as-errors converts a deprecation warning into a non-zero command result. If the API has already been removed, the request fails with an unsupported group/version or no matches for kind error instead.

Server warnings for deprecated API requests have been supported since Kubernetes 1.19, and kubectl provides the inherited --warnings-as-errors flag.

Search source and rendered manifests

Search manifest trees for apiVersion declarations:

bash
grep -R "^apiVersion:" ./manifests/

Also check generated output, not only hand-written files:

  • Helm-rendered manifests — helm template <release> <chart> (Kubernetes Helm charts)
  • Kustomize builds — kubectl kustomize <overlay-directory> (Kubernetes Kustomize)
  • CI/CD deployment bundles
  • Example YAML copied from older blog posts

Watch API server warnings during kubectl apply and upgrade preflight tools your distribution provides.

Inspect replacement schemas with kubectl explain

After you pick a target apiVersion, confirm the schema the connected cluster expects.

Inspect Deployment fields:

bash
kubectl explain deployment --api-version=apps/v1

Sample output:

output
GROUP:      apps
KIND:       Deployment
VERSION:    v1

DESCRIPTION:
    Deployment enables declarative updates for Pods and ReplicaSets.

Inspect Ingress at the supported networking API:

bash
kubectl explain ingress --api-version=networking.k8s.io/v1

Sample output:

output
GROUP:      networking.k8s.io
KIND:       Ingress
VERSION:    v1

DESCRIPTION:
    Ingress is a collection of rules that allow inbound connections to reach the
    endpoints defined by a backend.

For Ingress migration, pathType is required on each path in v1. Inspect it directly:

bash
kubectl explain ingress.spec.rules.http.paths.pathType --api-version=networking.k8s.io/v1

The explain output lists allowed values: Exact, Prefix, and ImplementationSpecific.


Understand Common API Migrations

Representative replacements readers encounter during upgrades:

Removed API Replacement Removed starting Important migration point
extensions/v1beta1 Deployment apps/v1 1.16 spec.selector is required and immutable
networking.k8s.io/v1beta1 Ingress networking.k8s.io/v1 1.22 Structured Service backend and required pathType
batch/v1beta1 CronJob batch/v1 1.25 No notable schema migration
policy/v1beta1 PodDisruptionBudget policy/v1 1.25 Empty selector behavior changed
autoscaling/v2beta2 HPA autoscaling/v2 1.26 Metric target fields moved under target

For PodDisruptionBudget, an empty selector selects no Pods in policy/v1beta1 but all namespace Pods in policy/v1. For HPA, fields such as targetAverageUtilization move into the structured target object.

Check the official migration guide for each resource type. Field moves, new required fields, and default changes are not always visible from the version string alone.

This article does not catalogue every removed API per Kubernetes release. Use kubectl api-versions on your target cluster as the source of truth.


Migrate Ingress from v1beta1 to v1

Ingress is a practical example because v1 restructured backend fields and added required pathType, not only a new apiVersion.

Create the backend workload

Create the namespace:

bash
kubectl create namespace deprec-lab

Sample output:

output
namespace/deprec-lab created

Save web.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: deprec-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27.0
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: deprec-lab
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Apply the backend workload:

bash
kubectl apply -f web.yaml

Sample output:

output
deployment.apps/web created
service/web created

Wait until the Deployment is ready before you attach an Ingress rule to the Service:

bash
kubectl rollout status deployment/web -n deprec-lab --timeout=120s

Sample output:

output
deployment "web" successfully rolled out

Test the removed manifest

Save the legacy manifest as legacy-ingress.yaml:

yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: web
  namespace: deprec-lab
spec:
  rules:
    - host: web.example.local
      http:
        paths:
          - path: /
            backend:
              serviceName: web
              servicePort: 80

Older clusters accepted networking.k8s.io/v1beta1 with flat backend fields. On Kubernetes 1.36.3 this API version is removed.

bash
kubectl apply -f legacy-ingress.yaml

Sample output:

output
error: resource mapping not found for name: "web" namespace: "deprec-lab" from "legacy-ingress.yaml": no matches for kind "Ingress" in version "networking.k8s.io/v1beta1"
ensure CRDs are installed first

The final CRD suggestion is a generic kubectl message. In this case, the failure is caused by the removed built-in Ingress API version.

Update apiVersion, backend fields, and pathType

Save the migrated manifest as migrated-ingress.yaml:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: deprec-lab
spec:
  rules:
    - host: web.example.local
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              service:
                name: web
                port:
                  number: 80

Field changes from v1beta1 to v1:

v1beta1 field v1 replacement
backend.serviceName backend.service.name
backend.servicePort backend.service.port.number (or name for named ports)
path without pathType path plus required pathType (Prefix, Exact, or ImplementationSpecific)

The old v1beta1 manifest did not define pathType. The official migration guide says ImplementationSpecific is the closest replacement for that undefined behavior. Use Prefix or Exact only when you intentionally want those matching rules.

For TLS, ingress class, and path rules beyond this minimal host rule, see expose Services with Ingress.


Validate, Diff, Apply, and Verify

Server-side dry-run

Server-side dry-run submits the manifest to the API server for validation without persisting it:

bash
kubectl apply --dry-run=server --validate=strict -f migrated-ingress.yaml

Sample output:

output
ingress.networking.k8s.io/web created (server dry run)

(server dry run) confirms admission and schema checks ran on the server.

Preview changes with kubectl diff

Preview the migrated Ingress before apply:

bash
kubectl diff -f migrated-ingress.yaml

Because the Ingress does not exist yet, the command displays the object as additions and exits with status 1. Status 1 means differences were found, not that kubectl diff failed. Status 0 means no differences, while values greater than 1 indicate an error. These are the documented kubectl diff exit codes.

See kubectl apply, edit, patch and replace for diff and dry-run details.

Apply the migrated resource

After validation succeeds, apply the migrated file:

bash
kubectl apply -f migrated-ingress.yaml

Sample output:

output
ingress.networking.k8s.io/web created

Verify the stored API and backend

Confirm API version and routing fields:

bash
kubectl get ingress web -n deprec-lab

Sample output:

output
NAME   CLASS    HOSTS               ADDRESS   PORTS   AGE
web    <none>   web.example.local             80      0s

Read stored API version and path type:

bash
kubectl get ingress web -n deprec-lab -o jsonpath='apiVersion={.apiVersion} host={.spec.rules[0].host} pathType={.spec.rules[0].http.paths[0].pathType}{"\n"}'

Sample output:

output
apiVersion=networking.k8s.io/v1 host=web.example.local pathType=ImplementationSpecific

Check rules and backends when routing does not behave as expected:

bash
kubectl describe ingress web -n deprec-lab

Sample output (trimmed):

output
Rules:
  Host               Path  Backends
  ----               ----  --------
  web.example.local  
                     /   web:80 (192.168.5.36:80)
Events:              <none>

This verifies the migrated API object and backend reference, not end-to-end ingress traffic. An Ingress controller must be installed and must select this Ingress before the rule can route requests. An empty ADDRESS is expected when no controller has fulfilled the resource.


Convert Manifests with kubectl-convert

kubectl convert can rewrite a manifest to another API version. It is distributed as a separate plugin, not bundled in the default kubectl binary on many installations.

Example invocation:

bash
kubectl convert -f old-deployment.yaml --output-version apps/v1

Review converted output before apply. The plugin may choose defaults that differ from your intended configuration. Treat conversion as a starting point, then run kubectl explain and server-side dry-run on the result.

Manual migration with explicit field edits remains the primary method in this guide.


Troubleshoot Migration Errors

Symptom Likely cause Fix
no matches for kind Removed or wrong apiVersion, or missing CRD Run kubectl api-versions; update apiVersion and kind; install required CRDs
unable to recognize Unsupported API version or malformed YAML Validate syntax; confirm the group/version is served
strict decoding error: unknown field Field removed or moved in the new API Compare schemas with kubectl explain; remove or relocate the field
Version updated but apply still fails Required new fields or changed defaults Read the official migration note for that resource type
Client dry-run passes, server apply fails Admission policies or server-only validation Use --dry-run=server --validate=strict on the target cluster
kubectl edit change lost after upgrade Source YAML still uses old API Update Git, Helm, and Kustomize sources, not only live objects

Manifest migration checklist

  1. Identify the target Kubernetes server version.
  2. Search hand-written and generated manifests for apiVersion.
  3. List APIs the target cluster serves with kubectl api-versions.
  4. Review field and default changes in official migration notes.
  5. Update YAML and render Helm or Kustomize output again.
  6. Validate with kubectl apply --dry-run=server --validate=strict.
  7. Test in a non-production namespace or cluster.
  8. Apply and verify resource status and Events.
  9. Update charts, bases, overlays, and deployment automation to match.

What's Next


References


Summary

You checked the server version, listed served APIs with kubectl api-resources and kubectl api-versions, and used kubectl explain to confirm Ingress v1 requires pathType and structured service backends. Discovery commands show what the server serves today; they do not label a served API as deprecated.

The Ingress walkthrough showed a removed networking.k8s.io/v1beta1 manifest failing with no matches for kind, then a v1 replacement with pathType: ImplementationSpecific validated with server-side dry-run and applied successfully. The main lesson is that migration is schema work: update apiVersion, required fields, and backend structure together, then validate on the cluster you will run against.

For discovery commands in depth, continue with Kubernetes API resources and kubectl explain. For apply, diff, and dry-run mechanics, use kubectl apply, edit, patch and replace.


Frequently Asked Questions

1. What is the difference between a deprecated and a removed Kubernetes API?

A deprecated API is still served but scheduled for removal in a future release. A removed API is no longer accepted by the API server, so manifests using that apiVersion fail with errors such as no matches for kind.

2. Is changing apiVersion enough to migrate a manifest?

Often not. New API versions can rename fields, require new fields such as Ingress pathType, or change defaults. Review the official migration notes for the resource type and validate with kubectl explain and server-side dry-run.

3. How do I find deprecated APIs in my YAML files?

Search source and rendered manifests for apiVersion, validate them against the target cluster with server-side dry-run, and inspect API server deprecation warnings. kubectl api-versions shows whether a version is served, but it does not identify served versions that are deprecated.

4. Why does my migrated manifest pass client dry-run but fail on the cluster?

Client dry-run does not run full API server validation or admission policies. Use kubectl apply --dry-run=server --validate=strict against the cluster you plan to upgrade or deploy to.

5. Is kubectl convert installed by default?

No. kubectl convert is distributed as a separate plugin. Even when available, review converted output because defaults and field choices may not match your intended configuration.

6. What does no matches for kind mean during apply?

The cluster does not serve that apiVersion and kind combination. Check kubectl api-versions, confirm the API was not removed, and verify required CRDs are installed for custom types.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)