Chapter 59: Kubernetes Pods in Depth: Architecture, YAML Anatomy and Pod Types
59.2 YAML file anatomy: read any Kubernetes file line by line
Why: the problem
Suppose you join the Infosys platform team on Monday. Your lead sends a 200-line YAML file and says: please add a memory limit and a new label. You open it and see apiVersion, kind, metadata, spec, template, selector, matchLabels. Where does the limit go? Which label is safe to change? One wrong indent and kubectl says error. One wrong label and the Deployment loses its pods. Good news: every Kubernetes file follows the same four-part shape. Once you see the shape, any file becomes easy. In this lesson we read YAML like a map, use kubectl explain as a built-in dictionary, and catch mistakes before they reach the cluster.
What: every term in plain English
- YAML — A text format for data. It uses indentation (spaces, never tabs) to show what belongs inside what.
- Key: value — One setting, for example replicas: 3.
- List item (-) — A dash starts one item of a list, for example one container.
- apiVersion — Which API group and version handles this object, like v1 or apps/v1.
- kind — The type of object: Pod, Deployment, Service, ConfigMap and so on.
- metadata — The identity: name, namespace, labels and annotations.
- spec — What you want. You write it.
- status — What is really happening. Kubernetes writes it. Never put it in your file.
- Label — A key-value tag used to select objects, for example app: cart.
- Selector — A query that picks objects by labels.
- Annotation — A key-value note for tools and humans. It is not used for selecting.
- kubectl explain — Prints the official documentation for any field, straight from the cluster.
- Dry run — Checks a file without saving it. Client dry run checks locally; server dry run asks the API server.
How: step by step
- Find the four parts. apiVersion, kind, metadata, spec. Every object has them.
- Find the API group. Run kubectl api-resources to see the group and version for each kind.
- Look up a field. kubectl explain deployment.spec.template.spec.containers.resources.
- Generate a skeleton. kubectl create deployment cart --image=nginx --dry-run=client -o yaml.
- Edit with care. Two spaces per level. Lists start with a dash. Never tabs.
- Validate. kubectl apply --dry-run=server -f file.yaml, then kubectl diff -f file.yaml.
- Apply and compare. kubectl apply, then kubectl get -o yaml to see the status Kubernetes added.
Example 1: Read a full Deployment and Service file part by part
Goal. Read a full Deployment and Service file part by part.
cart.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cart
namespace: shop
labels:
app: cart
team: checkout
annotations:
owner: "checkout-team@example.com"
spec:
replicas: 2
selector:
matchLabels:
app: cart
template:
metadata:
labels:
app: cart
version: "1.0"
spec:
containers:
- name: cart
image: nginx:1.27
ports:
- containerPort: 80
name: http
env:
- name: CURRENCY
value: "INR"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: cart
namespace: shop
spec:
selector:
app: cart
ports:
- port: 80
targetPort: http
What each line does
apiVersion: apps/v1— Deployment is in the apps group. Pods and Services are in the core group, written just v1.metadata.namespace: shop— The object lives in the shop namespace. The namespace must exist first.metadata.labels— Labels on the Deployment itself. Useful for kubectl get -l team=checkout.annotations.owner— A note. Tools and people read it; selectors ignore it.spec.selector.matchLabels— Which pods this Deployment owns. It must match the template labels. It cannot be changed later.template.metadata.labels— Labels stamped on each pod. version can change; app must stay, or the selector breaks.containers: - name: cart— A list. The dash starts the first container.ports.name: http— A named port. The Service uses targetPort: http, so the number can change in one place.value: "INR"— Env values must be strings. Quote numbers and yes/no values.requests / limits— requests help the scheduler; the memory limit is the hard ceiling.---— Separates two objects in one file.Service spec.selector— Sends traffic to pods with app: cart. Same label, different object.
Green output
$ kubectl create namespace shop
namespace/shop created
$ kubectl apply -f cart.yaml
deployment.apps/cart created
service/cart created
$ kubectl get deploy,svc,pods -n shop --show-labels
NAME READY UP-TO-DATE AVAILABLE LABELS
deployment.apps/cart 2/2 2 2 app=cart,team=checkout
NAME TYPE CLUSTER-IP PORT(S) LABELS
service/cart ClusterIP 10.96.142.17 80/TCP none
NAME READY STATUS LABELS
pod/cart-6f7d9c8b4-2lq8x 1/1 Running app=cart,pod-template-hash=6f7d9c8b4,version=1.0
pod/cart-6f7d9c8b4-wn5zk 1/1 Running app=cart,pod-template-hash=6f7d9c8b4,version=1.0
Expected result. Two objects from one file. Pods got the template labels plus pod-template-hash added by the ReplicaSet.
Example 2: Use kubectl explain and api-resources as your dictionary
Goal. Use kubectl explain and api-resources as your dictionary.
explain.sh
kubectl api-resources | grep -E 'NAME|^pods|^deployments|^services|^cronjobs|^ingresses'
kubectl explain deployment
kubectl explain deployment.spec.strategy
kubectl explain pod.spec.containers.resources --recursive
kubectl explain pod.spec.containers.livenessProbe.httpGet
What each line does
api-resources— Lists every kind, its short name, its API group and whether it is namespaced.explain deployment— Shows the group, version and top fields with a short description.explain ... .strategy— Goes deeper, one dot per level, like a path in the YAML.--recursive— Shows the whole tree under a field, with types.Why not Google?— explain matches the exact version of your cluster. Official docs are at kubernetes.io/docs/reference.
Green output
$ kubectl api-resources | grep -E 'NAME|^pods|^deployments|^services|^cronjobs|^ingresses'
NAME SHORTNAMES APIVERSION NAMESPACED KIND
pods po v1 true Pod
services svc v1 true Service
cronjobs cj batch/v1 true CronJob
deployments deploy apps/v1 true Deployment
ingresses ing networking.k8s.io/v1 true Ingress
$ kubectl explain deployment.spec.strategy
GROUP: apps
KIND: Deployment
VERSION: v1
FIELD: strategy (DeploymentStrategy)
DESCRIPTION:
The deployment strategy to use to replace existing pods with new ones.
FIELDS:
rollingUpdate (RollingUpdateDeployment)
type (string)
Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate.
$ kubectl explain pod.spec.containers.resources --recursive
FIELDS:
claims ([]ResourceClaim)
name (string) -required-
limits (map[string]Quantity)
requests (map[string]Quantity)
Expected result. You can find any field and its type without leaving the terminal.
Example 3: Generate, validate and diff before you apply
Goal. Generate, validate and diff before you apply.
safe-edit.sh
kubectl create deployment report --image=python:3.12-slim -n shop \
--dry-run=client -o yaml > report.yaml
kubectl apply --dry-run=server -f report.yaml
kubectl apply -f cart.yaml
sed -i 's/replicas: 2/replicas: 3/' cart.yaml
kubectl diff -f cart.yaml
kubectl apply -f cart.yaml
kubectl get deploy cart -n shop -o yaml | sed -n '/^status:/,$p'
What each line does
--dry-run=client -o yaml— Creates a correct skeleton file without touching the cluster.--dry-run=server— The API server fully validates the file, including defaults and admission rules, but saves nothing.kubectl diff— Shows exactly what will change in the cluster, like git diff.sed -n '/^status:/,$p'— Prints only the status part that Kubernetes wrote.
Green output
$ kubectl apply --dry-run=server -f report.yaml
deployment.apps/report created (server dry run)
$ kubectl diff -f cart.yaml
- replicas: 2
+ replicas: 3
- generation: 1
+ generation: 2
$ kubectl apply -f cart.yaml
deployment.apps/cart configured
service/cart unchanged
status:
availableReplicas: 3
observedGeneration: 2
readyReplicas: 3
replicas: 3
updatedReplicas: 3
Expected result. You changed one value safely, saw the diff first, and confirmed status matches spec.
Every command, step by step (kind lab with kubectl)
-
Create the namespace
kubectl create namespace shopWhat it does: Namespaces group objects. Our files use shop.
namespace/shop created -
Apply the two-object file
kubectl apply -f cart.yamlWhat it does: Creates the Deployment and the Service.
deployment.apps/cart created service/cart created -
Find objects by label
kubectl get all -n shop -l app=cartWhat it does: Labels are how you search.
pod/cart-6f7d9c8b4-2lq8x 1/1 Running service/cart ClusterIP 10.96.142.17 deployment.apps/cart 2/2 -
See the Endpoints the selector found
kubectl get endpointslices -n shop -l kubernetes.io/service-name=cartWhat it does: The Service selector found the two pod IPs.
NAME ADDRESSTYPE PORTS ENDPOINTS cart-x7k2p IPv4 80 10.244.1.6,10.244.2.7 -
Change a pod label by hand
kubectl label pod -n shop -l app=cart app=broken --overwrite kubectl get pods -n shop --show-labelsWhat it does: Pods with app=broken no longer match, so the ReplicaSet makes two new ones. The old ones become orphans.
cart-6f7d9c8b4-2lq8x 1/1 Running app=broken cart-6f7d9c8b4-wn5zk 1/1 Running app=broken cart-6f7d9c8b4-9fz3m 1/1 Running app=cart cart-6f7d9c8b4-qk7rd 1/1 Running app=cart -
Clean orphans
kubectl delete pod -n shop -l app=brokenWhat it does: Labels are powerful. Change them with care.
pod "cart-6f7d9c8b4-2lq8x" deleted pod "cart-6f7d9c8b4-wn5zk" deleted -
Read the full live object
kubectl get deploy cart -n shop -o yaml | head -30What it does: You see your fields plus defaults Kubernetes filled in, like strategy and revisionHistoryLimit.
progressDeadlineSeconds: 600 replicas: 3 revisionHistoryLimit: 10 strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate
When it is red 1
error: error parsing cart.yaml: error converting YAML to JSON: yaml: line 21: found character that cannot start any token
Cause. A tab character, or a wrong indent, on line 21.
Fix, step by step
- Open the file and go to line 21.
- Replace tabs with spaces. In vim use :set expandtab then :retab.
- Keep two spaces per level.
- Run kubectl apply --dry-run=client -f cart.yaml again.
When it is red 2
The Deployment "cart" is invalid: spec.template.metadata.labels: Invalid value: map[string]string{"app":"kart"}: `selector` does not match template `labels`
Cause. The template labels do not match spec.selector.matchLabels.
Fix, step by step
- Make the template label app: cart, the same as the selector.
- Never change the selector of an existing Deployment; delete and recreate if you must.
- Validate with --dry-run=server.
When it is red 3
json: cannot unmarshal number into Go struct field EnvVar.spec.template.spec.containers.env.value of type string
Cause. An env value is a number without quotes, like value: 8080.
Fix, step by step
- Write value: "8080" with quotes.
- Also quote true, false, yes and no.
- Apply again.
When it is red 4
error: resource mapping not found for name: "cart" namespace: "" from "cart.yaml": no matches for kind "Deployment" in version "extensions/v1beta1"
Cause. An old apiVersion that was removed from Kubernetes.
Fix, step by step
- Run kubectl api-resources | grep deployments to see the right version.
- Change apiVersion to apps/v1.
- Add spec.selector, which apps/v1 requires.
Practice tasks
Easy Task 1: Four parts
Why: Shape first.
What to do: Mark apiVersion, kind, metadata, spec in cart.yaml.
- Open the file.
- Mark each part.
Solution
apiVersion / kind / metadata / specEasy Task 2: Skeleton
Why: Never type from zero.
What to do: Generate a Deployment YAML for redis:7.
- Use --dry-run=client -o yaml.
Solution
kubectl create deployment cache --image=redis:7 --dry-run=client -o yamlEasy Task 3: Short names
Why: Speed.
What to do: Find the short names of 5 kinds.
- Run api-resources.
Solution
kubectl api-resourcesMedium Task 4: Explain
Why: Self-help.
What to do: Find what maxSurge means.
- Use kubectl explain.
Solution
kubectl explain deployment.spec.strategy.rollingUpdate.maxSurgeMedium Task 5: Server dry run
Why: Catch errors early.
What to do: Break a label and catch it with server dry run.
- Change template label.
- Run dry run.
Solution
kubectl apply --dry-run=server -f cart.yamlMedium Task 6: Diff
Why: Safe change.
What to do: Add a memory request change and view the diff.
- Edit the file.
- kubectl diff.
Solution
kubectl diff -f cart.yamlMedium Task 7: Label query
Why: Searching.
What to do: List all objects with team=checkout in shop.
- Use -l.
Solution
kubectl get all -n shop -l team=checkoutMedium Task 8: Annotate
Why: Notes.
What to do: Add an annotation change-cause to the Deployment.
- kubectl annotate.
Solution
kubectl annotate deploy cart -n shop kubernetes.io/change-cause="replicas 3"Hard Task 9: Orphans
Why: Selector power.
What to do: Relabel one pod and explain the new pod count.
- kubectl label --overwrite.
- Count pods.
Solution
kubectl label pod POD -n shop app=x --overwrite
kubectl get pods -n shopHard Task 10: Live vs file
Why: Defaults.
What to do: List three fields in the live object that are not in your file.
- get -o yaml.
- Compare.
Solution
kubectl get deploy cart -n shop -o yamlHomework: try at home
Take any YAML from chapter 18. Write the four parts in your notebook and, for every line, say whether it is identity, wanted state or a pod blueprint.
Recap
- Every object has apiVersion, kind, metadata and spec; Kubernetes adds status.
- Indent with two spaces, never tabs; dashes start list items.
- The selector must match the template labels.
- kubectl explain and api-resources are your offline dictionary.
- Use dry-run=server and diff before apply.
- Chapter 18.7 shows a Deployment file; this lesson explains every line of any file.
FAQ
Q. Can I use JSON instead of YAML?
Yes, the API accepts both. YAML is easier to read and comment.
Q. Why does my file have status after kubectl get -o yaml?
That is the live object. Do not copy status back into your file.
Q. How many spaces should I use?
Two per level is the common style. The number must just be the same everywhere.
Q. What is the difference between apply and create?
create fails if the object exists. apply creates or updates, and remembers what you applied.
Q. Where are the official docs?
kubernetes.io/docs/reference/kubernetes-api lists every kind and field.
Q. How do I check many files in CI?
Run kubectl apply --dry-run=server in a test cluster, or a schema tool like kubeconform.
Interview questions
-
Explain the structure of a Kubernetes manifest.
Model answer: apiVersion and kind say which API handles the object, metadata gives identity and labels, spec is the wanted state, and status is filled by controllers.
-
Difference between labels and annotations?
Model answer: Labels are short identifying tags used by selectors; annotations hold non-identifying data for tools, like change-cause or ingress settings.
-
Why must the Deployment selector match the template labels?
Model answer: The Deployment and ReplicaSet find their pods by selector. If the pods do not match, they would never count as owned, so the API rejects it.
-
How do you validate manifests before production?
Model answer: Server-side dry run, kubectl diff, schema validation in CI, and policy tools like Kyverno or OPA Gatekeeper.
-
What is the difference between client and server dry run?
Model answer: Client dry run only checks locally. Server dry run runs full validation and admission on the API server without saving.
Quick quiz
Q1. Which four top-level fields does almost every object have?
apiVersion, kind, metadata and spec.
Q2. Who writes the status section?
Kubernetes controllers, not you.
Q3. Labels or annotations: which are used by selectors?
Labels.
Q4. Which command shows docs for a field?
kubectl explain, for example kubectl explain pod.spec.containers.
Q5. How do you see changes before applying?
kubectl diff -f file.yaml.
Samajla ka?