Hugo Martins

Essential Fields in Kubernetes Manifests

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 and KodeKloud, 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 .yaml files.

In essence, there are four essential fields in Kubernetes manifests that must be present in all manifests. These are: apiVersion, kind, metadata and 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

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 /api/<name>. 2

We can find more about what APIs and versions exist on a given cluster by executing kubectl api-version:

$ 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.

ReplicationController and ReplicaSet are two popular objects that clarify this and differ in the apiVersion. 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

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 kind and apiVersion match on a specific manifest.

We can inspect which kind we can use for objects by executing kubectl api-resources:

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

(...)

With kubectl api-resources we can quickly see as well which apiVersion needs to be specified in order to specify a particular resource.

metadata

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 labels.

spec

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 apiVersion, kind and 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

Running kubectl create results in:

$ kubectl create -f my-namespace.yaml
namespace/my-namespace created

Executing kubectl get will allows us to see the injected spec field:

$ 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.