Chapter 59: Kubernetes Pods in Depth: Architecture, YAML Anatomy and Pod Types
59.1 Cluster and pod architecture: what really happens on kubectl apply
Why: the problem
Suppose we are at Swiggy on a Friday night. An engineer types kubectl apply -f order-api.yaml. Ten seconds later, three pods are running on three machines. Who picked the machines? Who pulled the image? Who gave each pod an IP address? If you cannot answer, you cannot debug a pod stuck in Pending at 9 pm. In chapter 15 and chapter 18 we used kubectl. In this lesson we open the box. We follow one pod from your terminal to a running container, step by step, on a real kind cluster. You will see every component with your own eyes: the API server, etcd, the scheduler, the controller manager, the kubelet, the container runtime and the tiny pause container inside each pod.
What: every term in plain English
- Cluster — A group of machines that Kubernetes treats as one big computer.
- Control plane — The brain of the cluster. It stores the wanted state and makes decisions. It does not run your app.
- Worker node — A machine that runs your pods. In kind, each node is a Docker container.
- kube-apiserver — The front door. Every request (kubectl, kubelet, controllers) goes through it. It checks who you are and saves objects.
- etcd — A small, safe key-value database. It is the memory of the cluster. All objects live here.
- kube-scheduler — Picks a node for each new pod. It looks at free CPU, memory, labels and rules.
- kube-controller-manager — Runs many control loops. Each loop compares wanted state with real state and fixes the difference.
- kubelet — An agent on every node. It asks the API server which pods belong to its node and starts them.
- Container runtime — The program that actually runs containers. Today it is usually containerd.
- kube-proxy — An agent on every node that sets network rules so Service IPs reach pods.
- Pause container — A tiny container that holds the pod's network namespace. App containers join it, so they share one IP.
- Watch — A long-lived connection. Components watch the API server and react the moment something changes.
How: step by step
- You run kubectl apply. kubectl reads the YAML and sends it to kube-apiserver over HTTPS.
- The API server checks and saves. It checks your identity, your permissions (RBAC) and the YAML. Then it writes the object to etcd.
- Controllers create pods. The Deployment controller creates a ReplicaSet. The ReplicaSet controller creates Pod objects with no node yet.
- The scheduler picks a node. It sees pods with an empty nodeName, scores the nodes, and writes the chosen node into the pod.
- The kubelet starts the pod. The kubelet on that node sees the new pod, asks containerd to start the pause container, then the app containers.
- Network and status. The CNI plugin gives the pod an IP. The kubelet reports Running back to the API server.
- Self-healing loops. If a pod dies, the ReplicaSet controller sees 2 instead of 3 and creates a new pod. The loop never stops.
Example 1: Create a kind cluster and see every control plane part
Goal. Create a kind cluster and see every control plane part.
kind-3node.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: ch59
nodes:
- role: control-plane
- role: worker
- role: worker
look.sh
kind create cluster --config kind-3node.yaml
kubectl cluster-info --context kind-ch59
kubectl get nodes -o wide
kubectl get pods -n kube-system -o wide
docker ps --format 'table {{.Names}}\t{{.Image}}'
What each line does
kind: Cluster— This file is for kind, not for Kubernetes. It describes the local cluster shape.apiVersion: kind.x-k8s.io/v1alpha4— The version of the kind config format.name: ch59— The cluster name. The kubectl context becomes kind-ch59.role: control-plane— One node runs the brain: API server, etcd, scheduler, controller manager.role: worker (two times)— Two nodes for your pods. Now the scheduler has a real choice.kubectl get pods -n kube-system— On kind and kubeadm, the control plane parts run as pods in the kube-system namespace.docker ps— Each kind node is just a Docker container. That is why kind starts in about a minute.
Green output
$ kind create cluster --config kind-3node.yaml
Creating cluster "ch59" ...
✓ Ensuring node image (kindest/node:v1.31.0)
✓ Preparing nodes
✓ Writing configuration
✓ Starting control-plane
✓ Installing CNI
✓ Installing StorageClass
✓ Joining worker nodes
Set kubectl context to "kind-ch59"
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ch59-control-plane Ready control-plane 62s v1.31.0
ch59-worker Ready worker 41s v1.31.0
ch59-worker2 Ready worker 41s v1.31.0
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-7db6d8ff4d-6x2mn 1/1 Running 0 55s
coredns-7db6d8ff4d-v8k4p 1/1 Running 0 55s
etcd-ch59-control-plane 1/1 Running 0 70s
kindnet-4dq8r 1/1 Running 0 41s
kube-apiserver-ch59-control-plane 1/1 Running 0 70s
kube-controller-manager-ch59-control-plane 1/1 Running 0 70s
kube-proxy-9kq2l 1/1 Running 0 41s
kube-scheduler-ch59-control-plane 1/1 Running 0 70s
$ docker ps --format 'table {{.Names}}\t{{.Image}}'
NAMES IMAGE
ch59-worker kindest/node:v1.31.0
ch59-worker2 kindest/node:v1.31.0
ch59-control-plane kindest/node:v1.31.0
Expected result. You can point at each part of the brain. kindnet is the CNI plugin; kube-proxy runs once per node.
Example 2: Follow one Deployment from apply to running pods using events
Goal. Follow one Deployment from apply to running pods using events.
order-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
labels:
app: order-api
spec:
replicas: 3
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
follow.sh
kubectl apply -f order-api.yaml
kubectl get events --sort-by=.metadata.creationTimestamp | tail -15
kubectl get deploy,rs,pods -l app=order-api -o wide
What each line does
apiVersion: apps/v1— Deployments live in the apps API group, version v1.kind: Deployment— We ask for a Deployment. It will create a ReplicaSet, which creates pods.replicas: 3— Wanted state: three pods.selector.matchLabels— The Deployment owns pods that carry app: order-api.template— The pod blueprint. Every pod is a copy of this.image: nginx:1.27— containerd pulls this image on the chosen node.get events --sort-by— Events are the cluster diary. Each line shows which component did what.
Green output
$ kubectl get events --sort-by=.metadata.creationTimestamp | tail -9
LAST SEEN TYPE REASON OBJECT MESSAGE
12s Normal ScalingReplicaSet deployment/order-api Scaled up replica set order-api-5d8c9b7f6 to 3
12s Normal SuccessfulCreate replicaset/order-api-5d8c9b7f6 Created pod: order-api-5d8c9b7f6-k2x7p
12s Normal Scheduled pod/order-api-5d8c9b7f6-k2x7p Successfully assigned default/order-api-5d8c9b7f6-k2x7p to ch59-worker2
11s Normal Pulling pod/order-api-5d8c9b7f6-k2x7p Pulling image "nginx:1.27"
6s Normal Pulled pod/order-api-5d8c9b7f6-k2x7p Successfully pulled image "nginx:1.27" in 4.9s
6s Normal Created pod/order-api-5d8c9b7f6-k2x7p Created container web
6s Normal Started pod/order-api-5d8c9b7f6-k2x7p Started container web
$ kubectl get pods -l app=order-api -o wide
NAME READY STATUS IP NODE
order-api-5d8c9b7f6-k2x7p 1/1 Running 10.244.2.3 ch59-worker2
order-api-5d8c9b7f6-m9qzt 1/1 Running 10.244.1.4 ch59-worker
order-api-5d8c9b7f6-r4w8d 1/1 Running 10.244.2.4 ch59-worker2
Expected result. The events match the 7 steps: deployment controller, replicaset controller, scheduler, then kubelet with containerd.
Example 3: Look inside a node and find the pause container
Goal. Look inside a node and find the pause container.
inside-node.sh
POD=$(kubectl get pod -l app=order-api -o jsonpath='{.items[0].metadata.name}')
NODE=$(kubectl get pod $POD -o jsonpath='{.spec.nodeName}')
echo "pod $POD is on $NODE"
docker exec $NODE crictl pods --name $POD
docker exec $NODE crictl ps --pod $(docker exec $NODE crictl pods --name $POD -q)
docker exec $NODE crictl images | grep pause
docker exec $NODE ls /etc/kubernetes/manifests
docker exec ch59-control-plane ls /etc/kubernetes/manifests
What each line does
jsonpath— Picks one field from the JSON answer. Here, the first pod name and its node.docker exec $NODE— Runs a command inside the kind node container, like SSH into a real node.crictl pods— Talks to containerd directly. It shows the pod sandbox (the pause container).crictl ps --pod— Lists the app containers inside that sandbox.crictl images | grep pause— The pause image is tiny, under 1 MB. It only holds the namespaces./etc/kubernetes/manifests— On the control plane this folder holds the static pod files for the API server, etcd, scheduler and controller manager. Lesson 59.7 covers static pods.
Green output
pod order-api-5d8c9b7f6-k2x7p is on ch59-worker2
POD ID CREATED STATE NAME NAMESPACE
3f1c2a9b8d7e6 2 minutes ago Ready order-api-5d8c9b7f6-k2x7p default
CONTAINER IMAGE STATE NAME POD ID
a1b2c3d4e5f60 39286ab8a5e14 Running web 3f1c2a9b8d7e6
registry.k8s.io/pause 3.10 873ed75102791 320kB
$ docker exec ch59-control-plane ls /etc/kubernetes/manifests
etcd.yaml
kube-apiserver.yaml
kube-controller-manager.yaml
kube-scheduler.yaml
Expected result. You saw the real layers: pod sandbox (pause), app container, and the control plane running as static pods.
Every command, step by step (kind lab with kubectl)
-
Check the cluster is healthy
kubectl get --raw='/readyz?verbose' | tail -4What it does: Asks the API server to run its own health checks.
[+]poststarthook/apiservice-openapiv3-controller ok [+]shutdown ok readyz check passed -
Read objects straight from etcd keys
docker exec ch59-control-plane sh -c 'ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key get /registry/deployments/default --prefix --keys-only'What it does: Proves that the Deployment is stored in etcd under /registry. Only for learning; never edit etcd by hand.
/registry/deployments/default/order-api -
Stop the scheduler and watch Pending
docker exec ch59-control-plane mv /etc/kubernetes/manifests/kube-scheduler.yaml /tmp/ kubectl scale deploy order-api --replicas=5 kubectl get pods -l app=order-apiWhat it does: Without a scheduler, new pods have no node, so they stay Pending.
order-api-5d8c9b7f6-x8v2n 0/1 Pending 0 8s order-api-5d8c9b7f6-z3p9q 0/1 Pending 0 8s -
Bring the scheduler back
docker exec ch59-control-plane mv /tmp/kube-scheduler.yaml /etc/kubernetes/manifests/ sleep 20; kubectl get pods -l app=order-apiWhat it does: The kubelet starts the scheduler again from the file. Pending pods get nodes.
order-api-5d8c9b7f6-x8v2n 1/1 Running 0 45s order-api-5d8c9b7f6-z3p9q 1/1 Running 0 45s -
Kill a pod and see self-healing
kubectl delete pod -l app=order-api --wait=false kubectl get pods -l app=order-apiWhat it does: The ReplicaSet controller sees fewer pods than wanted and makes new ones.
order-api-5d8c9b7f6-b7k1c 0/1 ContainerCreating 0 1s order-api-5d8c9b7f6-h4n6t 1/1 Running 0 2s -
See which pods each kubelet runs
kubectl get pods -A -o wide --field-selector spec.nodeName=ch59-workerWhat it does: Every pod with nodeName ch59-worker is the kubelet's job on that node.
default order-api-5d8c9b7f6-m9qzt 1/1 Running kube-system kindnet-7wqz5 1/1 Running kube-system kube-proxy-2xg8h 1/1 Running -
Clean up
kubectl delete -f order-api.yamlWhat it does: Removes the Deployment, its ReplicaSet and pods. Keep the cluster for the next lessons.
deployment.apps "order-api" deleted
When it is red 1
The connection to the server 127.0.0.1:6443 was refused - did you specify the right host or port?
Cause. The API server is down or kubectl points to the wrong cluster.
Fix, step by step
- Run kubectl config current-context and check it says kind-ch59.
- Run docker ps and check the ch59-control-plane container is Up.
- If it stopped, run docker start ch59-control-plane and wait 30 seconds.
- Run kubectl get nodes again.
When it is red 2
NAME READY STATUS RESTARTS AGE
order-api-5d8c9b7f6-x8v2n 0/1 Pending 0 3m
Cause. No node was chosen. Either the scheduler is down, or no node has enough CPU or memory.
Fix, step by step
- Run kubectl describe pod order-api-5d8c9b7f6-x8v2n and read Events.
- If there are no events at all, check the scheduler pod in kube-system.
- If you see Insufficient cpu, lower the requests or add a node.
- Run kubectl get pods again after the fix.
When it is red 3
Warning FailedCreatePodSandBox kubelet Failed to create pod sandbox: plugin type="ptp" failed: failed to allocate IP
Cause. The CNI plugin could not give the pod an IP address.
Fix, step by step
- Check the CNI pods: kubectl get pods -n kube-system -l app=kindnet.
- Restart a broken one with kubectl delete pod on it; the DaemonSet recreates it.
- For EKS, check free IPs in the subnet for the AWS VPC CNI.
- Delete the stuck pod so it is recreated.
Practice tasks
Easy Task 1: Build the cluster
Why: You need a 3-node lab.
What to do: Create the ch59 kind cluster.
- Save kind-3node.yaml.
- Run kind create cluster.
- Run kubectl get nodes.
Solution
kind create cluster --config kind-3node.yaml
kubectl get nodesEasy Task 2: Name the brain
Why: Know the control plane.
What to do: List control plane pods.
- Use namespace kube-system.
- Find 4 control plane pods.
Solution
kubectl get pods -n kube-system | grep control-planeEasy Task 3: Read events
Why: Events tell the story.
What to do: Apply order-api.yaml and read events.
- Apply the file.
- Sort events by time.
Solution
kubectl get events --sort-by=.metadata.creationTimestampMedium Task 4: Find the node
Why: Debugging starts with location.
What to do: Print each pod with its node.
- Use -o wide.
- Or custom columns.
Solution
kubectl get pods -o custom-columns=POD:.metadata.name,NODE:.spec.nodeNameMedium Task 5: Find pause
Why: See the sandbox.
What to do: Find the pause image on a worker.
- docker exec into ch59-worker.
- Run crictl images.
Solution
docker exec ch59-worker crictl images | grep pauseMedium Task 6: Break the scheduler
Why: Learn Pending.
What to do: Move the scheduler manifest away and scale up.
- mv the file to /tmp.
- Scale to 5.
- Watch Pending.
Solution
docker exec ch59-control-plane mv /etc/kubernetes/manifests/kube-scheduler.yaml /tmp/
kubectl scale deploy order-api --replicas=5Medium Task 7: Fix the scheduler
Why: Recovery.
What to do: Put the file back and see pods run.
- mv it back.
- Wait 20 seconds.
Solution
docker exec ch59-control-plane mv /tmp/kube-scheduler.yaml /etc/kubernetes/manifests/Medium Task 8: Self-heal
Why: Trust the loop.
What to do: Delete all order-api pods and count them after 10 seconds.
- Delete by label.
- Get pods.
Solution
kubectl delete pod -l app=order-api --wait=false
kubectl get pods -l app=order-apiHard Task 9: etcd keys
Why: See the memory.
What to do: List pod keys in etcd.
- Use etcdctl inside the control plane.
- Prefix /registry/pods/default.
Solution
etcdctl get /registry/pods/default --prefix --keys-onlyHard Task 10: Stop a node
Why: Fault tolerance.
What to do: Stop ch59-worker2 and watch pods move after about 5 minutes.
- docker stop ch59-worker2.
- kubectl get nodes -w.
- kubectl get pods -o wide.
Solution
docker stop ch59-worker2
kubectl get nodes -wHomework: try at home
Draw the 7 steps from this lesson on paper. Then run Example 2 again and match every event line to a box in your drawing.
Recap
- The control plane decides; worker nodes run pods.
- Everything goes through the API server and is stored in etcd.
- Controllers create pods, the scheduler picks nodes, the kubelet starts containers.
- A pod is a pause container plus your app containers sharing one IP.
- Events show every step; read them first when debugging.
- Basics are in chapter 15 and chapter 18; this lesson shows the inside.
FAQ
Q. Is kind the same as a real cluster?
The components are the same real Kubernetes binaries. Only the nodes are Docker containers instead of VMs.
Q. On EKS, can I see etcd and the scheduler?
No. AWS runs the control plane for you. You only see worker nodes and add-ons.
Q. Can I use minikube instead of kind?
Yes. Run minikube start --nodes 3. Use minikube ssh in place of docker exec.
Q. Does the kubelet run as a pod?
No. It is a normal service (systemd) on each node, because it must start the pods.
Q. Why is the scheduler separate from the API server?
Each part does one job. You can even replace the scheduler with your own.
Q. What happens if etcd is lost?
The cluster forgets everything. That is why production backs up etcd and runs 3 or 5 copies.
Interview questions
-
Explain what happens when you run kubectl apply for a Deployment.
Model answer: kubectl sends it to the API server, which authenticates, authorizes, validates and stores it in etcd. The Deployment controller creates a ReplicaSet, which creates pods. The scheduler assigns nodes. The kubelet on each node starts the containers through containerd, CNI gives IPs, and status goes back to the API server.
-
What are the control plane components?
Model answer: kube-apiserver, etcd, kube-scheduler, kube-controller-manager and, in clouds, cloud-controller-manager.
-
What runs on every worker node?
Model answer: kubelet, kube-proxy (or a CNI that replaces it), and a container runtime like containerd.
-
A pod is stuck in Pending. Where do you look?
Model answer: kubectl describe pod events: scheduler messages such as insufficient CPU, taints, node selectors, or unbound PVCs.
-
What is the role of the pause container?
Model answer: It creates and holds the Linux namespaces of the pod, so app containers can restart without losing the pod IP.
-
Why is etcd critical?
Model answer: It is the only source of truth. Losing it loses the cluster state, so it needs backups and an odd number of members for quorum.
Quick quiz
Q1. Which component writes the node name into a new pod?
kube-scheduler.
Q2. Where is every Kubernetes object stored?
In etcd, through the API server.
Q3. Which agent starts containers on a node?
The kubelet, using the container runtime such as containerd.
Q4. What is the pause container for?
It holds the pod's network namespace so all containers share one IP.
Q5. Who recreates a deleted pod of a Deployment?
The ReplicaSet controller inside kube-controller-manager.
Samajla ka?