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:
- Implement a Condition of type
Ready
for long-running execution objects (think of Pods and Services), and a typeSucceeded
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. - 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 theconditions
list and seeing a mix of"True"
and"False"
status values during normal conditions. - Condition type names should always describe the current state of the observed object, never a transition phase. Think of
ScaledOut
as opposed toScaling
– the former can be set to"True"
when successful,"False"
when failed, andUnknown
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!