Kubernetes CRD Validation Failure During AI Job Deployment
Problem
When deploying an AI training job to the staging cluster (Kubernetes v1.24) via Helm, the kubectl apply step aborts with a CRD validation error. The custom resource (AITrainingJob) is accepted by the Helm chart, but the API server rejects it, preventing the job from being created. The failure allows malformed configurations to slip through CI, leading to runtime crashes once the job is scheduled.
Typical error output:
error: error validating "ai-training-job.yaml": error validating data: ValidationError(AITrainingJob.spec): missing required field "resources", unknown field "gpuCount" in io.k8s.api.core.v1.PodSpec
Impact includes:
- Helm release stuck in
FAILEDstate. - Prometheus alerts fire for
helm_release_failed. - Staging environment cannot be used for end‑to‑end testing of new model versions.
Root Cause
The AITrainingJob CRD defines an OpenAPI v3 schema that is out of sync with the Helm chart’s generated manifest. Two specific mismatches caused the validation failure:
- Missing required field in the schema: The CRD marks
spec.resourcesasrequired, but the Helm values file omits it for jobs that rely on default resource limits. The API server therefore rejects any manifest lacking this field. - Stale field name: The chart still references a legacy field
gpuCountthat was renamed togpuin the CRD versionv1beta2. Because the CRD schema does not allowgpuCount, the API server reports an “unknown field” error.
Both issues stem from a version drift between the CRD definition (stored in the cluster) and the Helm chart source code. Since the staging cluster was upgraded to Kubernetes 1.24, the CRD version was also bumped from v1beta1 to v1, but the chart was never updated accordingly.
Debug
Step‑by‑step investigation that reproduced the failure:
- Render the Helm chart without applying it:
helm template ai-pipeline ./charts/ai-pipeline \
--values values/staging.yaml \
> rendered.yaml
- Inspect the rendered custom resource:
cat rendered.yaml | grep -A5 "kind: AITrainingJob"
Result:
apiVersion: pipelines.example.com/v1beta2
kind: AITrainingJob
metadata:
name: model-x-train
spec:
model: "model-x"
dataset: "s3://datasets/x"
gpuCount: 4
- Check the live CRD schema:
kubectl get crd aitrainingjobs.pipelines.example.com -o yaml \
| yq '.spec.versions[] | select(.name=="v1") | .schema.openAPIV3Schema'
Key excerpt of the schema:
spec:
type: object
required:
- model
- dataset
- resources
properties:
model:
type: string
dataset:
type: string
resources:
type: object
required:
- cpu
- memory
gpu:
type: integer
- Validate the manifest against the schema using
kubectl apply --dry-run=client:
kubectl apply -f rendered.yaml --dry-run=client
error: error validating "rendered.yaml": error validating data: ValidationError(AITrainingJob.spec): missing required field "resources", unknown field "gpuCount"
These steps confirm that the rendered manifest does not satisfy the CRD schema.
Solution
Two corrective actions were required:
1. Align Helm chart values with the CRD schema
Update values/staging.yaml to provide the required resources block and replace gpuCount with the new gpu field.
Before (values/staging.yaml)
aiTrainingJob:
model: "model-x"
dataset: "s3://datasets/x"
gpuCount: 4
After
aiTrainingJob:
model: "model-x"
dataset: "s3://datasets/x"
resources:
cpu: "8"
memory: "32Gi"
gpu: 4
2. Bump the chart’s apiVersion to match the cluster CRD
Modify the template that renders the custom resource to use apiVersion: pipelines.example.com/v1 instead of v1beta2.
Before (template snippet)
apiVersion: pipelines.example.com/v1beta2
kind: AITrainingJob
metadata:
name: {{ .Release.Name }}-{{ .Values.aiTrainingJob.model }}
spec:
{{ toYaml .Values.aiTrainingJob | indent 2 }}
After
apiVersion: pipelines.example.com/v1
kind: AITrainingJob
metadata:
name: {{ .Release.Name }}-{{ .Values.aiTrainingJob.model }}
spec:
{{ toYaml .Values.aiTrainingJob | indent 2 }}
After committing these changes, re‑run the Helm upgrade.
helm upgrade --install ai-pipeline ./charts/ai-pipeline \
--values values/staging.yaml \
--namespace ai-pipeline
The release now succeeds and the AITrainingJob object appears in the cluster.
Verify
Verification steps to ensure the fix works in staging:
- Run a client‑side dry‑run:
kubectl apply -f rendered.yaml --dry-run=client
# No output means validation passed
- Check the actual object:
kubectl get aitrainingjobs pipelines.example.com -n ai-pipeline -o yaml
Expected fields:
spec:
model: "model-x"
dataset: "s3://datasets/x"
resources:
cpu: "8"
memory: "32Gi"
gpu: 4
- Confirm the job pod starts:
kubectl get pods -n ai-pipeline -l app=ai-training-job
All pods should transition to Running and Prometheus should show the ai_training_job_success_total metric increment.
Prevent
Preventative measures to avoid recurrence:
- Schema‑driven CI lint: Add a step that runs
kubectl apply --dry-run=clientagainst the rendered Helm manifest in the CI pipeline. - Helm chart tests: Use
helm testwith a dedicated test chart that includes a minimalAITrainingJobmanifest. - OPA Gatekeeper policy: Enforce that all
AITrainingJobresources containspec.resourcesand do not contain deprecated fields. - Version bump checklist: Whenever the cluster’s CRD version is upgraded, update the chart’s
apiVersionand runhelm templatediff checks. - Documentation sync: Keep the CRD reference documentation (OpenAPI spec) in a version‑controlled file and generate Helm values schema from it (e.g., using
helm-schema-gen).
FAQ
- Why does the validation error appear only in staging and not in dev?
The dev cluster still runs the legacy CRD versionv1beta2, which does not require theresourcesfield. Staging was upgraded tov1, introducing stricter schema enforcement. - Can I disable CRD validation to bypass the error?
You can set--validate=falseonkubectl apply, but this defeats the purpose of schema enforcement and can lead to runtime failures. The proper fix is to align manifests with the CRD. - How do I discover which fields are required by a CRD?
Usekubectl explain aitrainingjob.specor inspect the CRD’s OpenAPI schema viakubectl get crd <name> -o yamland look for therequiredarray underspec.properties. - What if I need to keep the old field name for backward compatibility?
Add anx-kubernetes-preserve-unknown-fields: trueentry for the legacy field in the CRD schema, or implement a conversion webhook that mapsgpuCounttogpuduring admission. - Is there a way to automatically update Helm values when the CRD schema changes?
Yes. Generate a JSON schema from the CRD (e.g.,kubectl get crd <name> -o jsonpath='{.spec.versions[0].schema.openAPIV3Schema}' > schema.json) and feed it tohelm schema-gento produce an up‑to‑datevalues.schema.jsonfile that Helm can validate against.
Related Topic Hub: Distributed Systems Troubleshooting Hub