Kubernetes
Custom Resource
Nested Field
Default Value
Troubleshooting

Missing default value in nested field of kubernetes custom resource

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Kubernetes CRDs support schema-based default values, but nested fields often surprise people. A default on a child field is only applied when the parent object exists in the submitted resource, so a missing parent usually means the nested default never runs.

Why nested defaults appear to be missing

CRD defaulting is driven by the OpenAPI schema on the API server. When a request creates or updates a custom resource, Kubernetes walks the object that was actually sent and applies defaults where the schema says a value is missing.

The important detail is that defaulting does not invent arbitrary parent objects just because a grandchild has a default. If spec.settings is absent, Kubernetes cannot descend into that branch and apply a default to spec.settings.mode.

Consider this CRD fragment:

yaml
1openAPIV3Schema:
2  type: object
3  properties:
4    spec:
5      type: object
6      properties:
7        settings:
8          type: object
9          properties:
10            mode:
11              type: string
12              default: safe

And this custom resource:

yaml
1apiVersion: apps.example.com/v1
2kind: CustomApp
3metadata:
4  name: demo
5spec: {}

The resulting object still has no spec.settings.mode, because settings itself was never present.

How to make nested defaults work

There are three common fixes, and the right one depends on how much control you want in the schema.

1. Default the parent object too

If the parent can safely exist as an empty object, give it a default of object.

yaml
1openAPIV3Schema:
2  type: object
3  properties:
4    spec:
5      type: object
6      properties:
7        settings:
8          type: object
9          default: {}
10          properties:
11            mode:
12              type: string
13              default: safe

Now a submitted resource with an empty spec allows Kubernetes to materialize settings, and then the nested mode default can apply.

2. Default the whole nested structure explicitly

If you already know the desired shape, default the entire branch in one place.

yaml
1openAPIV3Schema:
2  type: object
3  properties:
4    spec:
5      type: object
6      properties:
7        settings:
8          type: object
9          default:
10            mode: safe
11          properties:
12            mode:
13              type: string

This is often easier to reason about because you are defining the final structure directly instead of depending on multiple defaulting steps.

3. Use an admission webhook for complex logic

Schema defaults are static. If the default depends on another field, cluster state, or versioned business logic, use a mutating admission webhook instead.

go
1func (w *CustomAppWebhook) Default(ctx context.Context, obj runtime.Object) error {
2    app := obj.(*appsv1.CustomApp)
3
4    if app.Spec.Settings == nil {
5        app.Spec.Settings = &appsv1.Settings{}
6    }
7
8    if app.Spec.Settings.Mode == "" {
9        app.Spec.Settings.Mode = "safe"
10    }
11
12    return nil
13}

A webhook adds operational overhead, but it gives full control and keeps defaults consistent even when the rules become more involved.

How to verify the behavior

After applying the CRD, create a minimal custom resource and inspect the stored object:

bash
kubectl apply -f customapp.yaml
kubectl get customapp demo -o yaml

If the default was applied, you should see the populated field in the server response. If not, inspect the CRD schema itself:

bash
kubectl get crd customapps.apps.example.com -o yaml

This helps catch indentation mistakes, wrong property paths, or defaults placed under the wrong version block.

Common Pitfalls

  • Expecting a nested child default to apply when the parent object is completely missing.
  • Adding defaults to CRD schemas on an older cluster version that does not support the behavior you expect.
  • Forgetting that defaults are server-side. Client-side YAML validation will not show the final stored object.
  • Using schema defaults for logic that depends on external state. That is a webhook problem, not a schema problem.
  • Defining incompatible defaults that violate the schema, such as a string default for a field declared as an integer.

Summary

  • CRD defaults are applied by the API server from the OpenAPI schema.
  • A nested default only works when Kubernetes can reach that branch in the submitted object.
  • If the parent object is missing, child defaults are usually skipped.
  • Defaulting the parent object or the whole nested structure is the simplest fix.
  • Use a mutating webhook when defaults depend on custom logic rather than static schema values.

Course illustration
Course illustration

All Rights Reserved.