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:
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:
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:
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."
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:
apiVersionandkindsay what type of object this is. Pods arev1; Deployments areapps/v1. You don't need to memorize these: kubectl tells you (see below).metadatanames the object and gives it labels.specis the desired state, the part you write and change.statusis 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:
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:
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:
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:
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:
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::
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
kind delete cluster --name learn
Check yourself
Why does a Deployment file have two spec: sections?
kubectl rejects your file with an error about an unknown field. What should you check first?
Which of the four parts of a Kubernetes object do you never write yourself?
An environment variable written as value: 8080 is rejected. What's the fix?
What's the fastest way to get a correct Deployment file to start from?
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
- Objects in Kubernetes (opens in a new tab) in the official docs explains spec and status in more depth.
- The kubectl reference (opens in a new tab) covers every command, including
explainand--dry-run.