Kubernetes keeps applications running across many machines by continuously comparing the state you declared with the state that exists, and correcting any difference. Knowing which component does what turns confusing failures into short checklists.
Versions in use on this date. Kubernetes 1.21 was released on 8 April 2021. Notable changes in it: CronJob is stable (batch/v1), PodDisruptionBudget is stable (policy/v1), immutable Secrets and ConfigMaps are stable, and PodSecurityPolicy is deprecated (removal is planned for 1.25). The 1.20 release already deprecated Docker as a runtime via dockershim; clusters on containerd are unaffected. Each minor release is supported for about a year.
The cluster
flowchart TB
U[kubectl and CI] -->|HTTPS| API
subgraph CP[Control plane]
API[kube-apiserver<br/>front door, auth, validation] <--> ETCD[(etcd<br/>cluster state)]
SCH[kube-scheduler<br/>picks a node for each pod] --> API
CM[kube-controller-manager<br/>deployments nodes endpoints] --> API
CCM[cloud-controller-manager<br/>load balancers volumes] --> API
end
subgraph N1[Worker node 1]
KL1[kubelet] --> CR1[containerd]
KP1[kube-proxy]
CR1 --> P1[Pods and containers]
end
subgraph N2[Worker node 2]
KL2[kubelet] --> CR2[containerd]
KP2[kube-proxy]
CR2 --> P2[Pods and containers]
end
KL1 -->|watch and report| API
KL2 -->|watch and report| API
P1 <-->|pod network via CNI plugin| P2
DNS[CoreDNS pods] -.-> P1
Components in plain words
| Component | Job | If it fails |
|---|---|---|
| kube-apiserver | The only component that talks to etcd; every kubectl call and every controller goes through it |
No changes or queries possible; running pods keep running |
| etcd | Consistent key-value store holding all cluster objects | Cluster state is lost without a backup; run 3 or 5 members for HA |
| kube-scheduler | Assigns unscheduled pods to nodes by resources, affinity, taints | New pods stay Pending |
| kube-controller-manager | Runs loops: ReplicaSet, Deployment, Node, Job, EndpointSlice… | Desired state stops being enforced |
| kubelet | Agent on each node; starts containers through the runtime, runs probes, reports status | That node goes NotReady; its pods are rescheduled after a timeout |
| Container runtime (containerd) | Pulls images and runs containers through runc | Pods on that node cannot start |
| kube-proxy | Programs iptables or IPVS rules so Service IPs reach pods | Services stop working on that node |
| CNI plugin (Calico, Flannel, Cilium…) | Gives each pod an IP and connects pods across nodes | Pods cannot talk across nodes |
| CoreDNS | Cluster DNS: service.namespace.svc.cluster.local |
Name lookups fail |
What happens on kubectl apply -f deployment.yaml
- kubectl sends the object to the API server, which authenticates you (certificate, token or OIDC), checks RBAC and runs admission controllers (defaults, policy), then stores it in etcd.
- The Deployment controller sees a new Deployment and creates a ReplicaSet; the ReplicaSet controller creates Pods (still without a node).
- The scheduler sees unscheduled pods, filters nodes that fit (CPU, memory, taints, affinity), scores the rest and writes the chosen node into each pod.
- The kubelet on that node sees a pod assigned to it, asks containerd to pull the image and start containers, sets up networking through the CNI plugin, and reports status back.
- Once readiness probes pass, the EndpointSlice controller adds the pod to the Service, and kube-proxy routes traffic to it.
Everything is a loop of “watch, compare, act”. That is why Kubernetes heals: delete a pod and the ReplicaSet controller creates another.
Objects you meet first
- Pod: one or more containers sharing a network and storage; the smallest unit.
- Deployment / ReplicaSet: keeps N identical pods running and manages rolling updates.
- Service: a stable IP and DNS name in front of changing pods.
- ConfigMap / Secret: configuration and sensitive values.
- Namespace: a scope for names, quotas and access rules.
- Ingress: HTTP routing from outside to services.
- PersistentVolumeClaim: a request for storage.
Where clusters run
Managed control planes (EKS, GKE, AKS) hide the control plane and are the default for production. Self-managed clusters use kubeadm, k3s or MicroK8s. For learning, kind runs a cluster inside Docker (next post).
Debugging by layer
kubectl get nodes -o wide # nodes Ready? runtime and version
kubectl get pods -A # anything Pending, CrashLoopBackOff, ImagePullBackOff?
kubectl describe pod <name> # Events at the bottom explain scheduling and pulls
kubectl logs <pod> --previous # logs from the crashed container
kubectl get events -A --sort-by=.lastTimestamp | tail -20
kubectl get --raw='/readyz?verbose' # API server health checks
Pending means scheduler or resources; ImagePullBackOff means registry or credentials; CrashLoopBackOff means the application itself; NotReady nodes mean kubelet, runtime or network.