---
title: "Kubernetes API Deprecation Guide: Detecting and Fixing Deprecated APIs Before Upgrades"
description: "Operational sequence for detecting deprecated Kubernetes APIs before cluster upgrades. Using pluto, kubent, and kubectl to find deprecated resources, update manifests, and validate compatibility."
url: https://agent-zone.ai/knowledge/kubernetes/kubernetes-api-deprecation-guide/
section: knowledge
date: 2026-02-22
categories: ["kubernetes"]
tags: ["api-deprecation","cluster-upgrades","pluto","kubent","migration","api-versions"]
skills: ["api-deprecation-detection","manifest-migration","upgrade-preparation","compatibility-testing"]
tools: ["kubectl","pluto","kubent","helm"]
levels: ["intermediate"]
word_count: 1289
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/kubernetes-api-deprecation-guide/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/kubernetes-api-deprecation-guide/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Kubernetes+API+Deprecation+Guide%3A+Detecting+and+Fixing+Deprecated+APIs+Before+Upgrades
---


# Kubernetes API Deprecation Guide

Kubernetes deprecates and removes API versions on a predictable schedule. When an API version is removed, any manifests or Helm charts using the old version will fail to apply on the upgraded cluster. Workloads already running are not affected -- they continue to run -- but you cannot create, update, or redeploy them until the manifests are updated. This guide walks through the complete workflow for detecting and fixing deprecated APIs before an upgrade.

## How Kubernetes API Deprecation Works

Kubernetes follows a strict deprecation policy:

- **GA APIs** (v1) are never removed within a major version.
- **Beta APIs** (v1beta1, v2beta1) are deprecated when a stable version is introduced and removed after a minimum of 3 minor releases or 9 months, whichever is longer.
- **Alpha APIs** (v1alpha1) can be removed in any release without notice.

The practical consequence is that every Kubernetes minor version upgrade potentially removes API versions that were deprecated 2-3 versions earlier. If you skip versions, the risk multiplies.

### Notable API Removals by Version

| Removed In | Old API | Replacement |
|---|---|---|
| 1.22 | networking.k8s.io/v1beta1 Ingress | networking.k8s.io/v1 |
| 1.22 | rbac.authorization.k8s.io/v1beta1 | rbac.authorization.k8s.io/v1 |
| 1.25 | policy/v1beta1 PodDisruptionBudget | policy/v1 |
| 1.25 | batch/v1beta1 CronJob | batch/v1 |
| 1.26 | autoscaling/v2beta2 HorizontalPodAutoscaler | autoscaling/v2 |
| 1.27 | storage.k8s.io/v1beta1 CSIStorageCapacity | storage.k8s.io/v1 |
| 1.29 | flowcontrol.apiserver.k8s.io/v1beta2 | flowcontrol.apiserver.k8s.io/v1 |
| 1.32 | flowcontrol.apiserver.k8s.io/v1beta3 | flowcontrol.apiserver.k8s.io/v1 |

This table is not exhaustive. Always check the release notes for your target version.

## Step 1: Identify Your Target Version

Before scanning for deprecated APIs, know exactly where you are and where you are going:

```bash
# Current cluster version
kubectl version --short 2>/dev/null || kubectl version

# Check available upgrade versions (managed services)
# EKS
aws eks describe-cluster --name my-cluster --query 'cluster.version'

# GKE
gcloud container get-server-config --zone us-central1-a --format="yaml(validMasterVersions)"

# AKS
az aks get-upgrades --resource-group myRG --name myCluster -o table
```

Record your current version and target version. You will need both when running deprecation scanners.

## Step 2: Scan In-Cluster Resources with Pluto

Pluto scans live cluster resources and Helm releases for deprecated API versions. It checks what is actually deployed, not just what is in your Git repo.

### Install Pluto

```bash
# macOS
brew install FairwindsOps/tap/pluto

# Linux
curl -L -o pluto.tar.gz \
  "https://github.com/FairwindsOps/pluto/releases/latest/download/pluto_linux_amd64.tar.gz"
tar -xzf pluto.tar.gz && sudo mv pluto /usr/local/bin/
```

### Scan All In-Cluster Resources

```bash
# Detect deprecated APIs targeting a specific Kubernetes version
pluto detect-all-in-cluster --target-versions k8s=v1.31

# Example output:
# NAME                          KIND                      VERSION                              REPLACEMENT              REMOVED   DEPRECATED   REPL AVAIL
# my-ingress                    Ingress                   networking.k8s.io/v1beta1            networking.k8s.io/v1     true      true         true
# my-hpa                        HorizontalPodAutoscaler   autoscaling/v2beta2                  autoscaling/v2           true      true         true
```

The columns that matter are `REMOVED` (will break on the target version) and `REPL AVAIL` (whether the replacement API exists in your current version so you can migrate now).

### Scan Helm Releases

Helm stores the last applied manifest in its release secrets. Pluto can scan these directly:

```bash
pluto detect-helm --target-versions k8s=v1.31

# This catches cases where the live resource was converted by the API server
# but the Helm release still references the old API version.
# On next helm upgrade, it will try to apply the old version and fail.
```

This is a critical distinction. The API server automatically converts stored objects to the latest version, so `kubectl get` may show `networking.k8s.io/v1` even though the Helm release template still uses `v1beta1`. The next `helm upgrade` will break.

## Step 3: Scan with Kubent for a Second Opinion

Kubent (kube-no-trouble) is an alternative scanner. Running both tools catches edge cases that either tool might miss.

### Install Kubent

```bash
sh -c "$(curl -sSL https://git.io/install-kubent)"
```

### Run Kubent

```bash
kubent --target-version 1.31

# Output:
# 4:17PM INF >>> Deprecated APIs <<<
# KIND                VERSION              NAMESPACE    NAME          REPLACEMENT              SCOPE
# Ingress             networking/v1beta1   production   my-ingress   networking.k8s.io/v1     CLUSTER
```

Kubent also scans Helm releases and can scan local manifest files:

```bash
# Scan local files
kubent -f ./manifests/ --target-version 1.31

# Scan a Helm chart's rendered output
helm template my-release ./my-chart | kubent --target-version 1.31
```

## Step 4: Check API Server Metrics for Runtime Usage

Even after scanning manifests, clients might be calling deprecated APIs at runtime:

```bash
kubectl get --raw /metrics | grep apiserver_requested_deprecated_apis
```

This catches controllers, operators, and CI/CD pipelines that use deprecated APIs programmatically.

## Step 5: Update Manifests

Once you have a list of deprecated APIs, update each manifest. The changes are usually straightforward -- replace the `apiVersion` field and adjust any fields that changed between versions.

### Ingress: v1beta1 to v1

The Ingress API had significant structural changes between beta and stable:

```yaml
# OLD: networking.k8s.io/v1beta1
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: my-service
          servicePort: 80

# NEW: networking.k8s.io/v1
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  ingressClassName: nginx          # annotation replaced by field
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix           # required in v1, did not exist in v1beta1
        backend:
          service:
            name: my-service       # restructured from flat fields
            port:
              number: 80
```

Key differences: `ingressClassName` replaces the annotation, `pathType` is now required (use `Prefix`, `Exact`, or `ImplementationSpecific`), and the `backend` structure is nested differently.

### Pure apiVersion Changes (No Structural Differences)

These resources only require changing the `apiVersion` field. The spec is identical:

| Resource | Old | New |
|---|---|---|
| HorizontalPodAutoscaler | autoscaling/v2beta2 | autoscaling/v2 |
| CronJob | batch/v1beta1 | batch/v1 |
| PodDisruptionBudget | policy/v1beta1 | policy/v1 |

### Using kubectl-convert for Automated Conversion

```bash
kubectl krew install convert
kubectl convert -f old-ingress.yaml --output-version networking.k8s.io/v1 > new-ingress.yaml
```

Always review the converted output. The tool handles structural changes but may not preserve comments or formatting.

## Step 6: Update Helm Charts

For Helm-managed resources, update the templates in your chart source and bump the chart version.

### Fix Helm Release Metadata

If a Helm release was deployed with old API versions, `helm upgrade` will fail even if the chart templates are updated. The release metadata stored in the cluster still references the old API version. Use the `mapkubeapis` Helm plugin to fix this:

```bash
# Install the plugin
helm plugin install https://github.com/helm/helm-mapkubeapis

# Update release metadata to use current API versions
helm mapkubeapis my-release -n my-namespace

# Now helm upgrade will work
helm upgrade my-release ./my-chart -n my-namespace
```

This is the single most common source of Helm upgrade failures after a Kubernetes version bump.

## Step 7: Test on a Non-Production Cluster

Never upgrade production first. Validate on a test cluster at the target version:

```bash
# Server-side dry-run validates API versions against the actual API server
kubectl apply --dry-run=server -f manifests/

# Confirm zero deprecated APIs remain
pluto detect-all-in-cluster --target-versions k8s=v1.31
```

Use `--dry-run=server`, not `--dry-run=client`. Client-side dry-run does not validate API versions against the server.

## Step 8: Build a Pre-Upgrade Checklist

Combine all the above into a repeatable checklist for every upgrade:

```bash
#!/bin/bash
TARGET_VERSION="v1.31"

echo "=== Pre-Upgrade API Deprecation Check ==="
echo "Target: $TARGET_VERSION"
echo ""

echo "--- Pluto: In-Cluster Scan ---"
pluto detect-all-in-cluster --target-versions "k8s=$TARGET_VERSION"

echo ""
echo "--- Pluto: Helm Releases ---"
pluto detect-helm --target-versions "k8s=$TARGET_VERSION"

echo ""
echo "--- Kubent ---"
kubent --target-version "${TARGET_VERSION#v}"

echo ""
echo "--- API Server Deprecated API Usage ---"
kubectl get --raw /metrics 2>/dev/null | grep -c apiserver_requested_deprecated_apis || echo "No deprecated API calls detected"

echo ""
echo "--- Helm Releases Status ---"
helm list -A --output json | jq -r '.[] | "\(.namespace)/\(.name) chart=\(.chart) status=\(.status)"'
```

Run this script before every upgrade. If it reports zero findings, proceed with confidence. If it reports findings, fix them before touching the cluster version.

## Common Pitfalls

**Third-party Helm charts using old APIs.** Update the chart version before upgrading the cluster. If the chart maintainer has not updated, fork the chart and fix the templates yourself.

**CRDs with deprecated API versions.** Operator-managed CRDs sometimes reference deprecated APIs in their conversion webhooks or stored versions. Check the operator's compatibility matrix for your target Kubernetes version.

**CI/CD pipelines hardcoding API versions.** Search pipeline definitions for hardcoded `apiVersion` strings. These break silently when the cluster is upgraded.

**GitOps repositories with stale manifests.** If you use ArgoCD or Flux, scan the Git repository, not just the cluster. The next sync from Git will try to apply old versions even if the cluster auto-converted stored resources.

