Ever wondered how Kubernetes (K8s) knows what to do when you deploy an app?
The answer lives inside a tiny, powerful file — a YAML file. 📄
🧁 First Things First — What Is a YAML File?
Imagine you're writing a recipe card for your mom.
You write things like:
Dish: Chocolate Cake
Servings: 8
Ingredients:
- flour
- sugar
- eggs
Steps:
- Mix everything
- Bake at 180°C for 30 mins
That's basically what YAML is! It's a human-readable file format
that uses plain English + indentation to describe things.
No curly braces 🧙 — just clean, easy lines.
In Kubernetes, YAML files are used to tell K8s exactly what you want it to build or run.
What Are Kubernetes Objects?
Think of Kubernetes like a giant LEGO city manager. 🏙️
You tell it: "I want a building here, a road there, a power station over there."
Each "thing" you ask Kubernetes to create is called an Object.
These objects can be:
- 🚢 A Pod — the smallest running app container
- 🔁 A Deployment — manages how many copies of your app run
- 🌐 A Service — exposes your app to the outside world
- 🗃️ A ConfigMap — stores settings your app needs
- 🔐 A Secret — stores passwords safely
Every single one of these objects is described using a YAML file.
And every YAML file follows the same 4-part structure — like a standard form! 📋
🔑 The 4 Magic Keys of Every K8s YAML File
Every K8s YAML file — no matter what kind of object — always has 4 main parent keys at the top level:
Fig 1 — The 4 parent keys in every Kubernetes YAML object definition
Let's now explore each key one by one with super simple analogies! 🚀
📘 Key 1: apiVersion — "Which Rulebook Are We Using?"
🧒 Imagine You're Playing a Board Game
When you play Monopoly, you follow the Monopoly rulebook.
When you play Chess, you follow the Chess rulebook.
You can't mix them up! ♟️🎲
In Kubernetes, apiVersion tells K8s:
"Hey! Use THIS version of the rules to understand what I'm defining."
Kubernetes evolves over time, and different objects use different rule sets (APIs).
The apiVersion field makes sure K8s picks the right one.
📋 Common apiVersion Values
| apiVersion | Used For | Example |
|---|---|---|
v1 |
Core objects (oldest, most stable) | Pod, Service, ConfigMap, Secret |
apps/v1 |
App-level workloads | Deployment, ReplicaSet, StatefulSet |
batch/v1 |
One-time or scheduled tasks | Job, CronJob |
networking.k8s.io/v1 |
Network access rules | Ingress, NetworkPolicy |
rbac.authorization.k8s.io/v1 |
Permissions and access control | Role, ClusterRole, RoleBinding |
apiVersion for each object type.Different versions have different features. Using the wrong one means K8s won't understand your file.
apiVersion.Using
v1 for a Deployment (which needs apps/v1) will throw an error!
# ✅ Correct: Deployment uses apps/v1
apiVersion: apps/v1
# ❌ Wrong: Deployment does NOT use plain v1
apiVersion: v1
📗 Key 2: kind — "What Type of Object Are You Building?"
🏪 Imagine You're Ordering at a Restaurant
When you walk in, you tell the waiter: "I want a Burger" or "I want a Pizza".
The kitchen then knows exactly what to prepare! 🍔🍕
In Kubernetes, kind tells K8s:
"I want to create THIS type of object."
🧱 Common Kind Values
- 🚢 Pod — Runs one or more containers together
- 📦 Deployment — Manages how many pods run and keeps them healthy
- 🔁 ReplicaSet — Ensures a certain number of pod copies are always running
- 🌐 Service — Exposes pods so others can talk to them
- 🗺️ Ingress — Routes outside internet traffic into your cluster
- 🗃️ ConfigMap — Stores non-sensitive config settings
- 🔐 Secret — Stores sensitive data like passwords, tokens
- 💾 PersistentVolumeClaim — Requests storage space
- ⏰ CronJob — Runs a task on a schedule (like a cron job)
- 📊 StatefulSet — Like Deployment, but for apps that need stable storage (e.g., databases)
- 🔧 DaemonSet — Runs one copy of a pod on every node in the cluster
- 🏷️ Namespace — Creates a virtual cluster inside the cluster
kind is always PascalCase (each word starts with a capital letter).Example:
Deployment, ConfigMap, ReplicaSet — NOT deployment or config-map.
# Kind examples
# For a Pod:
kind: Pod
# For a Deployment:
kind: Deployment
# For a Service:
kind: Service
📙 Key 3: metadata — "What's Your Object's Identity Card?"
🪪 Imagine a School ID Card
Your school ID has: your name, your class, your roll number, and maybe some tags like "Sports Captain". 🎒
It uniquely identifies YOU among all the students.
In Kubernetes, metadata is exactly that — the identity card of your object.
It tells K8s: "This object's name is X, it belongs to namespace Y, and has these labels."
🔍 What Goes Inside metadata?
| Field | What it does | Example |
|---|---|---|
name |
Unique name for this object in the namespace | my-app-pod |
namespace |
Which virtual cluster this belongs to (optional — defaults to default) |
production |
labels |
Key-value tags to organize and select objects | app: frontend |
annotations |
Extra non-identifying notes (not used for selection) | owner: team-alpha |
uid |
Auto-generated unique ID by K8s (you don't set this) | Assigned by cluster |
🏷️ Labels vs Annotations — What's the Difference?
Fig 2 — Labels vs Annotations: Looks similar, used differently
metadata:
name: my-web-app # Unique name for this pod/deployment
namespace: production # Which "room" in the cluster it lives in
labels:
app: web-frontend # K8s uses this to connect services to pods
env: production
tier: frontend
annotations:
description: "Main customer-facing web app"
owner: "team-alpha"
contact: "ops@example.com"
app, env, version.Labels are how Services find their Pods. If labels don't match → app won't work! 🚨
Use the standard recommended labels from K8s docs:
app.kubernetes.io/name, app.kubernetes.io/version,
app.kubernetes.io/component, app.kubernetes.io/part-of.These work well with Helm, ArgoCD, and monitoring tools like Prometheus! 📈
📕 Key 4: spec — "Now Tell Me EXACTLY What You Want!"
🏠 Imagine Building a House
The metadata told the builder: "This house is named 'Oak Villa' in the 'Suburb' area."
Now the spec tells the builder: "3 bedrooms, 2 bathrooms, a red roof, a garden, and underground parking." 🏡
spec is the most detailed and complex section.
It describes exactly what your Kubernetes object should look like and how it should behave.
spec are different for every kind of object.A Pod's spec looks completely different from a Service's spec or a Deployment's spec.
That's why K8s needs
kind first — so it knows how to read the spec!
🚢 spec for a Pod — Contains the Container Details
spec:
containers: # List of containers to run
- name: my-app # Container's name (you pick this)
image: nginx:1.25 # Which Docker image to use
ports:
- containerPort: 80 # Port the container listens on
resources:
requests:
memory: "64Mi" # Minimum RAM to reserve
cpu: "250m" # Minimum CPU (250 millicores)
limits:
memory: "128Mi" # Maximum RAM allowed
cpu: "500m" # Maximum CPU allowed
env:
- name: ENV_MODE # Environment variable inside container
value: "production"
📦 spec for a Deployment — Adds Replica + Template Logic
spec:
replicas: 3 # Run 3 copies of the pod
selector:
matchLabels:
app: my-web-app # Match pods with this label
template: # Blueprint for each pod
metadata:
labels:
app: my-web-app # Pods created get this label
spec:
containers:
- name: web
image: my-app:v2.0
ports:
- containerPort: 8080
🌐 spec for a Service — Defines How to Expose the App
spec:
selector:
app: my-web-app # Forward traffic to pods with this label
ports:
- protocol: TCP
port: 80 # Port the Service listens on (external)
targetPort: 8080 # Port the Pod listens on (internal)
type: ClusterIP # ClusterIP | NodePort | LoadBalancer
🧩 Putting It ALL Together — A Complete YAML File Example
Now that you know all 4 keys, let's see a full, real-world YAML for a Deployment that runs a web app. 🚀
Fig 3 — How the 4 keys combine to create a live Kubernetes object
📝 Complete Deployment YAML (Fully Annotated)
# ───────────────────────────────────────────────
# KEY 1: apiVersion — Which rulebook to use
# ───────────────────────────────────────────────
apiVersion: apps/v1
# ───────────────────────────────────────────────
# KEY 2: kind — What type of object to create
# ───────────────────────────────────────────────
kind: Deployment
# ───────────────────────────────────────────────
# KEY 3: metadata — The identity card
# ───────────────────────────────────────────────
metadata:
name: my-web-app # Unique name in this namespace
namespace: production # Virtual cluster section
labels:
app: my-web-app # Used by Service to find this Deployment
env: production
version: "2.0"
annotations:
description: "Customer-facing web application"
owner: "platform-team"
last-updated: "2025-03-19"
# ───────────────────────────────────────────────
# KEY 4: spec — The detailed instructions
# ───────────────────────────────────────────────
spec:
replicas: 3 # Always keep 3 pods running
selector: # Which pods does this Deployment manage?
matchLabels:
app: my-web-app # Match pods with this label
strategy:
type: RollingUpdate # Deploy updates without downtime
rollingUpdate:
maxSurge: 1 # Max extra pods during update
maxUnavailable: 0 # No pods should go down during update
template: # Blueprint for creating pods
metadata:
labels:
app: my-web-app # Pods get this label (must match selector above)
version: "2.0"
spec: # Pod-level spec (what runs inside each pod)
containers:
- name: web-server
image: nginx:1.25.3 # Docker image to use
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 # Port the container listens on
protocol: TCP
resources:
requests:
memory: "64Mi" # Guaranteed minimum RAM
cpu: "100m" # Guaranteed min CPU
limits:
memory: "256Mi" # Hard cap on RAM
cpu: "500m" # Hard cap on CPU
env:
- name: APP_ENV
value: "production"
- name: LOG_LEVEL
value: "info"
livenessProbe: # Is the container alive?
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe: # Is the container ready for traffic?
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 3
restartPolicy: Always # Always restart if a container crashes
📐 YAML Formatting Rules You MUST Know
YAML is picky about formatting! One wrong space can break everything. 😬
Here are the golden rules:
-
🔢 Indentation uses SPACES, not TABs
K8s YAML uses 2 spaces per level. NEVER use the Tab key! -
🔑 Keys and values are separated by a colon + space
name: my-app✅ |name:my-app❌ -
📋 Lists start with a dash + space
- nginx:1.25✅ |-nginx:1.25❌ -
🧵 Strings with special characters need quotes
value: "my:value"✅ -
✂️ Comments start with #
# This is a comment -
📄 Multiple documents in one file use ---
Use---to separate multiple K8s objects in a single file.
Always use a YAML linter like yamllint or the VS Code YAML extension before applying!
📚 Multiple Objects in One File (Real MLOps Pattern)
In real MLOps deployments, you often define a Deployment + Service together
in one single file using --- as a separator:
# ─── Object 1: Deployment ───────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: model-serving-app
namespace: ml-production
labels:
app: model-serving
model: resnet50
spec:
replicas: 2
selector:
matchLabels:
app: model-serving
template:
metadata:
labels:
app: model-serving
spec:
containers:
- name: inference-server
image: myregistry/resnet50-server:v3.1
ports:
- containerPort: 8501
resources:
limits:
nvidia.com/gpu: "1" # Request 1 GPU for inference!
memory: "4Gi"
cpu: "2"
--- # ← This separator starts a brand new object definition
# ─── Object 2: Service (exposes the deployment) ──
apiVersion: v1
kind: Service
metadata:
name: model-serving-svc
namespace: ml-production
spec:
selector:
app: model-serving # Finds pods from Deployment above
ports:
- port: 80
targetPort: 8501
type: LoadBalancer # Expose to internet
kubectl apply -f much cleaner and manageable.Tools like ArgoCD and FluxCD love this pattern! 🔄
⚡ How To Apply a YAML File to Kubernetes
Once you have your YAML file ready, use kubectl (the K8s command line tool) to apply it:
# Apply (create or update) objects from a YAML file
kubectl apply -f my-deployment.yaml
# Check if your deployment was created
kubectl get deployments -n production
# See the pods that were created
kubectl get pods -n production
# Describe a specific pod (see full details + events)
kubectl describe pod <pod-name> -n production
# See logs from a container
kubectl logs <pod-name> -n production
# Delete everything defined in the YAML
kubectl delete -f my-deployment.yaml
# Dry-run: Validate YAML without actually applying it
kubectl apply -f my-deployment.yaml --dry-run=client
# Validate YAML structure before applying
kubectl apply -f my-deployment.yaml --dry-run=server
Before applying to a real cluster, always use
--dry-run=client to catch YAML errors.Use
--dry-run=server to also validate against the cluster's API rules. 🛡️
🤖 Real-World MLOps Example — Deploying an AI Model API
In today's MLOps world, YAML files are used everywhere — from deploying training jobs to serving ML models. Here's a taste:
# ML Model Training Job (runs once and exits)
apiVersion: batch/v1
kind: Job
metadata:
name: train-sentiment-model-v4
namespace: ml-jobs
labels:
app: ml-training
model: sentiment-bert
run-id: "run-2025-03-19"
annotations:
dataset: "s3://datasets/sentiment-v4"
experiment-tracker: "mlflow"
spec:
completions: 1 # Run job once successfully
backoffLimit: 3 # Retry up to 3 times on failure
template:
metadata:
labels:
app: ml-training
spec:
containers:
- name: trainer
image: myregistry/bert-trainer:latest
command: ["python", "train.py"]
args: ["--epochs", "10", "--batch-size", "32"]
resources:
limits:
nvidia.com/gpu: "2"
memory: "16Gi"
env:
- name: MLFLOW_TRACKING_URI
value: "http://mlflow-svc:5000"
- name: MODEL_REGISTRY
value: "s3://my-model-registry"
volumeMounts:
- name: dataset-volume
mountPath: /data
volumes:
- name: dataset-volume
persistentVolumeClaim:
claimName: training-data-pvc # Attaches storage
restartPolicy: Never # Don't restart job pods
Always tag your ML Job YAML with
run-id, model, and dataset in annotations or labels.This makes it easy to track which runs generated which model versions in your MLflow or W&B tracking system! 📊
🚨 Most Common Mistakes (And How To Fix Them)
| ❌ Mistake | Why It Breaks | ✅ Fix |
|---|---|---|
Wrong apiVersion |
K8s can't find the object type | Use kubectl api-resources to find the right version |
| Label mismatch between Deployment selector and Pod template | Service can't find the pods | Make sure selector.matchLabels = template.metadata.labels |
| Indentation with tabs | YAML parse error | Always use 2 spaces. Configure your editor! |
Missing namespace in metadata |
Lands in default namespace by accident |
Always explicitly set namespace in production! |
Forgetting imagePullPolicy |
Stale image cache might run old code | Use Always in prod, IfNotPresent in dev |
| No resource limits set | One bad pod can eat all cluster RAM/CPU | Always set resources.limits for every container |
| Putting secrets as plain text in YAML | Security breach! | Use K8s Secret objects or tools like Vault/Sealed Secrets |
📝 Quick Reference Cheat Sheet
Every K8s YAML always has these 4 keys:
apiVersion: <rulebook> → v1 | apps/v1 | batch/v1 | networking.k8s.io/v1 ...
kind: <type> → Pod | Deployment | Service | ConfigMap | Secret ...
metadata: → name, namespace, labels, annotations
name: <unique-name>
namespace: <ns>
labels:
key: value
spec: → Different for every kind. Contains the real instructions.
...
Helpful kubectl commands:
kubectl api-resources → List all kinds + their apiVersions
kubectl apply -f file.yaml → Create/update objects
kubectl get pods -n <ns> → List pods in namespace
kubectl describe pod <name> → Detailed info + events
kubectl delete -f file.yaml → Delete objects
kubectl apply -f . --dry-run=client → Validate all YAMLs in current dir
🎓 Quick Summary — What We Learned
- ✅ YAML = A simple, human-readable file format K8s uses to define objects
- ✅ K8s Objects = The "things" K8s builds for you: Pods, Deployments, Services...
- ✅ apiVersion = Which rulebook/API group handles this object type
- ✅ kind = The type of object you want to create (PascalCase!)
- ✅ metadata = The identity card: name, namespace, labels, annotations
- ✅ spec = The detailed blueprint: what to run, how many, what image, what ports...
- ✅ Labels = Used for selection by Services and Deployments (critical!)
- ✅ Annotations = Notes for humans and tools, not used for selection
- ✅ Always use
--dry-run=clientbefore applying to catch errors early
You now understand the backbone of every Kubernetes configuration —
the 4 parent keys of a YAML file.
Every deployment, every ML pipeline, every cloud-native app you'll ever build in K8s
starts with these same 4 lines at the top.
Happy learning! 🐼🚀
Comments
Post a Comment