Kubernetes basics · Lesson 4 of 12 · about 25 minutes

Reading YAML without fear

Every Kubernetes file follows the same shape. Learn the three YAML rules and the four parts of every object, and any manifest becomes readable.

By the end you'll know

  • The three YAML rules that cover almost every Kubernetes file
  • The four parts every Kubernetes object has, and which one you never write
  • How to read a Deployment line by line, including the part that trips everyone up
  • How to get kubectl to write, explain and check YAML for you

Why YAML

So far you've copied files like pod.yaml and trusted them. That works until something breaks, or you need to change one. Kubernetes describes everything in YAML (YAML Ain't Markup Language), a format for writing structured data that people can read. The good news: Kubernetes only uses a small part of it.

The three rules

1. key: value pairs. A name, a colon, a space, a value:

YAML
name: hello
replicas: 2

2. Indentation means "belongs to". Lines indented under a key belong to it. Use spaces, never tabs. Two spaces per level is the convention:

YAML
metadata:          # metadata contains...
  name: hello      # ...a name
  labels:          # ...and labels, which contain...
    app: hello     # ...this label

3. A dash means "one item in a list". Each dash starts a new item:

YAML
containers:
  - name: web          # first item in the list
    image: nginx:1.27  # still the first item (lined up with "name")
  - name: logs         # second item
    image: busybox:1.36

That last pattern, a list where each item has several keys, is the one people find hardest. Read it as: "containers is a list; each dash is one container; the lines lined up under it describe that container."

Think of an outline. Indentation is like the levels in a document outline: everything indented under a heading belongs to it. A dash is a bullet point. That's genuinely most of it.

Two more things you'll see:

  • Comments start with #. Kubernetes ignores them.
  • Three dashes (---) on their own line separate several objects in one file. You'll often see a Deployment and a Service together, split by ---.

The four parts of every object

Every Kubernetes object, whether a Pod, a Deployment or anything else, has the same four top-level parts:

The four parts of every Kubernetes objectapiVersion and kind say what type of object this is. metadata gives it a name and labels. spec is the desired state you write. status is the actual state, which Kubernetes writes and keeps updating. Controllers compare spec with status and work to make them match.apiVersion: apps/v1kind: DeploymentWhat it isthe type of objectmetadata:name: helloWho it isits name and labelsspec:replicas: 2What you wantdesired state: you write thisstatus:readyReplicas: 2What actually isKubernetes writes thisControllers compare the twoand act until status matches specYou write the first three parts. Kubernetes fills in status, and keeps it up to date.
spec and status are Lesson 1's idea in file form: desired state on one side, actual state on the other, and a controller closing the gap.
  • apiVersion and kind say what type of object this is. Pods are v1; Deployments are apps/v1. You don't need to memorize these: kubectl tells you (see below).
  • metadata names the object and gives it labels.
  • spec is the desired state, the part you write and change.
  • status is the actual state. You never write it. Kubernetes fills it in and keeps updating it.

See it for yourself. With a cluster running (kind create cluster --name learn from Lesson 2) and the hello pod from Lesson 3 applied:

BASH
kubectl get pod hello -o yaml

Your short file comes back much longer. Kubernetes has filled in defaults you didn't set, and at the bottom there's a status: section with the pod's IP address, its conditions and when each container started.

Reading a Deployment, line by line

Here's the Deployment from Lesson 1 again, with every line explained:

YAML
apiVersion: apps/v1        # Deployments live in the "apps" group
kind: Deployment
metadata:
  name: hello              # the Deployment's own name
spec:
  replicas: 2              # desired state: keep 2 pods running
  selector:                # how the Deployment finds the pods it owns...
    matchLabels:
      app: hello           # ...any pod labeled app: hello
  template:                # the blueprint for each pod it creates
    metadata:
      labels:
        app: hello         # each new pod gets this label
    spec:                  # a pod spec, the same shape as Lesson 3
      containers:
        - name: hello
          image: nginx:1.27

Two things to notice:

There's a pod inside. Everything under template: is a pod definition, with its own metadata and spec. Compare it with pod.yaml from Lesson 3: it's the same shape, one level deeper. That's why a Deployment has two spec: lines. The top one describes the Deployment; the inner one describes each pod.

The selector and the labels must match. The Deployment finds its pods by label. selector.matchLabels says "my pods are labeled app: hello", and template.metadata.labels gives every new pod that label. If they don't match, Kubernetes refuses the file, because the Deployment could never find the pods it creates. This is the single most common Deployment mistake.

Let kubectl do the work

You don't have to write YAML from memory. Three commands save a lot of typing and guessing.

Generate a starting file:

BASH
kubectl create deployment hello --image=nginx:1.27 --replicas=2 --dry-run=client -o yaml > deploy.yaml

--dry-run=client means "don't create anything", and -o yaml means "show me the YAML instead". You get a correct file to edit, including a matching selector and labels.

Look up any field:

BASH
kubectl explain deployment.spec.replicas
kubectl explain pod.spec.containers

kubectl explain describes any field, its type and what it does, straight from your cluster, so it always matches your version of Kubernetes.

Check a file before applying it:

BASH
kubectl apply -f deploy.yaml --dry-run=server

The API server checks the file fully, including the fields and the selector rules, but saves nothing. If it prints deployment.apps/hello created (server dry run), the real apply will work.

Exam tip

In hands-on exams like the CKA (Certified Kubernetes Administrator), typing YAML from scratch is the slowest way to work. Generating it with --dry-run=client -o yaml, then editing, is faster and avoids typos. kubectl explain is often quicker than searching the docs.

Break it on purpose

YAML errors look scary but come in a few familiar shapes. Try each one with kubectl apply -f on a copy of pod.yaml.

1. A tab instead of spaces. Replace the spaces before name: hello with a tab. You'll get an error along the lines of found character that cannot start any token, with a line number. YAML doesn't allow tabs for indentation. Most editors can show tabs and convert them to spaces.

2. Indented at the wrong level. Move image: nginx:1.27 left so it lines up with containers: instead of name:. Kubernetes now reads image as a field of the pod spec, not of the container, and rejects it with an error mentioning an unknown field. When you see "unknown field", check the indentation of that line first.

3. A number where text is expected. Add an environment variable to the container, lined up with image::

YAML
      env:
        - name: PORT
          value: 8080

This fails with an error ending in something like cannot unmarshal number ... of type string. Environment variable values must be text, and YAML read 8080 as a number. The fix is quotes: value: "8080". The same trap catches values like true, which YAML reads as true/false instead of text. When in doubt, quote it.

In my real cluster

Every change to my production cluster is a YAML edit in Git. To free up memory, I turned off Grafana by adding one line, replicas: 0, nested under grafana: in a much larger file. Argo CD noticed the change in Git and applied it within minutes. Reading the nesting is what tells you that the line changes Grafana, and not something else in the same file.

Clean up

BASH
kind delete cluster --name learn

Check yourself

  1. Why does a Deployment file have two spec: sections?

  2. kubectl rejects your file with an error about an unknown field. What should you check first?

  3. Which of the four parts of a Kubernetes object do you never write yourself?

  4. An environment variable written as value: 8080 is rejected. What's the fix?

  5. What's the fastest way to get a correct Deployment file to start from?

  6. Which command tells you what a field like pod.spec.containers.ports means?

Key terms

  • YAML: the text format Kubernetes files are written in. Indentation shows structure.
  • Manifest: a YAML file describing one or more Kubernetes objects.
  • spec: the desired state you write.
  • status: the actual state, written and updated by Kubernetes.
  • Pod template: the pod blueprint inside a Deployment, under spec.template.
  • Dry run: checking or generating a change without actually making it.

Go deeper