Migrate Manifests Off Removed Kubernetes APIs
The `legacy-apps` team is moving to this Kubernetes 1.35 cluster. Their manifests in `/root/legacy` (a CronJob, a HorizontalPodAutoscaler and an Ingress for the existing Deployment and Service `web`) were written for an older release, and `kubectl apply` now rejects them.
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
The legacy-apps team is moving to this Kubernetes 1.35 cluster. Their manifests in /root/legacy (a CronJob, a HorizontalPodAutoscaler and an Ingress for the existing Deployment and Service web) were written for an older release, and kubectl apply now rejects them.
Requirements
1. Update each file in /root/legacy to an API version supported by this cluster. Keep every object's name, namespace and behaviour.
2. For the Ingress, keep the rule for host web.legacy.local and path / (use path type Prefix) routed to Service web on port 80.
3. Apply all three files so the objects exist in legacy-apps.
Keep the files in place: the updated manifests are what the team will reuse.
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster.
Migrate manifests off removed API versions without changing what they do
Kubernetes retires beta API versions on a published schedule. Objects already stored in the cluster survive, because the API server can serve them through the newer version, but a YAML file that still names a removed version is rejected by kubectl apply. Migration is therefore two jobs: change apiVersion, then rewrite any fields whose names or shape changed in the newer version.
old file (removed version) ──→ apply rejected
│ 1. new apiVersion 2. renamed / restructured fields
└──→ new file ──→ server dry-run ──→ apply
1. Ask the cluster what it serves
kubectl api-versions | grep -E 'batch|autoscaling|networking'
kubectl api-resources --api-group=networking.k8s.io
kubectl explain ingress --api-version=networking.k8s.io/v1
Trust the cluster over memory. api-versions lists every group/version the server offers, and kubectl explain documents the fields of the version you name, which is exactly what you need when a field has moved. Both answer from the live cluster, so you do not have to hunt through the docs for a field path during the exam.
2. Know the removals that matter for application manifests
- CronJob:
batch/v1beta1stopped being served in v1.25. Usebatch/v1; the deprecation guide lists no notable field changes. - HorizontalPodAutoscaler:
autoscaling/v2beta1went in v1.25 andautoscaling/v2beta2in v1.26. Useautoscaling/v2. A metric'stargetAverageUtilizationbecomes atargetblock withtype: UtilizationandaverageUtilization. - Ingress:
extensions/v1beta1andnetworking.k8s.io/v1beta1went in v1.22. Usenetworking.k8s.io/v1, where several fields were renamed (next step).
3. Rewrite an Ingress backend by hand
Here is an old-style rule of our own, routing /catalog on shop.example.test to a Service port that is referenced by name:
# networking.k8s.io/v1beta1 (no longer served)
- path: /catalog
backend:
serviceName: catalog-svc
servicePort: http
In networking.k8s.io/v1 the backend becomes a nested service object, a string port goes under port.name (a numeric one would go under port.number), and every path must declare a pathType:
# networking.k8s.io/v1
- path: /catalog
pathType: Prefix
backend:
service:
name: catalog-svc
port:
name: http
Choose pathType deliberately. Exact matches only the literal path, Prefix matches by path elements split on /, and ImplementationSpecific leaves matching to the Ingress class. The guide suggests ImplementationSpecific when you need the undefined old behaviour; when you know the intended matching, state it. A top-level spec.backend is now spec.defaultBackend.
4. Validate before you apply
Assume the migrated file is shop-ingress.yaml and defines an Ingress named shop in prepium-tutorial.
kubectl apply --dry-run=server -f shop-ingress.yaml
kubectl apply -f shop-ingress.yaml
kubectl -n prepium-tutorial get ingress shop -o jsonpath='{.spec.rules[0].http.paths[0].backend.service}'
A server-side dry run submits the request to the API server without persisting it, so the server's schema checks run and leftover old fields or a missing pathType surface before anything changes. kubectl apply validates strictly by default, failing on unknown fields instead of dropping them. Then compare the stored object with the old file field by field: the goal is the same routing, schedule or scaling target, only expressed in the new schema.
The kubectl convert plugin can rewrite a file to another version, but it is not part of kubectl by default (it was removed from the built-in commands and is now installed separately), and the documentation warns that it may fill in non-ideal defaults. Review its output as carefully as a hand edit.
Common mistakes
- Changing only
apiVersionon an Ingress: the request fails becauseserviceNameandservicePortare not part of the v1 schema andpathTypeis missing. - Putting a named port under
port.number. Name and number are mutually exclusive, and number must be an integer. - Moving an HPA to
autoscaling/v2but keepingtargetAverageUtilization: strict validation rejects the unknown field. - Silently changing behaviour while migrating, for example a different schedule, host or path type than the original file intended.
| Example check | Evidence |
|---|---|
| Each file uses a served version | apiVersion appears in kubectl api-versions |
| Schema is valid | kubectl apply --dry-run=server succeeds |
| Ingress routing preserved | Host, path, pathType and backend.service match the intent |
| Behaviour unchanged | Schedule, scale target and limits read the same as before |
Cleanup: delete anything you created in prepium-tutorial with kubectl delete namespace prepium-tutorial. References: Deprecated API Migration Guide, Ingress, Install the kubectl convert plugin. Next, try Ingress configuration or CronJobs.
Practice scope
This additional curriculum lab is available in single-task practice with the existing 12-minute limit. The three CKAD mocks keep their original 17-task sets. Hints, an explained solution and requirement-by-requirement feedback are available in the lab.
Browse CKAD practice tasks →