Skip to main content
The web app “spec” declares what a user wants. The “status” reports what the cluster actually has. These are related but distinct concepts — keeping them separate is critical for reliable operators, clear troubleshooting, and automation.
The image displays two distinct concepts that need to remain separate: "spec," which represents "What you want" or the desired state, and "status," which signifies "What you have" or the observed state.
For example: if a user requests three replicas, that belongs in spec. If only two Pods are ready right now, that belongs in status. Without exposing status, a custom resource can look successful while hiding important details. A WebApp object might exist and the controller might have created a Deployment, yet the Pods could still be pulling an image, a Pod could be crashing, or the Deployment might be pending due to cluster capacity.
The image illustrates a process flow with three stages: "WebApp object exists," "Deployment exists," and "Pods pulling or crashing," highlighting that success may appear hidden without status indicators.
If the WebApp resource does not report what the controller observed, every user must chase child resources and logs to answer a basic question: Is this thing ready? Example troubleshooting steps users or automation would otherwise need to run:
Kubernetes already gives a pattern for exposing observed state: built-in resources expose observed fields directly on the object. For example, a Deployment includes status.availableReplicas and status.conditions so users inspect the Deployment object when they care about the workload. Example Deployment status:
Your WebApp custom resource should provide the same contract: it is not only an input for a controller; it is the public status page for the abstraction you created. Example WebApp status:
That stable surface is important for automation as well as humans. A human can dig through kubectl get deploy, kubectl describe pod, and controller logs. Another controller, a GitOps tool, or an alerting rule needs a stable API field — such as status.readyReplicas or a Ready condition — to make decisions without understanding every child object your operator manages.
The image is a flowchart illustrating the concept "Automation Needs One Stable Field," showing steps from using tools like Another controller, GitOps tool, and Alerting rule, to making a decision based on a stable field.
The first status value we often expose is deliberately simple: ready replicas. The controller will inspect the child Deployment, read how many replicas are available, and copy that observed number onto the WebApp status. This enforces the core rule: status is derived from the real cluster state. Do not guess it or copy it from spec unless the cluster actually matches the requested state.
The image illustrates a process flow about how ready replicas are managed, starting with the controller reading child deployments, deployments reporting replicas, a web app copying the observed value, and advising not to guess readiness from specifications.
Kubernetes enforces the spec/status split mechanically via the status subresource. A spec update changes the desired state; a status update reports what the controller observed. Kubernetes exposes these as separate update paths so a user changing spec and the controller updating status do not overwrite each other. In controller code you should use the status client to write status:
Status should also be rebuildable. If the status block disappeared and the controller reconciled again, it must be able to inspect the cluster and write the same answer. This keeps the operator honest: the source of truth is spec plus the real child resources, not some ephemeral controller memory.
The image is a flowchart titled "Status Should Be Rebuildable," outlining a process for status rebuilding: status disappears, re-reading specifications and child resources, writing the same answer back, and highlighting that it's not controller memory.
Status must be derivable from cluster state alone. Avoid storing ephemeral controller memory in status. If a controller restarts, it should be able to recompute status from the child resources and spec.
Once you expose a basic observed number, give that number clearer meaning with conditions. Conditions are the standard way to communicate whether a resource is Ready, Progressing, or Blocked. A condition contains structured fields that explain why the resource is in a given state and when it transitioned. Example status with a condition:
Common condition fields and purposes: When the controller observes cluster state, set an appropriate condition and update status atomically via the status subresource. In Go, using metav1.Condition:
(You can use any condition helper you prefer; the important part is computing reproducible conditions from observed state and writing them via the status client.) Summary
  • Export observed state on the custom resource so humans and automation can rely on a single API surface.
  • Update status via the status subresource to avoid conflicting writes with user-driven spec changes.
  • Make status rebuildable from spec and child resources — the controller must be able to recompute it after restart.
  • Use conditions to give numeric status fields clear, machine- and human-readable semantics.
Links and references

Watch Video