📋 Prerequisites

Before We Begin

Kubernetes can feel overwhelming at first, but if you understand the following concepts you will have a smooth journey. Don't worry — we explain each one briefly below so you don't have to leave this page.

🐧

Basic Linux

Comfort with the terminal: ls, cd, cat, file permissions.

🐋

Docker / Containers

What an image is, how docker run works, difference between an image and a container.

🌐

Basic Networking

IP addresses, ports, DNS, HTTP/S request-response cycle.

📄

YAML

Indentation-sensitive data format. All Kubernetes configs are written in YAML.

Quick YAML Primer

YAML stands for "YAML Ain't Markup Language". It uses indentation to show structure, like Python does for code.

# Lines starting with '#' are comments — ignored by Kubernetes.

name: my-app          # string value — no quotes needed unless it contains special chars
version: "1.0"        # quoted because it looks like a number; quotes keep it a string
replicas: 3           # integer value — no quotes

labels:               # this is a "mapping" (key-value pairs), indented 2 spaces
  env: production     # key: value  (child of 'labels')
  team: backend

ports:                # this is a "sequence" (list), items start with a dash
  - 8080              # first item in the list
  - 9090              # second item
💡
Tip: In YAML, always use spaces (not tabs) for indentation. Two spaces per level is the Kubernetes convention.

What is a Container?

Think of a container like a shipping container on a cargo ship. It packages your application and all its dependencies together in a standardized box that runs the same way on any machine.

Host Operating System + Hardware Container Engine (Docker / containerd) Container A App + Libs nginx:1.24 Container B App + Libs node:20 Container C App + Libs python:3.12
Multiple containers share the host OS kernel but are isolated from each other

🧠 Check Your Understanding — Prerequisites

Q1. What is the primary advantage of containers over traditional Virtual Machines?
  • A) Containers have their own OS kernel
  • B) Containers are more isolated than VMs
  • C) Containers share the host OS kernel, making them lighter and faster to start
  • D) Containers cannot run on Linux
See Answer & Explanation
Answer: C
Containers share the host OS kernel, which means they don't need to boot a full OS. This makes them much lighter (MBs vs GBs) and faster to start (seconds vs minutes) compared to Virtual Machines.
Q2. Which data format is used to write Kubernetes configuration files?
  • A) JSON only
  • B) XML
  • C) YAML (JSON is also supported but YAML is standard)
  • D) TOML
See Answer & Explanation
Answer: C
Kubernetes accepts both YAML and JSON, but YAML is the community standard because it is more human-readable. All official Kubernetes documentation shows YAML examples.
Q3. What does the | symbol mean in a YAML value?
  • A) A boolean OR operator
  • B) A list separator
  • C) A literal block scalar — the value is a multi-line string, preserving newlines
  • D) A reference to another YAML file
See Answer & Explanation
Answer: C
The | (pipe) character starts a literal block scalar in YAML. Each subsequent indented line becomes part of the string with its newline preserved. This is commonly used in Kubernetes for multi-line config files stored in a ConfigMap.
Q4. What is the difference between a Docker image and a Docker container?
  • A) They are the same thing
  • B) An image is a running process; a container is a stopped snapshot
  • C) An image is a read-only blueprint; a container is a running instance created from that image
  • D) Images run on Linux only; containers run on any OS
See Answer & Explanation
Answer: C
A Docker image is like a class in OOP — a static, read-only template. A container is like an instance of that class — a live, running process with its own writable layer on top. You can create many containers from the same image.
Q5. In YAML, which of the following correctly represents a list of three strings?
  • A) items: {red, green, blue}
  • B) items: [red, green, blue] or each on a new line with -
  • C) items: "red, green, blue"
  • D) items: red | green | blue
See Answer & Explanation
Answer: B
YAML supports two list (sequence) styles: inline flow style [red, green, blue] or block style using a dash (- red) on separate lines. Both are valid. Kubernetes manifests typically use block style for readability.

☸️ Introduction

What is Kubernetes?

Imagine you run a pizza restaurant. You have 5 chefs making pizzas. If one chef calls in sick, you have to assign their work to the other 4. If it's Friday night and orders spike, you hire more temporary staff. If one oven breaks, you route orders to working ovens. You want all of this to happen automatically without you micromanaging every detail.

Kubernetes does exactly this — but for your containerized applications.

☸️
Kubernetes (also written as K8s — 8 letters between K and s) is an open-source container orchestration platform originally developed by Google and now maintained by the Cloud Native Computing Foundation (CNCF).

Why do we need it?

Modern applications are made of many small services (microservices), each running in its own container. Managing hundreds of containers manually across multiple servers is painful. Kubernetes automates:

  • Scheduling: "Which server should this container run on?"
  • Self-healing: "A container crashed — restart it automatically."
  • Scaling: "Traffic doubled — spin up more containers."
  • Load balancing: "Spread incoming requests across all healthy containers."
  • Rolling updates: "Deploy new version without downtime."
  • Configuration management: "Inject environment variables and secrets safely."

The Evolution: Bare Metal → VMs → Containers → Kubernetes

Bare Metal One App per Server 🖥️ Virtual Machines VM1 App+OS VM2 App+OS Hypervisor + Host OS Containers App A App B Container Engine Host OS Kubernetes Pod A Pod B Pod C Pod D Orchestration ✨
The evolution of application deployment infrastructure

Key Kubernetes Terms at a Glance

TermAnalogyWhat it really is
ClusterA factory buildingA set of machines (nodes) running Kubernetes together
NodeA workstation inside the factoryA single machine (VM or physical) in the cluster
PodA single work deskThe smallest deployable unit; wraps one or more containers
DeploymentThe HR department managing workersDeclares desired state; ensures Pods are running and updated
ServiceA reception desk with a stable phone numberStable network endpoint to reach a group of Pods

🧠 Check Your Understanding — Introduction

Q1. What does "K8s" stand for?
  • A) Kubernetes version 8
  • B) Kubernetes — K + 8 letters + s
  • C) Kube-8-Systems
  • D) An internal Google codename
See Answer & Explanation
Answer: B
"K8s" is a numeronym: K + the 8 middle letters (ubernete) + s = Kubernetes. This shorthand is widely used in the cloud-native community.
Q2. Which of the following is NOT an automatic feature provided by Kubernetes?
  • A) Self-healing crashed containers
  • B) Load balancing traffic across Pods
  • C) Writing application source code
  • D) Rolling deployment updates
See Answer & Explanation
Answer: C
Kubernetes manages infrastructure concerns (scheduling, healing, scaling, networking). It has no role in authoring your application code — that's still the developer's job.
Q3. A "Cluster" in Kubernetes refers to:
  • A) A group of containers inside one Pod
  • B) A collection of machines (nodes) managed by Kubernetes together
  • C) A single physical server
  • D) The Kubernetes dashboard UI
See Answer & Explanation
Answer: B
A cluster is the entire Kubernetes environment — it consists of a control plane (master) and one or more worker nodes that collectively run your workloads.
Q4. Which organization currently maintains the Kubernetes project?
  • A) Google
  • B) Red Hat
  • C) Cloud Native Computing Foundation (CNCF)
  • D) Microsoft
See Answer & Explanation
Answer: C
Google originally created Kubernetes and open-sourced it in 2014, but it was donated to the CNCF in 2016. The CNCF (a sub-foundation of the Linux Foundation) now governs the project with contributions from hundreds of companies.
Q5. What is "container orchestration"?
  • A) Writing Dockerfiles for multiple services
  • B) Automating the deployment, scaling, networking, and management of containers across multiple hosts
  • C) Running containers locally with docker-compose
  • D) Monitoring CPU usage of containers on a single machine
See Answer & Explanation
Answer: B
Orchestration goes beyond running a single container — it handles scheduling across a fleet of machines, ensuring availability, scaling based on load, service discovery, rolling updates, and more. Kubernetes is the de-facto standard orchestrator.
Q6. Which of the following is a key benefit of splitting an application into microservices deployed on Kubernetes?
  • A) Microservices require less total code than a monolith
  • B) Each service can be independently scaled, deployed, and updated without affecting others
  • C) Kubernetes eliminates the need for any CI/CD pipeline
  • D) All services must use the same programming language
See Answer & Explanation
Answer: B
Microservices allow independent lifecycle management. If your payment service needs more capacity, you scale just that Deployment — not the entire application. Combined with rolling updates, you can deploy new versions of individual services with zero downtime.

🏗️ Core Concepts

Kubernetes Architecture

A Kubernetes cluster has two main parts: the Control Plane (the brain) and Worker Nodes (the muscles). Think of it as a company: the control plane is the management team and worker nodes are the employees doing the actual work.

⚙️ Control Plane (Master Node) API Server kube-apiserver Front door of the cluster 🚪 etcd Key-Value Store Stores all cluster state/config 🗄️ Scheduler kube-scheduler Picks which node runs each Pod 📅 Controller Manager kube-controller-mgr Reconciles desired vs actual state 🔄 Worker Node 1 kubelet Node agent Pod A 🐳 nginx Pod B 🐳 api kube-proxy (networking) Worker Node 2 kubelet Node agent Pod C 🐳 database kube-proxy (networking) Worker Node 3 kubelet Node agent Pod D 🐳 cache Pod E 🐳 worker kube-proxy (networking) kubectl commands
Kubernetes Cluster Architecture — Control Plane manages Worker Nodes

Control Plane Components

1

API Server (kube-apiserver)

The single entry point for all control-plane communications. When you run kubectl apply, you're talking to the API server. It validates requests and updates the state in etcd.

2

etcd

A distributed key-value store that acts as Kubernetes' "database of truth". Every object's desired and current state is persisted here. If etcd goes down, the cluster cannot function correctly.

3

Scheduler (kube-scheduler)

When a new Pod needs to run, the scheduler looks at the available nodes, considers CPU/memory constraints, affinities, and taints, then assigns the Pod to the best node.

4

Controller Manager (kube-controller-manager)

Runs many control loops in background. Each controller watches the cluster state and works to make the actual state match the desired state. Example: the ReplicaSet controller ensures the right number of Pod copies are running.

Worker Node Components

A

kubelet

An agent running on each worker node. It receives Pod specifications from the API server and ensures the containers described in those specs are actually running and healthy.

B

kube-proxy

Maintains network rules on each node that allow network communication to and from Pods. It implements the Services concept at the network level.

C

Container Runtime

The software that actually runs containers (e.g., containerd, CRI-O). Docker was the classic choice but modern clusters use containerd directly.

🔑
kubectl is the command-line tool you use to interact with your cluster. It talks to the API Server. Every kubectl command ultimately reads from or writes to etcd through the API server.

🧠 Check Your Understanding — Architecture

Q1. Which component is responsible for deciding WHICH node a new Pod should run on?
  • A) API Server
  • B) kubelet
  • C) Scheduler
  • D) etcd
See Answer & Explanation
Answer: C
The kube-scheduler watches for unscheduled Pods and selects the best node for them based on resource availability, affinity rules, and other constraints.
Q2. Where does Kubernetes store the entire cluster state?
  • A) In a relational database like MySQL
  • B) In flat files on the master node
  • C) In etcd (distributed key-value store)
  • D) In the kubelet's local cache on each node
See Answer & Explanation
Answer: C
etcd is the source of truth. It stores the desired state (what you declared) and the current state. Controllers continuously compare these two and reconcile differences.
Q3. The kubelet runs on:
  • A) Only the Control Plane (master node)
  • B) Every Worker Node
  • C) The developer's local machine
  • D) Inside each Pod
See Answer & Explanation
Answer: B
The kubelet is a node-level agent. Every worker node has a kubelet that receives instructions from the API server and manages the lifecycle of Pods and containers on that specific node.
Q4. When you run kubectl apply -f deployment.yaml, which component is contacted first?
  • A) etcd
  • B) kubelet
  • C) The API Server
  • D) The Scheduler
See Answer & Explanation
Answer: C
Every interaction with the cluster goes through the API Server — it is the single front door. It authenticates the request, validates the YAML, and then persists the desired state to etcd. The scheduler and controllers react asynchronously afterwards.
Q5. What happens if the etcd database becomes unavailable?
  • A) All running Pods immediately stop
  • B) Existing workloads keep running but no new state changes (create, update, delete) can be processed
  • C) The scheduler takes over etcd's duties temporarily
  • D) The kubelet on each node crashes
See Answer & Explanation
Answer: B
etcd outage means the API server cannot read or write cluster state. Already-running Pods continue as-is (kubelet still manages them locally), but you cannot create new Pods, scale, deploy, or make any changes until etcd recovers. This is why etcd is run as a clustered HA setup in production.
Q6. Which control plane component continuously compares "desired state" with "actual state" and reconciles the difference?
  • A) API Server
  • B) etcd
  • C) Scheduler
  • D) Controller Manager
See Answer & Explanation
Answer: D
The Controller Manager runs control loops (controllers) for each resource type — ReplicaSet controller, Deployment controller, Node controller, etc. Each controller watches the current state in etcd and takes action to match the desired state. This reconciliation loop is the core of Kubernetes' self-healing.

🫛 Core Concepts

Pods — The Smallest Unit

A Pod is the smallest and most basic deployable object in Kubernetes. Think of a Pod as a wrapper around one or more tightly-coupled containers. While most Pods contain just one container, they can contain multiple containers that need to share storage or network.

🫛
All containers inside a Pod share the same IP address, port space, and storage volumes. They communicate with each other via localhost.
Pod (shared: IP 10.0.0.5, Network, Volumes) Main Container 🐳 nginx:1.24 Port: 8080 Mounts: /data (serves traffic) Sidecar Container 📊 log-shipper Mounts: /data (ships logs) localhost comm shared volume Pod IP 10.0.0.5
A Pod with a main container and a sidecar container — they share network and storage

Creating Your First Pod

You can create a Pod using a YAML manifest file (recommended) or the kubectl run command.

# pod.yaml

apiVersion: v1        # Which Kubernetes API group/version this object uses.
                      # Pods belong to the core "v1" API group.
kind: Pod             # The type of Kubernetes object to create.

metadata:
  name: my-first-pod  # Unique name for this Pod within the namespace.
  labels:             # Key-value tags. Used by Services/ReplicaSets to
    app: my-app       # find and group related Pods (via label selectors).

spec:                 # "spec" describes the DESIRED STATE of the Pod.
  containers:         # A Pod can hold one or more containers.
  - name: nginx-container   # Arbitrary name — used in logs and exec commands.
    image: nginx:1.24       # Docker image to run. Format: image:tag
                            # Always pin a specific tag (not "latest") in production.
    ports:
    - containerPort: 80     # Documents which port the container listens on.
                            # This is informational only — it does NOT publish the port.
                            # A Service is needed to actually route traffic here.
# kubectl apply -f <file>
#   Reads the YAML file and sends it to the API server.
#   Kubernetes creates or updates the described object.
#   -f means "from file" (can also be a URL or a directory).
kubectl apply -f pod.yaml

# kubectl get pods
#   Lists all Pods in the current namespace.
#   Columns: NAME, READY, STATUS, RESTARTS, AGE
#   Add -n <namespace> to check a specific namespace,
#   or --all-namespaces (-A) to see Pods everywhere.
kubectl get pods

# kubectl describe pod <name>
#   Shows the full event log and spec for a Pod.
#   Very useful for debugging — look at the "Events:"
#   section at the bottom when a Pod won't start.
kubectl describe pod my-first-pod

# kubectl logs <pod-name>
#   Prints stdout/stderr from the container.
#   Add -f to stream logs in real time (like tail -f).
#   Add --previous to see logs of a crashed container.
kubectl logs my-first-pod

# kubectl exec -it <pod-name> -- <command>
#   Opens an interactive shell inside a running container.
#   -i = keep stdin open  |  -t = allocate a TTY (terminal)
#   -- separates kubectl flags from the command to run.
#   Replace /bin/bash with /bin/sh for Alpine-based images.
kubectl exec -it my-first-pod -- /bin/bash

# kubectl delete pod <name>
#   Removes the Pod. Kubernetes sends SIGTERM to the container,
#   waits for graceful shutdown (30s by default), then force-kills.
#   Note: if managed by a Deployment, a new Pod is created immediately.
kubectl delete pod my-first-pod
⚠️
Pods are ephemeral! If a Pod dies, Kubernetes does not restart it by itself unless a higher-level controller (like a Deployment) manages it. Never run standalone Pods in production — use Deployments instead.

Pod Lifecycle Phases

PhaseMeaning
PendingPod accepted by cluster but containers not yet running (may be pulling image)
RunningAt least one container is running, starting, or restarting
SucceededAll containers terminated successfully (exit code 0)
FailedAll containers terminated, at least one with non-zero exit code
UnknownPod state cannot be determined (usually network issue with node)

🧠 Check Your Understanding — Pods

Q1. Two containers inside the same Pod communicate with each other using:
  • A) localhost (they share the same network namespace)
  • B) The Pod's external IP address
  • C) A Kubernetes Service
  • D) etcd
See Answer & Explanation
Answer: A
Containers in the same Pod share the same network namespace, including IP address and port space. They communicate via localhost, just like two processes on the same machine.
Q2. Why should you NOT run standalone Pods in production?
  • A) Pods cannot run more than one container
  • B) If a standalone Pod dies, Kubernetes won't automatically recreate it
  • C) Pods don't support resource limits
  • D) Pods are not visible to Services
See Answer & Explanation
Answer: B
Pods are ephemeral. Without a controller (Deployment, ReplicaSet), a dead Pod is just gone. Controllers provide self-healing by ensuring a desired number of Pod replicas are always running.
Q3. What is a "sidecar" container pattern?
  • A) A container that replaces the main container during updates
  • B) An extra container in the same Pod that provides supporting functionality such as logging, proxying, or config reloading
  • C) The first container to start in a multi-container Pod
  • D) A container with higher CPU limits than the main container
See Answer & Explanation
Answer: B
The sidecar pattern places a helper container alongside the main application container in the same Pod. Common examples: a Fluentd container that collects and ships the app's logs, an Envoy proxy that handles service mesh traffic, or a git-sync container that keeps a config volume up to date.
Q4. You run kubectl get pods and see STATUS: CrashLoopBackOff. What does this mean?
  • A) The Pod is still being scheduled onto a node
  • B) The Pod is waiting for a PersistentVolumeClaim to be bound
  • C) The container keeps crashing after starting, and Kubernetes is restarting it with exponentially increasing delays
  • D) The node ran out of memory and evicted the Pod
See Answer & Explanation
Answer: C
CrashLoopBackOff means the container starts, crashes (exits with non-zero), and Kubernetes restarts it. After each crash the backoff delay doubles (10s → 20s → 40s … up to 5 minutes). Debug with kubectl logs <pod> --previous to see logs from the crashed container.
Q5. Which Pod phase indicates that all containers terminated successfully (exit code 0)?
  • A) Running
  • B) Completed / Succeeded
  • C) Terminating
  • D) Ready
See Answer & Explanation
Answer: B
The Succeeded phase means every container in the Pod ran to completion and exited with code 0. This is the expected final state for Job-created Pods. Long-running service Pods (web servers, APIs) should always stay in Running state.
Q6. What does kubectl describe pod my-pod show that kubectl get pod my-pod does not?
  • A) The Pod's IP address
  • B) The full event log, resource usage, volume mounts, environment variables, and container state details
  • C) Only the Pod's labels
  • D) The YAML manifest used to create the Pod
See Answer & Explanation
Answer: B
kubectl describe is the go-to debugging command. It shows the full spec, current status, container states, resource requests/limits, mounted volumes, env vars, and — most importantly — the Events section at the bottom, which explains exactly why a Pod might be stuck or failing.

🔁 Core Concepts

ReplicaSets — Ensuring High Availability

A ReplicaSet ensures that a specified number of identical Pod replicas are running at all times. If a Pod crashes or is deleted, the ReplicaSet automatically creates a new one. Think of it as the "safety net" that maintains your desired number of running Pods.

ReplicaSet (replicas: 3) Pod 1 ✅ 🐳 nginx Running Node 1 Pod 2 ✅ 🐳 nginx Running Node 2 Pod 3 💀→🔄 🐳 nginx Crashed! Recreating…
When Pod 3 crashes, the ReplicaSet controller automatically spins up a replacement
💡
In practice, you rarely create ReplicaSets directly. Instead you create a Deployment (next section), which manages a ReplicaSet for you and adds extra features like rolling updates.
# replicaset.yaml

apiVersion: apps/v1   # ReplicaSets are in the "apps" API group, version v1.
kind: ReplicaSet

metadata:
  name: nginx-rs

spec:
  replicas: 3         # The desired number of identical Pod copies.
                      # Controller reconciles: if 2 are running → creates 1 more.
                      #                        if 4 are running → deletes 1.

  selector:           # How the ReplicaSet identifies which Pods it "owns".
    matchLabels:
      app: nginx      # It will manage any Pod that has the label app=nginx.
                      # ⚠️ selector must match the template's labels exactly.

  template:           # Blueprint for the Pods this ReplicaSet will create.
    metadata:
      labels:
        app: nginx    # Every Pod created from this template gets app=nginx.
                      # This must match the selector above.
    spec:
      containers:
      - name: nginx
        image: nginx:1.24
        ports:
        - containerPort: 80

🧠 Check Your Understanding — ReplicaSets

Q1. If you set replicas: 5 in a ReplicaSet and manually delete 2 Pods, what happens?
  • A) The cluster shuts down
  • B) The remaining 3 Pods continue but no new ones are created
  • C) The ReplicaSet creates 2 new Pods to maintain the desired count of 5
  • D) The ReplicaSet is deleted
See Answer & Explanation
Answer: C
The ReplicaSet controller constantly watches the cluster. When it detects fewer Pods than desired, it creates new ones. This is the core self-healing mechanism of Kubernetes.
Q2. How does a ReplicaSet identify which Pods it manages?
  • A) By the Pod's IP address
  • B) By matching labels defined in the selector.matchLabels field
  • C) By the Pod's creation timestamp
  • D) By the Pod's node name
See Answer & Explanation
Answer: B
Labels are key-value pairs attached to Kubernetes objects. The selector in a ReplicaSet defines which labels to look for when counting and managing Pods.
Q3. You have a ReplicaSet with replicas: 3. You manually create a 4th Pod with the same labels. What happens?
  • A) Nothing — all 4 Pods keep running alongside each other
  • B) The ReplicaSet immediately terminates one Pod (could be any of the four) to bring the total back to 3
  • C) The manually created Pod is rejected by the API Server
  • D) The ReplicaSet scales itself to 4
See Answer & Explanation
Answer: B
The ReplicaSet controller only cares about the total count of Pods matching its selector. If it detects 4 when it wants 3, it deletes one — regardless of which Pod created it. This is why you should never create Pods with labels that accidentally match an existing ReplicaSet's selector.
Q4. When you delete a ReplicaSet, what happens to the Pods it was managing?
  • A) Pods continue running independently and are never deleted
  • B) Pods are deleted along with the ReplicaSet (by default)
  • C) Pods are adopted by another ReplicaSet with a matching selector
  • D) Pods are moved to the kube-system namespace for safekeeping
See Answer & Explanation
Answer: B
By default, deleting a ReplicaSet cascades — all Pods it owns are garbage-collected. You can use --cascade=orphan to leave the Pods running without an owner, but this is rarely desirable in practice.
Q5. What does kubectl scale replicaset nginx-rs --replicas=0 effectively do?
  • A) Deletes the ReplicaSet permanently
  • B) Terminates all managed Pods but leaves the ReplicaSet object intact — useful for temporarily shutting down a workload
  • C) Pauses the ReplicaSet without stopping Pods
  • D) Prevents any new Pods from being scheduled on existing nodes
See Answer & Explanation
Answer: B
Scaling to 0 replicas terminates all Pods but keeps the ReplicaSet object in the cluster. This is a common pattern for temporarily stopping a workload without losing its configuration — you can scale back up to restart it at any time.
Q6. What is the primary reason to use a Deployment instead of managing a ReplicaSet directly?
  • A) A Deployment allows more replicas than a ReplicaSet
  • B) A Deployment adds rolling updates, rollback history, and pause/resume capabilities on top of ReplicaSet functionality
  • C) ReplicaSets cannot manage Pods with resource limits
  • D) Deployments are faster to schedule than ReplicaSets
See Answer & Explanation
Answer: B
A Deployment wraps a ReplicaSet and adds critical production features: declarative rolling updates (swap old RS for new RS), rollback history via revision tracking, and the ability to pause a rollout mid-way for canary-style testing. ReplicaSet alone has none of these.

🚀 Core Concepts

Deployments — The Workhorse

A Deployment is the recommended way to run stateless applications on Kubernetes. It manages a ReplicaSet for you and adds powerful features on top: rolling updates, rollbacks, and scaling. This is the object you will use most frequently in day-to-day Kubernetes work.

Deployment (my-app, replicas: 3) owns ReplicaSet (nginx:1.24) Pod nginx:1.24 ✅ Pod nginx:1.24 ✅ Pod nginx:1.24 ✅
A Deployment owns and manages a ReplicaSet, which in turn owns the Pods
# deployment.yaml

apiVersion: apps/v1
kind: Deployment      # Manages a ReplicaSet, which in turn manages Pods.

metadata:
  name: my-app

spec:
  replicas: 3         # Keep 3 Pods running at all times.

  selector:
    matchLabels:
      app: my-app     # The Deployment owns Pods with this label.

  strategy:
    type: RollingUpdate   # Default strategy. Alternative: Recreate
                          # (Recreate kills all old Pods first — causes downtime).
    rollingUpdate:
      maxSurge: 1         # During an update, allow UP TO 1 extra Pod above replicas.
                          # So temporarily up to 4 Pods can run (3 + 1 surge).
      maxUnavailable: 0   # During an update, NEVER let available Pods drop below 3.
                          # Combined with maxSurge:1 this gives zero-downtime updates.

  template:           # Pod blueprint — same as ReplicaSet template.
    metadata:
      labels:
        app: my-app   # Must match spec.selector.matchLabels above.
    spec:
      containers:
      - name: app
        image: my-app:1.0   # Change this value and apply → triggers rolling update.
        ports:
        - containerPort: 3000

Essential kubectl Commands

# kubectl apply -f <file>
#   Creates or updates the Deployment described in the YAML.
#   If the Deployment already exists, Kubernetes calculates
#   the diff and applies only what changed (e.g., a new image).
kubectl apply -f deployment.yaml

# kubectl scale deployment <name> --replicas=<n>
#   Imperatively adjusts the replica count without editing YAML.
#   Kubernetes adds or removes Pods to match the new target.
#   For permanent changes, update replicas: in your YAML instead.
kubectl scale deployment my-app --replicas=5

# kubectl set image deployment/<name> <container>=<image:tag>
#   Patches the container image, which triggers a rolling update.
#   "app" is the container name defined in spec.containers[].name.
#   Kubernetes will progressively replace old Pods with new ones.
kubectl set image deployment/my-app app=my-app:2.0

# kubectl rollout status deployment/<name>
#   Blocks and shows real-time progress of a rolling update.
#   Exits 0 when all replicas are updated, or non-zero on timeout.
#   Great to use in CI/CD pipelines to verify deployments succeed.
kubectl rollout status deployment/my-app

# kubectl rollout undo deployment/<name>
#   Rolls back to the previous ReplicaSet revision.
#   Add --to-revision=<n> to go back to a specific version number.
#   Kubernetes simply sets the Deployment's image back to the old one.
kubectl rollout undo deployment/my-app

# kubectl rollout history deployment/<name>
#   Shows all saved revisions of this Deployment.
#   Each 'kubectl apply' or 'set image' creates a new revision.
#   Add --revision=<n> to see the exact spec of a past revision.
kubectl rollout history deployment/my-app

Rolling Update — Zero Downtime Deployment

Rolling Update: v1 → v2 (replicas: 3) Step 1 Step 2 Step 3 Done ✅ v1 Pod ✅ v1 Pod ✅ v1 Pod ✅ v2 Pod 🆕 v1 Pod ✅ v1 Pod ✅ v2 Pod ✅ v2 Pod 🆕 v1 Pod ✅ v2 Pod ✅ v2 Pod ✅ v2 Pod ✅ At every step, at least 3 Pods are serving traffic → Zero Downtime!
Rolling Update progressively replaces old Pods with new ones, maintaining availability

🧠 Check Your Understanding — Deployments

Q1. What is the main advantage of Deployments over manually managing ReplicaSets?
  • A) Deployments can run more containers per Pod
  • B) Deployments support rolling updates, rollbacks, and maintain update history
  • C) Deployments don't need labels
  • D) Deployments run on the control plane, not worker nodes
See Answer & Explanation
Answer: B
A Deployment wraps a ReplicaSet and adds declarative update management. You can push a new image version, watch it roll out progressively, and roll back instantly if something goes wrong.
Q2. In a Rolling Update, what does maxUnavailable: 0 guarantee?
  • A) The update completes instantly
  • B) No Pod goes offline during the update — the old Pods are only terminated after new ones are healthy
  • C) At most 0 new Pods are created
  • D) The Deployment cannot be updated
See Answer & Explanation
Answer: B
maxUnavailable: 0 means zero old Pods are taken down until an equal number of new Pods are fully ready. This ensures 100% availability at the cost of temporarily using more resources (controlled by maxSurge).
Q3. Your newly deployed version is crashing in production. What is the fastest recovery command?
  • A) kubectl delete deployment my-app and recreate it
  • B) kubectl rollout undo deployment/my-app
  • C) Manually edit every Pod's image
  • D) Scale replicas to 0 and back to 3
See Answer & Explanation
Answer: B
kubectl rollout undo instantly reverts the Deployment to the previous ReplicaSet revision. It triggers another rolling update in reverse — new version Pods are replaced by old version Pods with zero downtime. Add --to-revision=N to jump to a specific older version.
Q4. Which deployment strategy kills ALL old Pods before starting new ones (accepts brief downtime)?
  • A) RollingUpdate
  • B) BlueGreen
  • C) Recreate
  • D) Canary
See Answer & Explanation
Answer: C
The Recreate strategy terminates all Pods of the old version first, then starts Pods of the new version. This causes downtime equal to the startup time of your new Pods but avoids running two versions simultaneously — useful when the new version is incompatible with the old database schema.
Q5. Where does Kubernetes store Deployment rollout history?
  • A) In a Git repository that you must configure
  • B) As old ReplicaSet objects kept in the cluster (controlled by revisionHistoryLimit)
  • C) In etcd as a dedicated ConfigMap per revision
  • D) Only in the Deployment's annotations as plain text
See Answer & Explanation
Answer: B
Each time you trigger a new rollout, Kubernetes creates a new ReplicaSet with the updated Pod template and keeps the old ReplicaSet (scaled to 0). These old ReplicaSets are the rollout history. revisionHistoryLimit (default: 10) controls how many old ReplicaSets are retained.
Q6. What does kubectl rollout pause deployment/my-app do?
  • A) Stops all running Pods immediately
  • B) Freezes the rollout mid-way — new Pods already created continue running but no further Pods are replaced until resumed
  • C) Reverts to the previous version
  • D) Scales the Deployment to 0 replicas
See Answer & Explanation
Answer: B
Pausing a rollout lets you do a manual canary check — you push a new version to a fraction of your Pods, verify it works (check logs, metrics, error rates), and then kubectl rollout resume deployment/my-app to continue replacing the rest.

🌐 Networking

Services — Stable Networking

Pods are ephemeral — they come and go, and each time a new Pod starts, it gets a new IP address. If you have 3 Pods running your web app, how does a database Pod know which IP to call? This is the problem Services solve.

A Service gives a stable virtual IP (ClusterIP) and DNS name to a group of Pods. kube-proxy load-balances traffic across all healthy Pods matching the Service's selector.

Client my-svc:80 Service ClusterIP: 10.96.0.1 selector: app=my-app port: 80 → 3000 load balance Pod A (app=my-app) Pod B (app=my-app) Pod C (app=my-app)
A Service routes traffic to all Pods matching its label selector, regardless of which node they run on

Service Types

TypeAccessible FromUse Case
ClusterIP (default)Only within the clusterInternal microservice-to-microservice communication
NodePortOutside the cluster via NodeIP:NodePortDevelopment/testing; exposes a port on every node
LoadBalancerOutside via cloud load balancerProduction workloads on cloud (AWS, GCP, Azure)
ExternalNameMaps to an external DNS nameAliasing external services into the cluster
# service.yaml — ClusterIP (internal only)

apiVersion: v1
kind: Service

metadata:
  name: my-app-svc      # Other Pods in the cluster can reach this Service
                        # by DNS: my-app-svc.<namespace>.svc.cluster.local
                        # or just "my-app-svc" within the same namespace.

spec:
  type: ClusterIP       # Default type. Only reachable from within the cluster.
                        # kube-proxy assigns a stable virtual IP (VIP) to this Service.

  selector:
    app: my-app         # Routes traffic to ALL Pods that have label app=my-app.
                        # Pods are added/removed from routing dynamically as they
                        # come and go — the Service always stays at the same VIP.

  ports:
  - port: 80            # The port clients use to call this Service.
                        # e.g., http://my-app-svc:80
    targetPort: 3000    # The port the container is actually listening on.
                        # The Service translates: client:80 → container:3000.

---
# LoadBalancer — exposes to the internet via a cloud load balancer

apiVersion: v1
kind: Service
metadata:
  name: my-app-lb
spec:
  type: LoadBalancer    # Tells the cloud provider (AWS/GCP/Azure) to provision
                        # an external load balancer and assign a public IP.
                        # The assigned IP appears in: kubectl get svc my-app-lb
                        #   → EXTERNAL-IP column.

  selector:
    app: my-app

  ports:
  - port: 80            # Internet-facing port on the cloud load balancer.
    targetPort: 3000    # Internal container port traffic is forwarded to.

🧠 Check Your Understanding — Services

Q1. You have a database Pod inside the cluster. Which Service type should you use so only other Pods can access it?
  • A) ClusterIP
  • B) NodePort
  • C) LoadBalancer
  • D) ExternalName
See Answer & Explanation
Answer: A
ClusterIP is only accessible within the cluster. It's perfect for internal services like databases that should never be exposed to the internet.
Q2. A Service's targetPort refers to:
  • A) The port clients use to access the Service
  • B) The port exposed on the Kubernetes node
  • C) The port the container inside the Pod is listening on
  • D) The port used by etcd
See Answer & Explanation
Answer: C
port is what clients call (e.g., 80). targetPort is where the container is actually listening (e.g., 3000). The Service translates between them.
Q3. How does a Service know which Pods to route traffic to as Pods are created and destroyed?
  • A) It uses a hardcoded list of Pod IP addresses updated manually
  • B) It continuously matches its selector labels against running Pod labels — the Endpoints list updates automatically
  • C) It routes to all Pods on the same node
  • D) It reads the Deployment's replica count to decide routing
See Answer & Explanation
Answer: B
Kubernetes maintains an Endpoints object for each Service. The Endpoints controller continuously watches for Pods with matching labels and updates the IP list. When a Pod becomes Ready it's added; when it fails a readiness probe it's removed. This is why Services provide stable access despite Pods being replaced.
Q4. What is a "headless" Service (clusterIP: None) used for?
  • A) A Service with no ports configured
  • B) A Service that returns individual Pod IPs directly via DNS instead of load-balancing through a virtual IP — used by StatefulSets for stable Pod hostnames
  • C) A Service that works only inside the kube-system namespace
  • D) A Service that disables all network traffic to the Pods
See Answer & Explanation
Answer: B
With clusterIP: None, no virtual IP is assigned. DNS queries for the Service return the individual Pod IPs directly (A records). StatefulSets use headless Services so each Pod gets a stable DNS name like pod-0.my-svc.default.svc.cluster.local.
Q5. What port range does a NodePort Service use?
  • A) 80 and 443 only
  • B) 1024–49151
  • C) 30000–32767
  • D) Any port above 1024
See Answer & Explanation
Answer: C
NodePort Services use ports in the 30000–32767 range by default. Each node in the cluster opens that port and forwards traffic to the corresponding Service. You can specify a particular port in this range, or let Kubernetes assign one automatically.
Q6. From a Pod in the frontend namespace, what DNS name correctly reaches a Service named api in the backend namespace?
  • A) api
  • B) api.cluster.local
  • C) api.backend.svc.cluster.local
  • D) backend/api
See Answer & Explanation
Answer: C
The full DNS name format for a Service is <service>.<namespace>.svc.cluster.local. Within the same namespace you can use just the service name (api). Across namespaces you must include at least the namespace: api.backend or the full FQDN.

📂 Core Concepts

Namespaces — Virtual Clusters

Imagine a large office building shared by multiple companies. Each company has its own floor with locked access — they can see each other exists but can't accidentally walk into each other's rooms. Namespaces do the same for Kubernetes resources.

Namespaces provide a way to divide cluster resources between multiple users, teams, or environments (e.g., dev, staging, production) within a single cluster.

Kubernetes Cluster ns: default my-app Deployment my-app Service ConfigMap ns: dev api-v2 Deployment api-v2 Service dev Secrets ns: production api Deployment api Service prod Secrets
Three isolated namespaces inside one cluster — each with its own resources and policies
# kubectl get namespaces
#   Lists all namespaces in the cluster (shorthand: kubectl get ns).
#   You'll see the built-in ones: default, kube-system, kube-public.
kubectl get namespaces

# kubectl create namespace <name>
#   Creates a new namespace imperatively.
#   Alternatively: kubectl apply -f namespace.yaml
#   (where the YAML has kind: Namespace)
kubectl create namespace dev

# kubectl apply -f <file> -n <namespace>
#   The -n flag targets a specific namespace for this command.
#   If omitted, the command uses whatever namespace your
#   kubeconfig context is currently set to (often "default").
kubectl apply -f deployment.yaml -n dev

# kubectl get pods -n <namespace>
#   Lists Pods only in the specified namespace.
#   You must specify -n every time, or change the default below.
kubectl get pods -n dev

# kubectl get pods --all-namespaces   (shorthand: -A)
#   Lists Pods across ALL namespaces in one view.
#   Adds a NAMESPACE column to the output so you can see
#   which namespace each Pod belongs to.
kubectl get pods --all-namespaces

# kubectl config set-context --current --namespace=<ns>
#   Changes the DEFAULT namespace for your current kubeconfig context.
#   After this, you no longer need to pass -n dev on every command.
#   Run 'kubectl config view --minify' to verify the active context.
kubectl config set-context --current --namespace=dev
📌
Kubernetes creates 4 built-in namespaces: default (for user workloads), kube-system (cluster internals), kube-public (public readable data), and kube-node-lease (node heartbeats). Never delete these!

🧠 Check Your Understanding — Namespaces

Q1. Which namespace contains core Kubernetes components like the API server and scheduler?
  • A) default
  • B) kube-system
  • C) kube-public
  • D) system-core
See Answer & Explanation
Answer: B
The kube-system namespace holds all Kubernetes system Pods (like CoreDNS, kube-proxy, the scheduler, etc.). You can view them with kubectl get pods -n kube-system.
Q2. Can two Deployments in different namespaces have the same name?
  • A) Yes — namespaces provide scope, so names only need to be unique within a namespace
  • B) No — names must be unique across the entire cluster
  • C) Only if they run on different nodes
  • D) Only ClusterIP Services can share names across namespaces
See Answer & Explanation
Answer: A
Namespaces isolate resource names. Both the dev and production namespaces can have a Deployment named my-app — they are fully independent objects.
Q3. When you run kubectl get pods without any flags, which namespace does it look in?
  • A) kube-system
  • B) kube-public
  • C) The default namespace (or whichever namespace your current kubeconfig context is set to)
  • D) All namespaces simultaneously
See Answer & Explanation
Answer: C
kubectl commands operate in the namespace configured in the current kubeconfig context. Out of the box, that's the default namespace. Change it with kubectl config set-context --current --namespace=<ns>, or pass -n <ns> on each command.
Q4. Can a Pod in the frontend namespace call a Service in the backend namespace?
  • A) No — namespaces are fully network-isolated by default
  • B) Yes — using the full DNS name service-name.backend.svc.cluster.local
  • C) Only if the namespaces share the same labels
  • D) Only if a NetworkPolicy explicitly allows it and they are in the same node
See Answer & Explanation
Answer: B
Namespaces scope Kubernetes objects but do NOT provide network isolation by default — all Pods can reach all other Pods and Services across namespaces. Network isolation requires explicit NetworkPolicy objects. Cross-namespace DNS uses the full FQDN.
Q5. Which built-in namespace contains information readable by all users, including unauthenticated ones?
  • A) default
  • B) kube-system
  • C) kube-node-lease
  • D) kube-public
See Answer & Explanation
Answer: D
kube-public is readable by all users (even unauthenticated). It's used by the cluster bootstrap process to expose cluster info via the cluster-info ConfigMap. In practice it's rarely used directly by application workloads.
Q6. What is a ResourceQuota in Kubernetes?
  • A) A per-container CPU cap, the same as resource limits
  • B) A namespace-level policy that limits total CPU, memory, and object counts (Pods, Services, PVCs, etc.) for that namespace
  • C) A cloud billing policy managed outside Kubernetes
  • D) A per-node hardware reservation
See Answer & Explanation
Answer: B
ResourceQuota enforces aggregate limits at the namespace level. For example, "the dev namespace can use at most 4 CPU cores, 8Gi RAM, and 20 Pods total." This prevents one team's namespace from consuming all cluster resources and starving others.

🔐 Core Concepts

ConfigMaps & Secrets

Hard-coding configuration (database URLs, API keys, feature flags) directly into container images is bad practice. It makes images non-portable and secrets insecure. Kubernetes provides two objects to solve this:

  • ConfigMap — Stores non-sensitive key-value configuration data.
  • Secret — Stores sensitive data (passwords, tokens, keys) encoded in base64.
⚠️
Secrets are base64-encoded, NOT encrypted by default. Anyone with access to the cluster can decode them. For production, use additional tools like Vault, Sealed Secrets, or enable etcd encryption-at-rest.
# configmap.yaml

apiVersion: v1
kind: ConfigMap

metadata:
  name: app-config      # Pods reference this by name.

data:                   # All values here are plain strings — no base64 encoding.
  DATABASE_HOST: "postgres.default.svc.cluster.local"
                        # Full DNS name of a Service in the cluster.
                        # Format: <service-name>.<namespace>.svc.cluster.local
  APP_PORT: "3000"
  LOG_LEVEL: "info"

  config.properties: | # The pipe "|" means multi-line string literal.
    max-connections=100 # This entire block is stored as a single file-like value.
    timeout=30s         # Useful for mounting as a config file inside a container.

---
# secret.yaml

apiVersion: v1
kind: Secret

metadata:
  name: app-secret

type: Opaque            # "Opaque" = generic secret (arbitrary key-value data).
                        # Other types: kubernetes.io/tls, kubernetes.io/dockerconfigjson

data:                   # Values MUST be base64-encoded.
                        # Encode: echo -n "password123" | base64
                        # Decode: echo "cGFzc3dvcmQxMjM=" | base64 --decode
  DATABASE_PASSWORD: cGFzc3dvcmQxMjM=   # base64("password123")
  API_KEY: c2VjcmV0a2V5                 # base64("secretkey")
                        # ⚠️ base64 is encoding, NOT encryption.
                        # The real security comes from RBAC restricting who
                        # can read Secrets, and optionally etcd encryption-at-rest.

Injecting ConfigMaps & Secrets into Pods

spec:
  containers:
  - name: app
    image: my-app:1.0

    # ── Method 1: Single env var pulled from a ConfigMap key ──────────────
    # The container sees DB_HOST as a normal environment variable.
    # It does NOT know it came from a ConfigMap — just like export DB_HOST=...
    env:
    - name: DB_HOST             # Name of the env var inside the container.
      valueFrom:
        configMapKeyRef:
          name: app-config      # Name of the ConfigMap object.
          key: DATABASE_HOST    # Which key from that ConfigMap to use.

    # ── Method 2: Single env var pulled from a Secret key ─────────────────
    # Kubernetes automatically base64-DECODES the value before injecting it.
    # So the container receives the plain-text password, not the encoded form.
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secret      # Name of the Secret object.
          key: DATABASE_PASSWORD

    # ── Method 3: Load ALL keys from a ConfigMap as env vars at once ──────
    # Every key in app-config becomes an env var automatically.
    # Useful when you have many config values and don't want to list each one.
    envFrom:
    - configMapRef:
        name: app-config

    # ── Method 4: Mount a ConfigMap as files inside the container ─────────
    # Each key in the ConfigMap becomes a separate file under /etc/config/.
    # e.g., /etc/config/LOG_LEVEL, /etc/config/config.properties
    # The app can read these files at runtime. Updates propagate automatically.
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config    # Directory inside the container where files appear.

  volumes:
  - name: config-volume         # Must match the volumeMount name above.
    configMap:
      name: app-config          # Which ConfigMap to expose as files.

🧠 Check Your Understanding — ConfigMaps & Secrets

Q1. What is the difference between a ConfigMap and a Secret?
  • A) ConfigMaps can only store strings; Secrets can store binary data
  • B) ConfigMaps store non-sensitive config; Secrets store sensitive data (with base64 encoding and additional access controls)
  • C) Secrets are encrypted with AES-256 by default in etcd
  • D) ConfigMaps are available in all namespaces; Secrets are cluster-scoped
See Answer & Explanation
Answer: B
The key distinction is intent and access control. Secrets are base64-encoded (not encrypted by default) and Kubernetes provides mechanisms to restrict who can read them via RBAC. ConfigMaps are plain-text and typically readable by anyone in the namespace.
Q2. If you update a ConfigMap that is mounted as a volume, what happens?
  • A) Nothing — Pods must be restarted to pick up changes
  • B) The mounted file is updated automatically (with a small delay), but the app must re-read the file
  • C) The Pod is automatically restarted
  • D) The ConfigMap is immutable after creation
See Answer & Explanation
Answer: B
Volume-mounted ConfigMaps are updated automatically by the kubelet (typically within 1-2 minutes). However, environment variable injections from ConfigMaps are NOT updated — the Pod must be restarted for those to take effect.
Q3. What happens if a Pod references a ConfigMap that does not exist in the cluster?
  • A) The Pod starts with empty environment variables as a fallback
  • B) Kubernetes creates an empty ConfigMap automatically
  • C) The Pod is created but stays in a Pending/Error state and the container does not start
  • D) The namespace is automatically deleted
See Answer & Explanation
Answer: C
If a referenced ConfigMap (or Secret) is missing, the Pod gets stuck before the container even starts — the event log shows something like "configmap not found". You can set optional: true on the reference to allow the Pod to start even if the ConfigMap doesn't exist yet.
Q4. How do you base64-encode the value password123 on the command line to put it in a Secret?
  • A) kubectl encode "password123"
  • B) echo -n "password123" | base64
  • C) openssl secret "password123"
  • D) kubectl create secret --encode "password123"
See Answer & Explanation
Answer: B
echo -n "password123" | base64 outputs cGFzc3dvcmQxMjM=. The -n flag is important — it prevents echo from adding a newline character, which would otherwise be included in the encoded value and cause the decoded secret to have a trailing newline.
Q5. Which Secret type should you use to store Docker registry credentials?
  • A) Opaque
  • B) kubernetes.io/tls
  • C) kubernetes.io/dockerconfigjson
  • D) kubernetes.io/basic-auth
See Answer & Explanation
Answer: C
Kubernetes recognises specialised Secret types for structured data. kubernetes.io/dockerconfigjson holds credentials for pulling images from a private registry. You reference it in a Pod with imagePullSecrets. kubernetes.io/tls holds a TLS certificate + key pair (used by Ingress).
Q6. You create a ConfigMap and inject it via envFrom. Later you update the ConfigMap. When do the running Pods see the new value?
  • A) Immediately — environment variables are live-updated
  • B) Only after the Pod is deleted and recreated (or a rolling restart is triggered)
  • C) After exactly 60 seconds
  • D) Only if you set configMap.live: true
See Answer & Explanation
Answer: B
Environment variables are set at container startup and are NOT updated while the container runs. To pick up new values, the container must restart. The easiest way is kubectl rollout restart deployment/my-app, which triggers a rolling restart without downtime.

💾 Storage

Volumes & Persistent Storage

Container filesystems are ephemeral — when a Pod dies, any data written inside it is gone. If your database Pod crashes and restarts, you don't want to lose all your data! Volumes solve this by providing storage that outlives a container (and sometimes even a Pod).

Storage Concepts

PersistentVolume (PV) Admin creates 100Gi SSD on storage backend PersistentVolumeClaim (PVC) App requests: "I need 10Gi" bound StorageClass Auto-provisions PVs on demand (dynamic) Pod mounts PVC at /data writes persist across restarts
PVs represent physical storage; PVCs are requests for storage; StorageClasses enable dynamic provisioning
# PersistentVolumeClaim — a request for storage from the cluster.

apiVersion: v1
kind: PersistentVolumeClaim

metadata:
  name: my-pvc          # The Pod will reference this PVC by this name.

spec:
  accessModes:
  - ReadWriteOnce       # This volume can be mounted read-write by ONE node at a time.
                        # For databases, RWO is the correct choice — only one node
                        # should write at a time. (RWX requires NFS or similar.)

  storageClassName: fast-ssd
                        # Tells Kubernetes WHICH storage backend to use.
                        # "fast-ssd" is an example StorageClass name defined by
                        # your cluster admin. Cloud clusters come with classes like
                        # "gp2" (AWS), "standard" (GCP), "managed-premium" (Azure).
                        # Omit storageClassName to use the cluster's default.

  resources:
    requests:
      storage: 5Gi      # How much storage you need. Kubernetes finds (or creates)
                        # a PersistentVolume that satisfies this request.

---
# Pod using the PVC

apiVersion: v1
kind: Pod
metadata:
  name: db-pod
spec:
  containers:
  - name: postgres
    image: postgres:15
    volumeMounts:
    - name: data                          # Must match the volume name below (not the PVC name).
      mountPath: /var/lib/postgresql/data # Where Postgres expects to find its data files.
                                          # Data written here persists even if the Pod is deleted.

  volumes:
  - name: data                  # Internal name used to link volumeMount ↔ volume source.
    persistentVolumeClaim:
      claimName: my-pvc         # Name of the PVC defined above.
                                # Kubernetes mounts the bound PersistentVolume here.

Access Modes

ModeShorthandMeaning
ReadWriteOnceRWOMounted read-write by a single node
ReadOnlyManyROXMounted read-only by many nodes
ReadWriteManyRWXMounted read-write by many nodes (needs NFS/CephFS)

🧠 Check Your Understanding — Volumes

Q1. What happens to data stored in a container's filesystem when the Pod is deleted?
  • A) Data is persisted in etcd automatically
  • B) Data is lost — container storage is ephemeral
  • C) Data is moved to another Pod
  • D) Data is stored in the ConfigMap
See Answer & Explanation
Answer: B
Container filesystems are tied to the container's lifecycle. When the Pod is deleted, all data written inside the container is gone. You must use Volumes (PVC) to persist data beyond a Pod's lifecycle.
Q2. A PersistentVolumeClaim (PVC) is best described as:
  • A) The physical disk itself
  • B) A request by a Pod/user for a specific amount and type of storage
  • C) A StorageClass configuration file
  • D) A volume mounted at /tmp inside a container
See Answer & Explanation
Answer: B
PVC is an abstraction — the app just says "I need 5Gi of ReadWriteOnce storage." Kubernetes then binds the PVC to an available PersistentVolume (or creates one dynamically via StorageClass).
Q3. Which access mode allows multiple Pods on different nodes to read and write the same volume simultaneously?
  • A) ReadWriteOnce (RWO)
  • B) ReadOnlyMany (ROX)
  • C) ReadWriteMany (RWX)
  • D) ReadWriteAll (RWA)
See Answer & Explanation
Answer: C
ReadWriteMany (RWX) allows concurrent read-write access from multiple nodes simultaneously. This requires a distributed filesystem like NFS, CephFS, or Azure Files. Most cloud block storage (AWS EBS, GCP PD) only supports RWO — one node at a time.
Q4. What does a StorageClass enable in Kubernetes?
  • A) It classifies Pods by their storage usage
  • B) It defines the type and properties of the storage backend, enabling automatic (dynamic) PersistentVolume provisioning when a PVC is created
  • C) It sets memory limits on containers that use volumes
  • D) It manages ConfigMap data stored on disk
See Answer & Explanation
Answer: B
Without a StorageClass, an admin must manually create PersistentVolumes. With dynamic provisioning, when a PVC is created the StorageClass's provisioner (e.g., the AWS EBS CSI driver) automatically creates and binds a matching PV. Cloud clusters come with default StorageClasses pre-configured.
Q5. A PVC shows STATUS: Bound. What does this mean?
  • A) The PVC is full and cannot accept more data
  • B) The PVC has been successfully matched to a PersistentVolume and is ready for a Pod to use
  • C) The PVC is locked to a specific node
  • D) The Pod using the PVC has been deleted
See Answer & Explanation
Answer: B
A PVC transitions from Pending (waiting for a matching PV) to Bound (matched and ready). Once Bound, the PVC can be mounted by a Pod. If no matching PV exists and no StorageClass provides dynamic provisioning, the PVC stays Pending indefinitely.
Q6. You delete the Pod that was using a PVC. What happens to the PVC and its data?
  • A) The PVC is automatically deleted along with the Pod
  • B) The PVC remains and retains its data — a new Pod can mount the same PVC
  • C) The data on the PVC is automatically wiped for security
  • D) The PVC enters a Terminating state and cannot be reused
See Answer & Explanation
Answer: B
PVCs are independent objects from Pods. Deleting a Pod does not delete its PVCs. The PVC stays Bound to its PV and retains all data, ready to be mounted by a replacement Pod. This is precisely why PVCs exist — to persist data across Pod restarts and replacements.

🌐 Networking

Ingress — Smart HTTP Router

A LoadBalancer Service creates one cloud load balancer per Service — this can get expensive. Ingress is a single entry point that routes incoming HTTP/S traffic to different Services based on the URL path or hostname.

Think of Ingress as a smart reverse proxy or API gateway sitting in front of all your Services.

🌍 Internet :443 Ingress Controller (nginx/traefik) TLS termination Routing Rules: example.com/api → api-service:80 (API backend) example.com/ → web-service:80 (Frontend) admin.example.com → admin-svc:80 (Admin panel)
A single Ingress controller routes traffic to multiple Services based on URL path and hostname
# ingress.yaml

apiVersion: networking.k8s.io/v1   # Ingress moved to this stable API in K8s 1.19+.
kind: Ingress

metadata:
  name: my-ingress
  annotations:                     # Annotations pass extra config to the Ingress Controller.
    nginx.ingress.kubernetes.io/rewrite-target: /
    # ↑ Strips the path prefix before forwarding to the backend.
    # e.g., /api/users → / (the backend only sees /users, not /api/users).
    # Different controllers use different annotation keys.

spec:
  ingressClassName: nginx          # Selects which Ingress Controller handles this object.
                                   # Allows multiple controllers (nginx + traefik) in one cluster.

  tls:                             # Enable HTTPS. The controller terminates TLS here,
  - hosts:                         # so backends only need to handle plain HTTP internally.
    - example.com
    secretName: tls-secret         # A kubernetes.io/tls Secret containing cert.pem + key.pem.
                                   # Create with: kubectl create secret tls tls-secret
                                   #              --cert=cert.pem --key=key.pem

  rules:
  - host: example.com              # Only route requests with this Host header.
                                   # Omit host to match ALL hostnames.
    http:
      paths:
      - path: /api                 # Match requests starting with /api.
        pathType: Prefix           # Prefix = /api, /api/users, /api/v2/... all match.
                                   # Exact = only /api matches (not /api/users).
        backend:
          service:
            name: api-service      # Forward to this Service.
            port:
              number: 80

      - path: /                    # Catch-all: everything not matched above.
        pathType: Prefix           # More specific paths (/api) are matched first.
        backend:
          service:
            name: web-service
            port:
              number: 80

🧠 Check Your Understanding — Ingress

Q1. What is the main advantage of using Ingress over multiple LoadBalancer Services?
  • A) Ingress supports UDP traffic, LoadBalancer does not
  • B) A single Ingress can route to many Services, saving the cost of multiple cloud load balancers
  • C) Ingress is faster than Services
  • D) Ingress works without an Ingress controller
See Answer & Explanation
Answer: B
Each LoadBalancer Service provisions a separate cloud load balancer (which has a cost). Ingress provides one entry point that handles path-based and host-based routing to multiple Services.
Q2. For an Ingress resource to work, what must be installed in the cluster?
  • A) A dedicated Ingress Pod in every namespace
  • B) An Ingress Controller (e.g., nginx-ingress, Traefik)
  • C) An additional cloud load balancer per Ingress rule
  • D) A PersistentVolume for Ingress caching
See Answer & Explanation
Answer: B
The Ingress object is just a configuration declaration. You also need an Ingress Controller — a running Pod (like nginx or Traefik) that reads Ingress rules and actually routes the traffic.
Q3. An Ingress rule with pathType: Exact and path: /api will match which requests?
  • A) /api, /api/users, /api/v2/products
  • B) Only exactly /api — not /api/ or /api/users
  • C) Any URL containing the string "api"
  • D) /api and all its sub-paths
See Answer & Explanation
Answer: B
pathType: Exact matches only the exact path string. pathType: Prefix would match /api and all paths that start with /api (like /api/users). Use Exact for strict routing and Prefix for REST API hierarchies.
Q4. How does an Ingress Controller handle HTTPS/TLS?
  • A) Each backend Service must independently configure HTTPS
  • B) The Ingress Controller terminates TLS at the edge, then forwards plain HTTP to the backend Services
  • C) Ingress does not support HTTPS — use a LoadBalancer Service instead
  • D) TLS is handled by the kube-proxy on each node
See Answer & Explanation
Answer: B
TLS termination at the Ingress Controller is the standard pattern. The controller decrypts incoming HTTPS traffic using the certificate stored in a kubernetes.io/tls Secret, then forwards plain HTTP to the backend. This simplifies backend services — they don't need TLS certificates.
Q5. You have two Ingress Controllers installed (nginx and Traefik). How do you tell a specific Ingress object to use the nginx controller?
  • A) Set spec.controller: nginx in the Ingress YAML
  • B) Set spec.ingressClassName: nginx in the Ingress YAML
  • C) Add a label ingress-controller: nginx to the Ingress object
  • D) Only one Ingress Controller can be active in a cluster at a time
See Answer & Explanation
Answer: B
spec.ingressClassName specifies which IngressClass (and therefore which controller) should handle this Ingress object. Multiple controllers can coexist in one cluster, each watching only the Ingress resources that match their class name.

❤️ Reliability

Health Probes — Self-Healing

How does Kubernetes know if your app is actually working? A container could be running but the app inside might be stuck or not ready to accept traffic. Health Probes let you tell Kubernetes exactly how to check your app's health.

Probe TypeQuestion It AnswersAction on Failure
Liveness"Is the app alive? Should it be restarted?"Restarts the container
Readiness"Is the app ready to serve traffic?"Removes Pod from Service endpoints (stops sending traffic)
Startup"Has the app finished starting up?"Disables liveness/readiness until startup completes
spec:
  containers:
  - name: my-app
    image: my-app:1.0

    # ── Startup Probe ─────────────────────────────────────────────────────
    # Purpose: Give slow-starting apps time to initialize.
    # While this probe is running, liveness and readiness probes are PAUSED.
    # If it fails more than failureThreshold times → container is killed.
    # If it succeeds once → it never runs again; liveness/readiness take over.
    startupProbe:
      httpGet:                  # Probe type: send an HTTP GET request.
        path: /healthz          # The URL path your app exposes for health checks.
        port: 8080              # The port to hit (must be containerPort).
      failureThreshold: 30      # Number of consecutive failures before giving up.
      periodSeconds: 10         # How often to probe (in seconds).
                                # Total timeout = failureThreshold × periodSeconds
                                # = 30 × 10s = 5 minutes before killing the container.

    # ── Liveness Probe ───────────────────────────────────────────────────
    # Purpose: Detect deadlocks or hung processes and restart the container.
    # Your /healthz endpoint should verify the app is responsive
    # (e.g., can process requests), NOT check external dependencies.
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10   # Wait this many seconds after container start
                                # before the FIRST liveness check. Gives the app
                                # time to become ready without failing immediately.
      periodSeconds: 15         # Probe interval: check health every 15 seconds.
      failureThreshold: 3       # Restart the container after 3 consecutive failures.
                                # Total tolerated downtime before restart: 3 × 15s = 45s.

    # ── Readiness Probe ──────────────────────────────────────────────────
    # Purpose: Control when traffic is sent to this Pod.
    # A failing readiness probe does NOT restart the container —
    # it only removes the Pod from the Service's endpoint list.
    # Useful during: startup warm-up, config reload, DB migration, etc.
    readinessProbe:
      httpGet:
        path: /ready            # Separate endpoint from /healthz — can check DB
        port: 8080              # connections, cache warm-up, etc.
      initialDelaySeconds: 5
      periodSeconds: 5          # Check every 5s. When it passes again, the Pod
      failureThreshold: 2       # is automatically added back to Service endpoints.
🔑
Best Practice: Always define a readiness probe for web services. Without it, Kubernetes sends traffic to Pods that are still warming up (loading caches, connecting to DB), causing request failures.

🧠 Check Your Understanding — Health Probes

Q1. Your app takes 60 seconds to connect to the database on startup. Which probe should you configure to prevent premature restarts?
  • A) Liveness Probe with a long initialDelaySeconds
  • B) Startup Probe — it pauses liveness checks until startup succeeds
  • C) Readiness Probe with failureThreshold: 100
  • D) No probe needed, containers always wait
See Answer & Explanation
Answer: B
The Startup Probe was specifically designed for slow-starting apps. While the startup probe is running, liveness probes are disabled, preventing the app from being killed before it finishes initializing.
Q2. A Pod's readiness probe fails. What does Kubernetes do?
  • A) Immediately restarts the container
  • B) Deletes the Pod
  • C) Removes the Pod from the Service's endpoint list (stops routing new traffic to it)
  • D) Drains the node
See Answer & Explanation
Answer: C
A failing readiness probe means "I'm not ready for traffic." Kubernetes keeps the container running (unlike liveness) but removes it from the Service endpoints so no new requests are routed to it.
Q3. Besides HTTP GET, what other probe mechanisms does Kubernetes support?
  • A) SMTP and FTP
  • B) TCP Socket check and exec command (run a command inside the container and check exit code)
  • C) DNS lookup and ICMP ping
  • D) WebSocket and gRPC only
See Answer & Explanation
Answer: B
Kubernetes probes support three mechanisms: httpGet (HTTP status 200-399 = success), tcpSocket (TCP connection succeeds = success), and exec (command exit code 0 = success). Use exec for apps that don't expose an HTTP health endpoint, e.g., a Redis container you check with redis-cli ping.
Q4. Your readiness probe checks the database connection. The database goes down briefly. What happens to your app Pods?
  • A) The Pods are restarted immediately
  • B) The Pods are removed from the Service endpoints (traffic stops going to them), but they are NOT restarted
  • C) Nothing — readiness probes only check the container, not external services
  • D) All Pods are deleted and rescheduled on other nodes
See Answer & Explanation
Answer: B
This is intentional behaviour, but also a design consideration. If the DB goes down, your app Pods fail readiness checks and stop receiving traffic — which looks like an outage even though the app containers are healthy. Many teams prefer readiness probes that only check the app itself, and handle DB unavailability within the app (with retry logic).
Q5. What is the purpose of initialDelaySeconds in a liveness probe?
  • A) It sets the interval between each probe check
  • B) It delays the first probe check after container start — preventing premature restarts while the app is still initializing
  • C) It defines the probe timeout — if no response arrives within this time, it counts as a failure
  • D) It adds a delay between container restart attempts
See Answer & Explanation
Answer: B
Without initialDelaySeconds, the liveness probe starts immediately after the container begins and can kill a slow-starting app before it's even ready. Set this to slightly longer than your app's typical startup time. For unpredictably slow starts, a Startup Probe is a better solution.
Q6. What does failureThreshold: 3 mean in a liveness probe?
  • A) The probe runs for a maximum of 3 seconds
  • B) The container is restarted after 3 consecutive probe failures (individual failures don't count if the next probe succeeds)
  • C) The maximum number of container restarts allowed
  • D) The HTTP status code threshold below which a response is considered a failure
See Answer & Explanation
Answer: B
The failure counter resets to zero after any successful probe. failureThreshold: 3 means the container must fail 3 consecutive probes before action is taken. This prevents flapping — a single transient timeout won't restart your container unnecessarily.

⚖️ Reliability

Resource Requests & Limits

Without constraints, a single runaway container could consume all CPU and memory on a node, starving other containers. Kubernetes lets you specify resource requests and limits per container.

Node (4 CPU, 8Gi RAM) Container A Request: 0.5 CPU Limit: 1.0 CPU Request: 256Mi Limit: 512Mi Container B Request: 1.0 CPU Limit: 2.0 CPU Request: 512Mi Limit: 1Gi Container C (throttled) Request: 2.0 CPU Limit: 3.0 CPU If exceeds limit → CPU throttled / OOMKilled
Requests are used for scheduling; Limits are enforced at runtime to prevent resource hogging
spec:
  containers:
  - name: app
    image: my-app:1.0
    resources:
      requests:           # MINIMUM resources guaranteed to this container.
                          # The Scheduler uses these to decide which node to place the Pod on.
                          # A node is considered "full" when sum(requests) ≥ node capacity.
        memory: "256Mi"   # 256 Mebibytes (1 Mi = 1,048,576 bytes).
                          # Other valid units: Ki, Mi, Gi, Ti  (binary)
                          #                   K, M, G, T       (decimal SI)
        cpu: "250m"       # 250 millicores = 0.25 of one CPU core.
                          # "1" = 1 full CPU core. "500m" = 0.5 CPU.
                          # CPU is a compressible resource — the container can
                          # burst above its request if the node has spare capacity.

      limits:             # MAXIMUM resources this container can use.
                          # Enforced at runtime by the Linux kernel (cgroups).
        memory: "512Mi"   # If the container tries to use more than 512Mi of RAM,
                          # the kernel's OOM killer terminates it immediately.
                          # You'll see: OOMKilled in 'kubectl describe pod'.
        cpu: "500m"       # If the container tries to use more than 0.5 CPU,
                          # the kernel throttles it (slows it down).
                          # Unlike memory, the container is NOT killed for CPU overuse.
📌
CPU vs Memory behaviour: If a container exceeds its CPU limit, it is throttled (slowed down). If it exceeds its memory limit, it is OOMKilled (killed immediately). Always set both requests and limits in production.

🧠 Check Your Understanding — Resources

Q1. The Scheduler uses ________ to decide if a node has enough capacity for a Pod.
  • A) Resource Requests
  • B) Resource Limits
  • C) Current actual CPU usage
  • D) The Pod's label selector
See Answer & Explanation
Answer: A
The scheduler looks at requests (guaranteed minimums) — not limits — when choosing a node. A node is considered "full" when the sum of all Pod requests exceeds its capacity.
Q2. What happens when a container exceeds its memory limit?
  • A) It is CPU-throttled
  • B) The node is drained
  • C) The container is OOMKilled (killed by the Out-Of-Memory killer)
  • D) A warning is logged but nothing happens
See Answer & Explanation
Answer: C
Memory is not compressible — you can't slow down memory usage. When a container hits its memory limit, the Linux kernel's OOM killer terminates it. Kubernetes then restarts it based on the Pod's restart policy.
Q3. What does 250m mean in a CPU resource request?
  • A) 250 megabytes of CPU memory
  • B) 250 millicores — equivalent to 0.25 of one CPU core
  • C) 250 CPU cores reserved for this container
  • D) 250 milliseconds of CPU time per request
See Answer & Explanation
Answer: B
Kubernetes measures CPU in millicores: 1000m = 1 CPU core. So 250m = 0.25 cores, 500m = 0.5 cores, 2000m = 2 cores (equivalent to writing 2). Millicores allow fine-grained CPU allocation — many small services can share a single core.
Q4. What is a LimitRange object used for?
  • A) Setting the maximum number of replicas in a Deployment
  • B) A namespace-level policy that sets default requests/limits for containers and enforces min/max boundaries
  • C) A per-node memory cap set by the cluster admin
  • D) A storage quota for PersistentVolumeClaims only
See Answer & Explanation
Answer: B
A LimitRange sets default resource requests and limits for containers in a namespace that don't specify their own. It also enforces min/max boundaries — for example, preventing a container from requesting more than 4 CPU cores. This works together with ResourceQuota for namespace governance.
Q5. A Pod has no resource requests set. What risk does this create?
  • A) The Pod cannot start without resource requests
  • B) The Scheduler may place the Pod on an already overloaded node, causing resource contention and poor performance for all workloads on that node
  • C) The Pod automatically gets 1 CPU and 1Gi memory
  • D) The Pod is placed in the kube-system namespace as a fallback
See Answer & Explanation
Answer: B
Without requests, the Scheduler has no signal about the Pod's resource needs and may place it on a full node. The Pod also gets the lowest Quality of Service (QoS) class: BestEffort — the first to be evicted when the node runs low on memory. Always set requests in production.
Q6. What are the three QoS (Quality of Service) classes Kubernetes assigns to Pods, and which is highest priority?
  • A) Gold, Silver, Bronze — Gold is highest
  • B) Guaranteed (requests = limits), Burstable (requests < limits), BestEffort (no requests/limits) — Guaranteed is highest priority and last to be evicted
  • C) Critical, Normal, Background — Critical is highest
  • D) QoS is determined by the cloud provider, not Kubernetes
See Answer & Explanation
Answer: B
Kubernetes automatically assigns QoS based on your resource settings. Guaranteed: every container has identical requests and limits — this Pod is never evicted due to resource pressure. Burstable: some containers have requests or limits. BestEffort: no requests or limits — evicted first.

⭐ Advanced

StatefulSets — For Stateful Applications

Deployments are great for stateless apps (like web servers), where all Pods are identical and interchangeable. But what about databases like PostgreSQL or Redis clusters? Each instance needs a stable identity, stable network hostname, and stable storage. Enter StatefulSets.

FeatureDeploymentStatefulSet
Pod identityRandom names (my-app-xk2pz)Ordered names (my-db-0, my-db-1, my-db-2)
StorageShared or noneEach Pod gets its own persistent volume
Scaling orderRandomOrdered (0→1→2 for scale-up, 2→1→0 for scale-down)
DNSRandom Pod DNSStable: my-db-0.my-svc.default.svc.cluster.local
Use caseWeb servers, APIsDatabases, message queues (Kafka, ZooKeeper)
apiVersion: apps/v1
kind: StatefulSet

metadata:
  name: postgres

spec:
  serviceName: postgres   # Name of a "headless" Service (clusterIP: None) that gives
                          # each Pod a stable DNS entry:
                          # postgres-0.postgres.default.svc.cluster.local
                          # postgres-1.postgres.default.svc.cluster.local
                          # This lets Pods address each other by name (needed for clustering).

  replicas: 3             # Pods are created in order: postgres-0, then postgres-1, then postgres-2.
                          # Each waits to be Running+Ready before the next starts.

  selector:
    matchLabels:
      app: postgres

  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data

  volumeClaimTemplates:   # KEY DIFFERENCE from Deployments:
                          # Each Pod gets its OWN PVC automatically.
                          # postgres-0 → PVC "data-postgres-0"  (10Gi)
                          # postgres-1 → PVC "data-postgres-1"  (10Gi)
                          # postgres-2 → PVC "data-postgres-2"  (10Gi)
                          # These PVCs persist even if the StatefulSet is deleted,
                          # protecting your data from accidental loss.
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

🧠 Check Your Understanding — StatefulSets

Q1. You are deploying a 3-node Kafka cluster on Kubernetes. Which controller should you use?
  • A) Deployment
  • B) ReplicaSet
  • C) StatefulSet
  • D) DaemonSet
See Answer & Explanation
Answer: C
Kafka nodes need stable identities (broker IDs), stable storage (topic partitions), and ordered startup/shutdown. All these are StatefulSet features. A Deployment would create interchangeable Pods without stable identity.
Q2. When you scale a StatefulSet from 3 to 5 replicas, which Pods are created?
  • A) 2 new Pods with random names
  • B) Pod index 3 first, then Pod index 4 (in order)
  • C) All 5 Pods are recreated
  • D) 2 new Pods on a dedicated node
See Answer & Explanation
Answer: B
StatefulSets maintain ordered deployment. New Pods are created sequentially starting from the next index (3, then 4). Each new Pod must be Running and Ready before the next one starts.
Q3. When scaling a StatefulSet DOWN from 5 to 3 replicas, which Pods are deleted first?
  • A) Pods 0 and 1 are deleted first (lowest indices)
  • B) Pods 4 and 3 are deleted first (highest indices, in reverse order)
  • C) Two random Pods are deleted simultaneously
  • D) All 5 Pods are deleted and 3 new ones are created
See Answer & Explanation
Answer: B
StatefulSets scale down in reverse ordinal order: Pod 4 is terminated first, then Pod 3. Each Pod must be fully terminated before the next one is deleted. This mirrors how distributed systems like database replicas or Kafka brokers expect to be decommissioned — from the "newest" node backward.
Q4. What happens to the PersistentVolumeClaims when you delete a StatefulSet?
  • A) All PVCs are automatically deleted along with the StatefulSet to free storage
  • B) PVCs are NOT deleted — they persist to protect data from accidental loss, and must be deleted manually
  • C) PVCs are transferred to the default namespace
  • D) PVCs are converted into ConfigMaps
See Answer & Explanation
Answer: B
StatefulSet PVCs are intentionally not garbage-collected when the StatefulSet is deleted. This is a safety mechanism — your database data survives even if the StatefulSet is accidentally deleted. You must manually delete PVCs when you truly want to remove the data.
Q5. What is the role of the serviceName field in a StatefulSet spec?
  • A) It creates a LoadBalancer Service for the StatefulSet
  • B) It references a headless Service that provides stable DNS names for each individual Pod (e.g., pod-0.svc-name.namespace.svc.cluster.local)
  • C) It sets the hostname visible inside the container
  • D) It links the StatefulSet to a specific StorageClass
See Answer & Explanation
Answer: B
The serviceName must point to a headless Service (clusterIP: None). This Service does not load balance but instead creates a DNS A record per Pod: postgres-0.postgres.default.svc.cluster.local. This stable, predictable hostname is essential for database clustering — nodes need to reach each other by a known address.

⭐ Advanced

DaemonSets — One Pod Per Node

A DaemonSet ensures that exactly one copy of a Pod runs on every node (or a selected subset of nodes). Whenever a new node joins the cluster, the DaemonSet automatically places a Pod on it. When a node is removed, the Pod is garbage-collected.

💡
DaemonSets are perfect for cluster-wide infrastructure concerns: log collectors (Fluentd), metrics exporters (Prometheus Node Exporter), network plugins (Cilium, Calico), and security agents.
DaemonSet: one log-collector Pod per Node Node 1 📋 log-collector Pod app Pod A app Pod B Node 2 📋 log-collector Pod db Pod C Node 3 (new) ✨ 📋 log-collector Pod (auto-placed!)
DaemonSet automatically places a Pod on every node, including newly added nodes
apiVersion: apps/v1
kind: DaemonSet         # Automatically places ONE Pod per node (or per selected nodes).

metadata:
  name: log-collector

spec:
  selector:
    matchLabels:
      app: log-collector

  template:
    metadata:
      labels:
        app: log-collector
    spec:
      containers:
      - name: fluentd
        image: fluentd:v1.16
        volumeMounts:
        - name: varlog
          mountPath: /var/log   # Inside the container — fluentd reads logs from here.

      volumes:
      - name: varlog
        hostPath:               # hostPath mounts a directory FROM the node's filesystem
          path: /var/log        # INTO the container. This gives the log-collector access
                                # to all logs written by processes on that node.
                                # ⚠️ Use hostPath carefully — it breaks Pod portability and
                                # can expose sensitive node data. Only use it for infra Pods
                                # like log collectors and monitoring agents.

🧠 Check Your Understanding — DaemonSets

Q1. You add a new node to a cluster that has a DaemonSet. What happens?
  • A) You must manually create a Pod on the new node
  • B) The DaemonSet automatically schedules its Pod on the new node
  • C) The existing DaemonSet Pods are redistributed
  • D) The DaemonSet must be redeployed
See Answer & Explanation
Answer: B
The DaemonSet controller continuously watches for new nodes. When a new node joins the cluster, it automatically creates a DaemonSet Pod on it. No manual intervention needed.
Q2. Which of the following is the most appropriate use case for a DaemonSet?
  • A) Running a web API with 3 replicas for high availability
  • B) Running a Prometheus node-exporter on every node to collect hardware and OS metrics
  • C) Running a one-time database migration job
  • D) Deploying a stateful PostgreSQL cluster
See Answer & Explanation
Answer: B
The node-exporter is the classic DaemonSet use case — it must run on every node to scrape that node's metrics. Other common examples: Fluentd/Filebeat for log collection, Cilium/Calico network agents, and security scanners. Web APIs belong in Deployments; databases in StatefulSets; one-time tasks in Jobs.
Q3. Can you configure a DaemonSet to run on only a specific subset of nodes?
  • A) No — DaemonSets always run on every single node in the cluster without exception
  • B) Yes — using nodeSelector, node affinity rules, or taints and tolerations to target specific nodes
  • C) Only if the nodes are in the same cloud availability zone
  • D) Only if you also create a matching NodePort Service
See Answer & Explanation
Answer: B
You can use spec.template.spec.nodeSelector to target nodes with specific labels (e.g., only GPU nodes). Taints and tolerations also work — for example, DaemonSet Pods often have tolerations for node.kubernetes.io/not-ready so they get scheduled even on problematic nodes (critical for network plugins).

⭐ Advanced

Jobs & CronJobs — Batch Workloads

Deployments run continuously (web servers, APIs). But sometimes you need to run a task to completion — like a database migration, a report generator, or a one-time data import. That's what Jobs are for.

A CronJob runs a Job on a scheduled interval — just like a Unix cron task, but for Kubernetes.

# job.yaml — run a database migration exactly once

apiVersion: batch/v1
kind: Job               # Manages Pods that run to completion (not indefinitely).

metadata:
  name: db-migration

spec:
  completions: 1        # How many Pods must complete successfully for the Job to be "done".
                        # Set completions: 5 to process 5 independent work items.

  parallelism: 1        # How many Pods can run simultaneously.
                        # completions:5, parallelism:2 → runs 2 at a time, 3 rounds total.

  backoffLimit: 3       # Maximum number of retries before the Job is marked as Failed.
                        # Each failed Pod = 1 retry. After 3 failures, Kubernetes gives up.

  template:
    spec:
      restartPolicy: OnFailure  # OnFailure = restart the container on the SAME Pod.
                                # Never = create a new Pod for each retry (cleaner logs).
                                # ⚠️ 'Always' is NOT allowed — it would loop forever.
      containers:
      - name: migration
        image: my-app:1.0
        command: ["python", "manage.py", "migrate"]
        # command overrides the container image's default ENTRYPOINT + CMD.
        # Each element in the list is a separate argument (no shell string splitting).

---
# cronjob.yaml — generate a report every day at midnight

apiVersion: batch/v1
kind: CronJob           # Creates a new Job object on a time-based schedule.

metadata:
  name: daily-report

spec:
  schedule: "0 0 * * *" # Standard cron syntax (5 fields):
                        # ┌─ minute (0-59)
                        # │ ┌─ hour (0-23)
                        # │ │ ┌─ day of month (1-31)
                        # │ │ │ ┌─ month (1-12)
                        # │ │ │ │ ┌─ day of week (0-7, 0=Sunday)
                        # 0 0 * * *  → at 00:00 (midnight) every day
                        # */5 * * * * → every 5 minutes
                        # 0 9 * * 1  → every Monday at 09:00

  jobTemplate:          # The Job spec to create on each schedule trigger.
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: report-gen
            image: report-generator:1.0
            command: ["python", "generate_report.py"]
🕐
Cron schedule format: minute hour day-of-month month day-of-week. Example: "*/5 * * * *" = every 5 minutes. Use crontab.guru to build cron expressions visually.

🧠 Check Your Understanding — Jobs & CronJobs

Q1. Why must the restartPolicy in a Job's Pod template be OnFailure or Never?
  • A) Jobs don't support restart policies
  • B) Jobs run to completion — using Always would loop forever after success
  • C) The default is OnFailure so no change is needed
  • D) Jobs require external restart management
See Answer & Explanation
Answer: B
The default restartPolicy: Always is used for long-running services. For Jobs, when the container finishes successfully (exit code 0), you want it to stay done — not restart. Hence OnFailure or Never.
Q2. What cron schedule runs a task every 30 minutes?
  • A) "30 * * * *"
  • B) "*/30 * * * *"
  • C) "0 30 * * *"
  • D) "* */30 * * *"
See Answer & Explanation
Answer: B
"*/30 * * * *" means "every 30 minutes." The */n syntax means "every n units." "30 * * * *" would run only at minute 30 of every hour (once per hour).
Q3. A Job has completions: 10 and parallelism: 3. How does it execute?
  • A) 10 Pods run at the same time and 3 of them must succeed
  • B) 3 Pods run simultaneously; as each succeeds a new Pod starts — continuing until 10 total successes are recorded
  • C) 3 Pods run and each must complete successfully 10 times in a row
  • D) 10 Pods start, and the Job finishes when the first 3 complete
See Answer & Explanation
Answer: B
Think of it as a work queue: parallelism is the number of concurrent workers, completions is the total items to process. 3 workers run simultaneously; when one finishes, a new one starts immediately — until the total reaches 10. This pattern is ideal for parallel batch processing.
Q4. A CronJob has schedule: "0 9 * * 1". When does it run?
  • A) Every 9 hours on Mondays
  • B) Every Monday at 09:00
  • C) On the 9th of every month at midnight
  • D) At 01:00 every Monday and on the 9th of every month
See Answer & Explanation
Answer: B
Reading left to right: minute=0, hour=9, day-of-month=*, month=*, day-of-week=1 (Monday). So: at minute 0 of hour 9, on any day of the month, in any month, but only when the day of week is Monday → every Monday at 09:00.

🔒 Advanced

RBAC — Access Control

Role-Based Access Control (RBAC) controls who can do what in your Kubernetes cluster. Without RBAC, any user or service could read secrets, delete Pods, or modify any resource — a serious security risk.

RBAC has four key objects:

  • Role — Grants permissions within a specific namespace
  • ClusterRole — Grants permissions cluster-wide (or defines reusable roles)
  • RoleBinding — Attaches a Role to a User/ServiceAccount in a namespace
  • ClusterRoleBinding — Attaches a ClusterRole cluster-wide
👤 Subject (User / SA / Group) RoleBinding binds subject to role Role apiGroups: [""] resources: ["pods"] verbs: [get, list]
RBAC: Subject + RoleBinding + Role = "Alice can get/list Pods in the dev namespace"
# Role: read-only access to Pods in the 'dev' namespace.
# Think of a Role as a JOB DESCRIPTION (what actions are allowed).

apiVersion: rbac.authorization.k8s.io/v1
kind: Role              # Namespace-scoped. For cluster-wide: use ClusterRole.

metadata:
  namespace: dev        # This Role only grants permissions WITHIN the 'dev' namespace.
  name: pod-reader      # Arbitrary name used by RoleBindings to reference this Role.

rules:                  # A list of permission rules. Subject gets ALL of these.
- apiGroups: [""]       # "" = the core API group (Pods, Services, ConfigMaps, Secrets).
                        # For apps/v1 resources: apiGroups: ["apps"]
                        # For batch/v1: apiGroups: ["batch"]
  resources: ["pods", "pods/log"]
                        # Which resource types are covered.
                        # "pods/log" is a sub-resource (kubectl logs).
                        # "pods/exec" would allow kubectl exec.
  verbs: ["get", "list", "watch"]
                        # What actions are allowed on those resources.
                        # get    = read one object      (kubectl get pod my-pod)
                        # list   = list all objects     (kubectl get pods)
                        # watch  = stream changes       (kubectl get pods -w)
                        # create = create new objects
                        # update = modify existing      (kubectl apply)
                        # patch  = partial update
                        # delete = remove objects

---
# RoleBinding: grants the pod-reader Role TO Alice, in the 'dev' namespace.
# Think of a RoleBinding as an EMPLOYMENT CONTRACT (who gets which job description).

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding

metadata:
  namespace: dev
  name: alice-pod-reader

subjects:               # WHO gets the permissions (can list multiple subjects).
- kind: User            # User = human identity authenticated by the cluster.
                        # Other kinds: Group (team of users), ServiceAccount (for Pods/apps).
  name: alice           # Must match the username in the user's TLS certificate or OIDC token.
  apiGroup: rbac.authorization.k8s.io

roleRef:                # WHAT Role/ClusterRole to grant. Immutable after creation.
  kind: Role            # Use ClusterRole here if referencing a ClusterRole.
  name: pod-reader      # Name of the Role defined above.
  apiGroup: rbac.authorization.k8s.io

🧠 Check Your Understanding — RBAC

Q1. What is the difference between a Role and a ClusterRole?
  • A) Role is for admins; ClusterRole is for regular users
  • B) Role is namespace-scoped; ClusterRole applies cluster-wide or across all namespaces
  • C) ClusterRole supports more verbs than Role
  • D) There is no functional difference
See Answer & Explanation
Answer: B
A Role only grants permissions within the namespace where it is defined. A ClusterRole grants permissions across all namespaces or for cluster-scoped resources (like Nodes, PersistentVolumes).
Q2. If you want a CI/CD pipeline (running as a ServiceAccount) to deploy to the "production" namespace, what RBAC objects do you need?
  • A) A ClusterRole and ClusterRoleBinding
  • B) A Role (in production namespace) + RoleBinding (binding the ServiceAccount to that Role)
  • C) Just a ServiceAccount — it has all permissions by default
  • D) A NetworkPolicy + RBAC Policy
See Answer & Explanation
Answer: B
For namespace-scoped access, use Role + RoleBinding. Using ClusterRole + ClusterRoleBinding would give the CI/CD service account access to all namespaces — more than needed. Prefer the principle of least privilege.
Q3. What is a ServiceAccount in Kubernetes?
  • A) A human administrator account with cluster-admin permissions
  • B) An identity used by Pods and applications (not humans) to authenticate with the Kubernetes API
  • C) An external OAuth or OIDC provider for SSO
  • D) A billing account linked to cloud resource consumption
See Answer & Explanation
Answer: B
Every Pod runs with a ServiceAccount (the default ServiceAccount if not specified). Kubernetes automatically mounts a token for this ServiceAccount inside the Pod at /var/run/secrets/kubernetes.io/serviceaccount/token. The app can use this token to call the Kubernetes API. Use RBAC to control what that ServiceAccount is allowed to do.
Q4. What verbs would you include in a Role to allow a user to run kubectl apply and kubectl delete?
  • A) get, list, watch
  • B) exec, port-forward
  • C) create, update, patch, delete
  • D) bind, escalate
See Answer & Explanation
Answer: C
kubectl apply needs create (new objects) + update/patch (existing objects). kubectl delete needs delete. Read-only operations use get, list, watch. Grant only what is needed — a developer who only reads logs needs get, list, and pods/log, not delete.
Q5. By default, what can the default ServiceAccount in a namespace do via the Kubernetes API?
  • A) Full cluster-admin access
  • B) Read access to all objects in its namespace
  • C) Very limited access — essentially nothing beyond discovering the API itself
  • D) It depends entirely on the cloud provider
See Answer & Explanation
Answer: C
In a standard cluster, the default ServiceAccount has no RBAC bindings and therefore cannot perform any meaningful operations on cluster resources. This is intentional — the principle of least privilege. You must explicitly create RoleBindings to grant your app the specific permissions it needs.
Q6. What is the "principle of least privilege" in the context of Kubernetes RBAC?
  • A) Give every developer cluster-admin access to avoid friction
  • B) Grant only the minimum permissions necessary for a user or service to perform its specific job — no more, no less
  • C) Use ClusterRoles for all permissions to simplify management
  • D) Limit RBAC to only the production namespace
See Answer & Explanation
Answer: B
Least privilege is a security principle: if an app only needs to read ConfigMaps, don't give it permission to delete Secrets. If a user only needs to view Pods, don't give them deployment permissions. This limits the blast radius of a compromised token or insider threat. Regularly audit your RBAC bindings.

⭐ Advanced

Helm — The Package Manager for Kubernetes

Deploying a real application might require 10-15 YAML files for Deployments, Services, ConfigMaps, Ingress, Secrets, RBAC, etc. Managing all of these individually is tedious. Helm is the package manager for Kubernetes that bundles all related YAML files into a Chart and lets you install, upgrade, and uninstall applications with a single command.

The Helm mascot is a ship's helm (steering wheel). A Chart is a package, a Release is an installed instance, and a Repository is where Charts are stored (like npm registry for Node.js).
Helm Chart Chart.yaml (metadata) values.yaml (defaults) templates/deploy.yaml templates/service.yaml + Custom Values replicas: 5 image: my-app:2.0 ingress.enabled: true helm install Kubernetes Deployment ✅ Service ✅ Ingress ✅
Helm merges a Chart's templates with your custom values.yaml to produce Kubernetes manifests
# brew install helm
#   Installs the Helm CLI on macOS via Homebrew.
#   Linux: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
brew install helm

# helm repo add <alias> <url>
#   Registers a Chart repository under a local alias.
#   Bitnami is a popular repo with production-ready charts
#   for PostgreSQL, Redis, Kafka, WordPress, etc.
helm repo add bitnami https://charts.bitnami.com/bitnami

# helm repo update
#   Fetches the latest chart index from all registered repos.
#   Run this before searching or installing to get fresh versions.
helm repo update

# helm search repo <chart-name>
#   Searches locally cached repo indexes.
#   Shows available versions, app version, and description.
#   Add --versions to list all available chart versions.
helm search repo bitnami/postgresql

# helm install <release-name> <chart> [flags]
#   Installs the chart and names this instance "my-postgres".
#   --set key=value overrides default values from values.yaml.
#   --namespace deploys into a specific namespace (must exist).
#   Helm records the full rendered YAML as a "release" in a Secret.
helm install my-postgres bitnami/postgresql \
  --set auth.postgresPassword=secret \
  --namespace production

# helm upgrade <release-name> <chart> [flags]
#   Updates an existing release with new values or a new chart version.
#   Helm performs a diff and applies only the changed Kubernetes objects.
#   Add --install to create the release if it doesn't exist yet.
helm upgrade my-postgres bitnami/postgresql \
  --set image.tag=15.3.0

# helm list -A
#   Lists all Helm releases across all namespaces.
#   Shows release name, namespace, revision, status, and chart version.
#   -A = --all-namespaces (same as kubectl's -A flag)
helm list -A

# helm rollback <release-name> <revision>
#   Rolls the release back to a specific revision number.
#   Each install/upgrade increments the revision counter.
#   Use 'helm history my-postgres' to see all past revisions.
helm rollback my-postgres 1

# helm uninstall <release-name>
#   Deletes all Kubernetes objects that were created by this release.
#   Also removes Helm's internal release history from the cluster.
helm uninstall my-postgres

🧠 Check Your Understanding — Helm

Q1. What is a Helm "Release"?
  • A) A new version of Helm itself
  • B) A specific installed instance of a Helm Chart in a cluster
  • C) The collection of all Charts in a repository
  • D) A Git tag for Kubernetes manifests
See Answer & Explanation
Answer: B
When you run helm install my-postgres bitnami/postgresql, "my-postgres" becomes the Release name. You can install the same Chart multiple times with different Release names (e.g., one for dev, one for prod), each with its own configuration.
Q2. Where are Helm chart default configuration values stored?
  • A) In Chart.yaml
  • B) In the Kubernetes ConfigMap
  • C) In values.yaml (overridable at install/upgrade time)
  • D) In a Helm secret in etcd
See Answer & Explanation
Answer: C
values.yaml contains the default values for a chart's templates. At install or upgrade time you can override them with --set key=value or by supplying your own values file with -f my-values.yaml.
Q3. How do you override a Helm chart's default configuration at install time without modifying the chart itself?
  • A) Edit the template files in the chart directory directly
  • B) Use --set key=value flags or -f my-values.yaml with helm install or helm upgrade
  • C> Create a Kubernetes ConfigMap with the same name as values.yaml
  • D) Run helm values override <chart>
See Answer & Explanation
Answer: B
Helm is designed so you never need to edit a chart's source to customise it. Use --set replicaCount=5 for simple overrides, or -f production-values.yaml for a full set of environment-specific settings. This keeps the upstream chart upgradeable without merge conflicts.
Q4. What does helm install --dry-run --debug my-release bitnami/nginx do?
  • A) Installs the chart and then immediately deletes it
  • B) Renders the chart templates with your values and prints the resulting Kubernetes manifests to stdout WITHOUT creating any resources in the cluster
  • C) Validates that the chart is syntactically correct
  • D) Shows estimated resource usage before installing
See Answer & Explanation
Answer: B
--dry-run simulates the install and renders the full YAML output that would be applied to the cluster, without actually applying anything. This is invaluable for reviewing what a chart will create, catching misconfigured values, or generating manifests for GitOps workflows.
Q5. Where does Helm store release history inside the Kubernetes cluster?
  • A) In a dedicated helm-releases namespace as Deployments
  • B) As Kubernetes Secrets (base64-encoded) in the same namespace as the release
  • C) In etcd directly as raw key-value pairs
  • D) In a file on the machine where you ran helm install
See Answer & Explanation
Answer: B
Helm 3 stores each release revision as a Kubernetes Secret of type helm.sh/release.v1 in the release's namespace. Each upgrade creates a new Secret, and helm history <release> reads these Secrets to show the revision history. This means Helm state lives in-cluster with no external storage needed.
Q6. What is the difference between helm upgrade and helm upgrade --install?
  • A) They are identical — --install is just a legacy flag
  • B) helm upgrade fails if the release doesn't exist yet; --install creates the release if it doesn't exist, making it safe to use in CI/CD pipelines
  • C> --install upgrades the Helm CLI itself, not the chart
  • D) --install forces a reinstall even if nothing has changed
See Answer & Explanation
Answer: B
helm upgrade --install my-release my-chart is the idiomatic CI/CD command: if the release exists it upgrades it; if not it creates it. This makes your pipeline script work for both first-time deployments and subsequent updates without needing separate install and upgrade commands.
🎉

You've completed the Kubernetes Tutorial!

You now understand every major Kubernetes concept from architecture to Helm. The best way to learn is to practice — set up a local cluster and deploy something real!

📖 Official Docs 🖥️ Minikube (Local K8s) 🎯 Practice Online