FM6: API Drift
Kubernetes follows a strict API deprecation lifecycle: beta APIs are introduced, stable APIs replace them, and beta APIs are eventually removed. LLMs hallucinate removed API versions more than any other type of Kubernetes error because their training data contains years of blog posts, tutorials, and Stack Overflow answers written for older cluster versions.
The Deprecation Lifecycle
Every API migration follows the same pattern:
- Beta API introduced -- a new resource or feature enters as
v1beta1under an API group. - Stable API introduced -- the resource graduates to
v1. The beta version is deprecated in the same release or shortly after. - Beta API removed -- typically 2-3 minor versions after deprecation, per the Kubernetes deprecation policy. From this point, the API server rejects manifests using the old version with a hard error.
"Deprecated" means the API still works but prints a warning. "Removed" means it fails. LLMs do not distinguish between these states.
Major Migrations LLMs Get Wrong
Ingress: extensions/v1beta1 to networking.k8s.io/v1
Removed in Kubernetes 1.22. This is the most frequently hallucinated API version because Ingress existed as a beta for years (1.1 through 1.21) and generated enormous amounts of training data.
The structural changes in v1 are not just a version swap:
spec.backendrenamed tospec.defaultBackend.serviceNameandservicePort(flat fields) replaced byservice.nameandservice.port.number(nested).pathTypeis required on every path -- it was optional in beta.ingressClassNamereplaces thekubernetes.io/ingress.classannotation.
An LLM that generates extensions/v1beta1 will also use the old field structure, compounding the error.
PodDisruptionBudget: policy/v1beta1 to policy/v1
Removed in Kubernetes 1.25. The v1 API makes spec.selector immutable after creation and adds spec.unhealthyPodEvictionPolicy. LLMs frequently generate policy/v1beta1 because PDB examples in training data predate 1.25.
HorizontalPodAutoscaler: autoscaling/v2beta1 and v2beta2 to autoscaling/v2
v2beta1 removed in 1.25, v2beta2 removed in 1.26. The key structural change: targetAverageUtilization (a top-level field in beta) moves to target.averageUtilization (nested under target in v2). LLMs mix beta and stable field structures unpredictably.
Other Removed APIs
| Resource | Old API | Stable API | Removed in |
|---|---|---|---|
| CronJob | batch/v1beta1 |
batch/v1 |
1.25 |
| EndpointSlice | discovery.k8s.io/v1beta1 |
discovery.k8s.io/v1 |
1.25 |
| CSIDriver | storage.k8s.io/v1beta1 |
storage.k8s.io/v1 |
1.22 |
| FlowSchema | flowcontrol.apiserver.k8s.io/v1beta1 |
v1 |
1.26 |
Schema Validation
There are two levels of manifest validity, and LLM-generated manifests can fail at either:
- Structural validity: Does the YAML conform to the schema for this API version? Caught by
kubeconformor--dry-run=server. Wrong field names, wrong nesting, unknown fields. - Semantic validity: Does the manifest make sense in context? Does the referenced Service exist? Is the port correct? Caught only at apply time or with policy tools.
kubeconform validates manifests against the OpenAPI schema for a specific Kubernetes version. Always pin the version to match your target cluster:
kubeconform -kubernetes-version 1.30.0 -strict manifests/
The -strict flag rejects unknown fields, which catches the common case where an LLM generates fields from one API version in a manifest tagged with a different version.
Helm-Specific Drift
Helm templates can produce syntactically valid YAML that uses the wrong API version. The template renders without error, but kubectl apply fails on the cluster. Use Capabilities.APIVersions to branch on cluster version:
{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
apiVersion: networking.k8s.io/v1
{{- else }}
apiVersion: networking.k8s.io/v1beta1
{{- end }}
Another common Helm drift error: broken Go template expressions that fail silently. {{ .Values.replicas }} evaluates to empty (not an error) if replicas is not defined in values.yaml. Always use defaults: {{ .Values.replicas | default 3 }}.
Kustomize-Specific Drift
Kustomize strategic merge patches specify a target with group, version, and kind. If the API group in the patch does not match the resource, the patch silently fails to apply -- no error, no warning, just unpatched output.
What LLMs Get Wrong
extensions/v1beta1for Ingress. Removed since 1.22, but still the most common LLM-generated Ingress API version.- Beta HPA API versions. Mixing
autoscaling/v2beta1field structures withautoscaling/v2API version, or vice versa. - Flat Ingress backend fields. Using
serviceName/servicePortinstead of the nestedservice.name/service.port.numberstructure. - Missing
pathTypeon Ingress paths. Required innetworking.k8s.io/v1but optional in beta. LLMs trained on beta examples omit it. batch/v1beta1for CronJob. Removed since 1.25, but CronJob tutorials from the beta era are abundant in training data.- No schema validation in the workflow. LLMs generate manifests without suggesting validation, so errors are discovered only at deploy time.
Prevention
The most effective defense against API drift is automated validation in the CI pipeline:
kubeconformwith-strictand-kubernetes-versionmatching the target cluster.plutoscans manifests, Helm charts, and running clusters for deprecated and removed APIs.--dry-run=servervalidates against the live API server schema, catching CRD and admission webhook issues that offline tools miss.
Run all three: kubeconform in CI, pluto as a pre-commit check, and dry-run=server in the deployment pipeline.