Crisp answer: You submit a pod spec to the API server, it writes to etcd, the scheduler watches for unscheduled pods, scores candidate nodes, assigns the best one, and the kubelet on that node pulls the image and starts the containers.
The full scheduling sequence:
1. API server receives the request
kubectl apply -f deployment.yaml
# or a controller creates a pod spec internally
The API server validates the object (schema, admission webhooks), writes it to
etcd with nodeName: "" (empty — not yet scheduled), and returns 201 Created.
The pod now exists in the cluster but is not running anywhere.
2. Scheduler watches for unscheduled pods
The kube-scheduler watches the API server for pods where spec.nodeName is
empty. When it finds one, it runs the scheduling cycle:
Filtering (Predicates): Remove any node that cannot run the pod. Reasons include:
- Node does not have enough CPU or memory (
NodeResourcesFit) - Node has a taint the pod does not tolerate (
TaintToleration) - Pod requires a specific node via
nodeSelectorornodeAffinityand this node doesn't match - Pod uses a PVC and the volume is not available on this node (
VolumeBinding) - Node is marked
Unschedulable(cordoned)
Scoring (Priorities): Score the remaining feasible nodes (0-100). Criteria include:
LeastAllocated— prefer nodes with more available resources (spread load)ImageLocality— prefer nodes that already have the container image cachedNodeAffinity— prefer nodes matching soft affinity rulesInterPodAffinity— prefer or avoid nodes where related pods are running
The scheduler picks the highest-scoring node. On a tie it round-robins.
3. Binding
The scheduler writes spec.nodeName: worker-3 back to the API server (a
Binding object). This is an optimistic write — the scheduler doesn't
coordinate with the kubelet directly.
4. kubelet acts
The kubelet on worker-3 is watching the API server for pods assigned to its
node. It sees the new pod, pulls the container image if not cached, calls the
container runtime (containerd) to create the containers, sets up the network
via the CNI plugin, and updates the pod status to Running.
5. Pod status transitions
Pending → (scheduler assigns node)
Pending → (kubelet pulls image: ContainerCreating)
Running → (all containers started)
Succeeded / Failed → (for Jobs/batch workloads)
Useful debugging commands:
# Why is my pod still Pending?
kubectl describe pod <pod>
# Look at Events section — it shows scheduler decisions:
# "0/3 nodes are available: 3 Insufficient memory"
# "0/3 nodes are available: 3 node(s) had taint {key:value}"
# Which node was chosen?
kubectl get pod <pod> -o wide
# What does the scheduler see?
kubectl get events --field-selector reason=FailedScheduling
# See node capacity and allocatable resources
kubectl describe node <node> | grep -A 5 "Capacity:\|Allocatable:"
# See what's consuming resources on a node
kubectl describe node <node> | grep -A 30 "Allocated resources"
What to say in the interview:
"A pod goes to the API server which persists it in etcd with no node assigned. The scheduler watches for those unscheduled pods, runs two phases: filtering to remove nodes that can't run the pod (no resources, wrong taints, volume unavailable), then scoring to rank the feasible nodes. It picks the winner and writes the node name back to etcd. The kubelet on that node is watching for pods assigned to it, pulls the image, calls containerd, sets up networking via the CNI, and reports back Running. The scheduler and kubelet never talk directly — the API server and etcd are the coordination point for everything."