I recently started a quest to complete CKAD in the next few months, by May 2022. As I’ve explained in that previous essay, “I have spent quite some €€€ enrolling in it and I feel that it can still teach me a lot of relevant concepts about Kubernetes that will be useful” on my day-to-day.
While studying for CKAD, through Kubernetes Certified Application Developer (CKAD) with Tests, I’ve come to realize the importance of understanding the syntax of manifests in Kubernetes. Subconsciously, I obviously already knew this - the same way I know how important it is to dominate the syntax of a given programming language - but it is too easy to fall into a pattern of copy-pasting-and-changing, or simply filling in the gaps in already existing manifests.
Manifests in Kubernetes are the baseline of describing and defining resources, that we can then create and edit afterwards. Manifests represent the object specification describing “its desired state, as well as some basic information about the object (such as a name).” 1 These manifests are most often described in
In essence, there are four essential fields in Kubernetes manifests that must be present in all manifests. These are:
spec. Each of these might have widely varying values populating them.
As an example, a starting point for a Kubernetes manifest would be:
apiVersion: kind: metadata: spec:
apiVersion allows us to define what version of the API a given resource is going to be using. It can be simply
v1, which means it will be part of the core API specified at
/api/v1. It can also be
<name>/<version>, for example
batch/v1, specifying that at resource uses an API that is under
We can find more about what APIs and versions exist on a given cluster by executing
$ kubectl api-versions admissionregistration.k8s.io/v1 admissionregistration.k8s.io/v1beta1 apiextensions.k8s.io/v1 apiextensions.k8s.io/v1beta1 apiregistration.k8s.io/v1 apiregistration.k8s.io/v1beta1 apps/v1 authentication.k8s.io/v1 authentication.k8s.io/v1beta1 (...)
Results will differ from cluster to cluster, and between Kubernetes versions. We can have custom APIs, disabled APIs, or recent APIs could’ve been implemented in different Kubernetes versions.
ReplicaSet are two popular objects that clarify this and differ in the
ReplicaController is a component of the core API in
v1 so we would write
apiVersion: v1 in its spec.
ReplicaSet, which evolved from
ReplicaController, is a component of a more recent API served at
apps/v1. This sort of versioning is incredibly powerful and flexible, allowing Kubernetes to evolve while keeping a lot of backwards compatibility.
kind represents the kind of object that is specified via a manifest. Each kind of resource will be available on a particular API. This makes it essential that the specified
apiVersion match on a specific manifest.
We can inspect which
kind we can use for objects by executing
kubectl api-resources NAME SHORTNAMES APIVERSION NAMESPACED KIND bindings v1 true Binding componentstatuses cs v1 false ComponentStatus configmaps cm v1 true ConfigMap apiservices apiregistration.k8s.io/v1 false APIService controllerrevisions apps/v1 true ControllerRevision daemonsets ds apps/v1 true DaemonSet deployments deploy apps/v1 true Deployment replicasets rs apps/v1 true ReplicaSet statefulsets sts apps/v1 true StatefulSet (...)
kubectl api-resources we can quickly see as well which
apiVersion needs to be specified in order to specify a particular resource.
metadata describes information of an object that allows for the unique identification of that object. When creating a manifest, this field should have at least an associated name. Usually we will also see a field named
spec defines the desired state of the object in Kubernetes. It will vary widely between different resources and API versions, which means that it can be tricky to figure out - or memorize - all the needed fields.
I’ve found that there’s one instance of a resource that can be created without a
spec field which is namespaces. If we create a namespace with only
metadata, creating the namespace with
kubectl create, Kubernetes will accept that manifest but it will create the namespace internally with an appropriate
spec. As an example:
apiVersion: v1 kind: Namespace metadata: name: my-namespace
kubectl create results in:
$ kubectl create -f my-namespace.yaml namespace/my-namespace created
kubectl get will allows us to see the injected
$ kubectl ns my-namespace -o yaml apiVersion: v1 kind: Namespace metadata: name: my-namespace (...) selfLink: /api/v1/namespaces/test uid: f1b901a6-31d6-457a-aaaf-0cb6d600d52c spec: finalizers: - kubernetes status: phase: Active
All of this information can be confirmed in Kubernetes' own documentation by reading Required Fields. Although this isn’t a deep exploration of manifests, having solid bases can be extremely important to understand what has been built on top of this. Reasoning about this structure also provides a glimpse at the baseline that provides so much flexibility to Kubernetes, allowing it to have 50+ components out-of-the-box and a lot of extensibility via custom resources.