Status and Conditions: Explained!

How to interpret the status and conditions fields in Kubernetes resources

Luis Ramirez
Engineer
Cloud native ALL the things!

Published on December 11, 2024


Table of Contents

If you’ve ever used Kubernetes, you may have already introspected the many fields inside a Kubernetes object, usually by getting the object via kubectl get and having the -o yaml flag set. If so, you would have noticed the top-level status field, which is never set by a user. You may have also seen the command kubectl wait --for=condition=Ready=true being used in commands to wait for resources to be in an expected state. What’s the purpose of the status field? Why aren’t any changes in this field persisted when we try to modify them with kubectl edit? And what are conditions? You’re definitely not the only one with these questions, so let’s find out a bit more about the status field and conditions!

What is a status field?

In Kubernetes, all resources are bound to an API, which has fields for the specification of the desired state of the object (the top-level field usually named spec), identifying information of the object (the top-level field named metadata), and the current state of the object (the top-level field named status). The metadata field contains data such as the name, namespace and UID that helps to uniquely identify the object (but is not relevant to the topic of this article). We can see an example of these fields below with our fake object MyObject:

apiVersion: resources.superorbital.com/v1
kind: MyObject
metadata:
  name: my-object
  namespace: testing
  uid: 314e7f00-2694-4f9a-bc08-73aa3104fa8b
spec:
  widgets:
  - "foo"
  - "bar"
status:
  observedWidgets:
  - "baz"

The spec field contains a resource-specific description of the configuration for that object. The information in this field is used by controllers in the cluster to perform operations such as creating and scaling containers, and any other actions required to ensure the object can be put into the desired state.

The status field provides a space for controllers to summarize the current state of the object in the system. This may include (but is not limited to) the current progress of an ongoing action, the success or failure of said action taken by the controller, whether the object is in an expected state or not, the progress towards the expected state, or any other observations made by the controller that may be relevant for the consumer of the object to know about.

One thing to note is that the status field is usually not directly editable by a user via kubectl edit. This is because the status field is only modifiable from a different subresource (/status) from the main object to prevent a situation where an object modification can overwrite the status field unintentionally. This also means that RBAC permissions for the status subresources are provided separately from the permissions for the main object. As an example with the resources.superorbital.com/myobject resource from before:

apiVersion: v1
kind: Role
metadata:
  name: role
rules:
# Permissions for the object
- apiGroups:
  - resources.superorbital.com
  resources:
  - myobject
  verbs:
  - get
  - list
  - patch
  - update
  - watch
# Separate permissions for status fields of the object
- apiGroups:
  - resources.superorbital.com
  resources:
  - myobject/status
  verbs:
  - get
  - patch
  - update

Given that the structure of the status field can differ between different resources, there are no required subfields within the status field. However, it is expected that the status field will hold the values of the observed current state. For many workload-type resources (such as Pods), useful information in the status includes the current container status, the IP address assigned to the container, and when the container was started. Additionally, some resources (such as Pods again) contain a conditions subfield that holds similar information to the ones in the other status subfields. But what exactly is this field?

Conditions

The conditions subfield is a list of Condition elements, each of which provides a standard format to store information about the state of a resource. This information is meant to complement the existing information in the status field and allows consumers of the object to read information about the observed state without having to know the resource-specific status subfields. As an example, a Pod’s conditions subfield contains a type: Ready Condition that is only set to status: "True" when all of the Pod’s containers are running and ready to accept traffic – but the Pod’s status fields also contain a containerStatuses field with a lot more detail about the state of each container.

Conditions will typically follow the standard schema with the following fields:

  • type: The type of condition (in CamelCase)
  • status: The status of the condition – either "True", "False", or "Unknown".
  • observedGeneration: The .metadata.generation value of the object when this condition was observed. This field is optional.
  • lastTransitionTime: The time when a Condition transitioned from one status to another.
  • reason: An identifier (in CamelCase) that provides the reason for the last transition. This value is used for an API to consume.
  • message: A human-readable message with details about the transition.

However, this is not always the case. Some Conditions, such as the PodCondition, will include a lastProbeTime, and others will have a severity field, such as the Cluster API Condition. In general, the fields mentioned are always present, and extra optional ones may be added depending on the needs of the object.

One misconception is that the conditions field represents a chronological record of updates on the object by the controller; however, that is incorrect. Even though conditions is a list of Conditions, it’s actually treated as if it were a map with type being used as the key. More Conditions get appended to this array when their type value differs from all the other ones. This allows a single object to report multiple Conditions at once. As an example:

status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2024-10-23T18:41:59Z"
    status: "True"
    type: PodReadyToStartContainers
  - lastProbeTime: null
    lastTransitionTime: "2024-10-23T18:41:58Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2024-10-23T18:41:59Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2024-10-23T18:41:59Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2024-10-23T18:41:58Z"
    status: "True"
    type: PodScheduled

The Conditions in this Pod aren’t in any particular order but do indicate information about the Pod being scheduled, the containers starting, and eventually the containers running. If we were to watch the conditions field being modified in real-time, each element would start with a status value of "False" or "Unknown", and as time passes and the containers start to run, the appropriate Condition’s status being set to "True" and the lastTransitionTime being set to the timestamp when the Condition transitioned to "True".

This is the exact logic that kubectl wait uses! When you run kubectl wait in the CLI, it retrieves the target resource by its name and reviews the conditions array. It looks for a condition matching the name you have queried and watches for updates to that resource until that status returns "True" or the timeout occurs.

A small history lesson…

Once upon a time, in the ancient times of the year 2015, Pods had a status.phase field where the state of the Pod was reflected as an enum. This made a lot of people very angry and was widely regarded as a bad move since any change to the values of the field would necessitate reinterpreting an existing enum or adding a new one, which is not backwards compatible. An issue (#7856) was created where users discussed the alternatives to phase, and came to the conclusion that conditions would be the successor to phase. However, phase was never actually phased out, as breaking an existing API that was being used proved to be far too difficult. This is why today we still see a phase field in Pods, even though it’s officially been deprecated in favor of Conditions.

Conditions in Kubernetes

Nowadays, even though Conditions were almost deprecated back in 2017, it looks like the field is here to stay. Official documentation recommends that all new resources contain a conditions field in their status field and provide guidance on how to do so. In addition to Pods, Conditions are present in Namespaces, Nodes, PersistentVolumes, PersistentVolumeClaims, and Services for the core APIs. All resources in the batch (e.g. Jobs) API implement Conditions, as well as the autoscaler resources such as HorizontalPodAutoscaler objects. Unfortunately, not all core resources in Kubernetes use Conditions. All apps API resources (Deployment, StatefulSet, DaemonSet…) define Conditions, and the ReplicaSet and the Deployment resources actually implement it, but StatefulSet and DaemonSet do not attempt to populate the conditions field. This is evident when comparing the logic for kubectl rollout status, which is a command that waits for Deployments, StatefulSets and DaemonSets to be in a Ready state after a rollout: the logic for checking if a Deployment is rolled out does include a substep where it checks the Condition on the object, but none of that is found for StatefulSets or DaemonSets.

Best Practices for Conditions in Custom Resources

If you’re building a custom resource, you will invariably need to implement a status field. This means that a conditions field will almost surely follow. If so:

  1. Implement a Condition of type Ready for long-running execution objects (think of Pods and Services), and a type Succeeded for bounded-execution objects (e.g. Jobs). Strive to always have an all-encompassing summary Condition for quickly assessing if the object is in a good state.
  2. When a Condition’s "True" status represents normal operations, it is referred to as a “positive-polarity” condition, whereas Conditions where "False" represents this state are “negative-polarity”. Standardize all your Conditions to use the same polarity to represent normal operations. This will help avoid confusion when scanning the conditions list and seeing a mix of "True" and "False" status values during normal conditions.
  3. Condition type names should always describe the current state of the observed object, never a transition phase. Think of ScaledOut as opposed to Scaling – the former can be set to "True" when successful, "False" when failed, and Unknown when the process is still ongoing.

Takeaways

The ubiquitous presence of Conditions in Kubernetes resources has an interesting history and represents the desire of the Kubernetes architects to provide a declarative, level-based, and observation-driven design. Even though the API still contains artifacts from past decisions (I’m looking at you, phase), Conditions signify an important step forward in standardizing the visualization of the observed state.

Subscribe (yes, we still ❤️ RSS) or join our mailing list below to read more blog posts like this one!

Luis Ramirez
Engineer
Cloud native ALL the things!