Skip to main content
Previously we had a working WebApp custom resource: CRD, schema, validation, and a controller. Functionally it worked, but it didn’t behave like a first-class Kubernetes resource:
  • kubectl get wa showed only NAME and AGE.
  • kubectl scale wa failed.
  • A regular kubectl apply could overwrite .status because the API server wasn’t preventing writes to it.
Below is the minimal CRD we started from:
In this guide we add four CRD fields that make your WebApp resource behave like a built-in workload:
  • Enable the status subresource so only the /status endpoint (typically controllers) can update .status.
  • Enable the scale subresource so kubectl scale and HPAs can target the CR.
  • Add additionalPrinterColumns so kubectl get wa shows meaningful columns.
  • Add categories: [all] so tools that honor categories treat your CR as a top-level workload.
No controller code changes or rebuilds are required — the API server enforces these behaviors based purely on the CRD.

Quick overview: What each CRD field does


Step 1 — Teach the schema about .status and tighten .spec validation

Add a status object to the OpenAPI schema so the API server does not prune it. Also make spec validation explicit (required fields, types and ranges):
This schema change ensures:
  • The API server preserves .status (it won’t be removed by pruning).
  • Clients get clear validation errors for missing or invalid spec fields.

Step 2 — Enable /status and add the scale subresource

Turn on the /status endpoint by adding an empty status: {} under subresources. Add the scale block and point it to the JSONPaths for desired and observed replicas and for the label selector:
With this in place:
  • kubectl scale wa <name> --replicas=<n> is routed to the scale subresource and updates .spec.replicas.
  • HorizontalPodAutoscalers (HPAs) that target this resource can read replicas and update the scale endpoint.
Enabling the status subresource prevents ordinary apply/update requests from modifying .status. Only the /status endpoint (used by controllers) may change status fields.

Step 3 — Add additional printer columns for kubectl get

Define columns so kubectl get wa displays useful information. Each column needs name, type, and a jsonPath. Use priority: 1 to hide less important columns unless -o wide is requested:
These columns make kubectl get wa feel like a native resource listing (Image, Replicas, Ready, Port, Age).

Step 4 — Add categories: [all]

Add categories: [all] under spec.names so tools that respect categories (and kubectl api-resources --categories=all) include the resource among top-level workload types:
Note: kubectl get all is a hard-coded convenience command and will not list CRDs even if they declare categories: [all]. The category is still useful for other tooling and kubectl api-resources --categories=all.
kubectl get all will not list your CRs even if categories: [all] is set. Use kubectl api-resources --categories=all or kubectl get webapps / kubectl get wa to view your resources.

Final consolidated CRD (relevant fragments)

Below is a consolidated view showing the key fields you should include. Fill in the remaining CRD metadata as appropriate for your project.

Apply the CRD

No API server restart is required — changes to CRDs are picked up immediately.
After applying:
  • kubectl get wa will show the extra columns you defined. (The Image column may be hidden unless you use -o wide because it has priority: 1.)
  • kubectl scale wa <name> --replicas=<n> updates .spec.replicas via the scale subresource.
  • Regular kubectl apply / kubectl update attempts that include .status will see .status fields dropped — only /status updates persist status.

Validate category registration

Summary

By adding these CRD fields:
  • schema for status and spec validation
  • subresources.status
  • subresources.scale
  • additionalPrinterColumns
  • names.categories: [all]
you make your custom resource behave like a first-class Kubernetes workload. All of the enforcement is handled by the API server from the CRD itself — you do not need to change or rebuild the controller. References:
  • Kubebuilder can generate these CRD fields automatically from Go marker comments on your types.

Watch Video

Practice Lab