In Lesson 1, you saw the big picture. Now we go deep into each control plane component — what it actually does, how it's configured, and what breaks in production.
1. kube-apiserver — The Heart of the Cluster
The API server is the only component that reads and writes to etcd. Every other component — scheduler, controllers, kubelet, even kubectl — talks exclusively to the API server. It is the cluster's central nervous system.
What It Actually Does
When a request arrives at the API server, it passes through a strict pipeline:
| Stage | What Happens | Example |
|---|---|---|
| Authentication | Identifies the caller | Client cert CN=admin, ServiceAccount token |
| Authorization | Checks if this identity can do this action | RBAC: can user X create pods in ns Y? |
| Mutating Admission | Webhooks can modify the request | Inject sidecar container, add labels |
| Schema Validation | Is the object well-formed? | Reject if replicas: -1 |
| Validating Admission | Webhooks can reject (not modify) | Deny images not from approved registry |
| Persist | Write to etcd | Object stored, resourceVersion assigned |
API Server as a Watch Hub
Beyond CRUD, the API server's most critical function is watch notifications. Controllers and kubelets open long-lived watch connections:
GET /api/v1/pods?watch=true&resourceVersion=12345
The API server streams change events (ADDED, MODIFIED, DELETED) for any objects matching the watch. This is how the entire system stays reactive without polling.
Running Multiple API Servers
In HA setups, you run 3+ API server instances behind a load balancer. They are stateless — all state lives in etcd. Any instance can serve any request. The load balancer just needs to health-check /healthz.
2. etcd — The Cluster's Memory
etcd is a distributed, consistent key-value store that uses the Raft consensus algorithm. It's where every Kubernetes object lives — Pods, Services, Secrets, ConfigMaps, everything.
How Data is Organized
/registry/pods/default/nginx-7d9fc44b6c-x2k4j /registry/deployments/default/nginx /registry/services/specs/default/kubernetes /registry/secrets/kube-system/bootstrap-token-abcdef /registry/nodes/worker-1
Each key stores the full serialized object (protobuf by default, JSON if configured).
Raft Consensus — Why 3 or 5 Nodes?
Raft requires a quorum (majority) to accept writes:
| Cluster Size | Quorum Needed | Tolerates Failures |
|---|---|---|
| 1 | 1 | 0 (no HA) |
| 3 | 2 | 1 node failure |
| 5 | 3 | 2 node failures |
| 7 | 4 | 3 node failures (rarely worth the write latency cost) |
Key Operational Concepts
- resourceVersion: Every K8s object has one — it maps to etcd's
ModRevision. Used for optimistic concurrency (conflict detection) and watch resumption. - Compaction: etcd keeps history of all revisions. Compaction removes old revisions to reclaim space. K8s triggers this automatically.
- Defragmentation: After compaction, disk space isn't freed until defrag runs. Must be done per-member, sequentially.
- Quota: Default 2GB. If etcd exceeds quota, it goes read-only (alarm state). Your cluster effectively freezes for writes.
etcd_mvcc_db_total_size_in_bytes and set alerts at 80% of quota. Increasing quota beyond 8GB is not recommended — if you need more, reduce object churn instead.
Backup and Restore
# Take a snapshot etcdctl snapshot save /backup/etcd-$(date +%Y%m%d).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/etcd/ca.crt \ --cert=/etc/etcd/server.crt \ --key=/etc/etcd/server.key # Restore (stops the cluster!) etcdctl snapshot restore /backup/etcd-20240101.db \ --data-dir=/var/lib/etcd-restored
3. kube-scheduler — The Placement Engine
The scheduler's job is deceptively simple: for each unscheduled Pod, pick the best node. But "best" involves a complex scoring algorithm.
The Scheduling Cycle
Filter Plugins (Must Pass)
| Plugin | Rejects Node If... |
|---|---|
| NodeResourcesFit | Not enough CPU/memory for Pod's requests |
| NodeAffinity | Node labels don't match required affinity |
| TaintToleration | Node has taint the Pod doesn't tolerate |
| PodTopologySpread | Would violate maxSkew constraint |
| VolumeBinding | Required PV not available in that zone |
Score Plugins (Rank Nodes 0-100)
| Plugin | Prefers Nodes With... |
|---|---|
| LeastAllocated | Most free resources (spread workload) |
| MostAllocated | Least free resources (bin-pack for cost) |
| ImageLocality | Container image already pulled |
| InterPodAffinity | Matching Pod affinity rules |
| NodeAffinity | Preferred (soft) node affinity matches |
percentageOfNodesToScore parameter (default: adapts based on cluster size) to avoid scoring all nodes. It stops once it finds enough feasible nodes. This is why scheduling decisions can vary between identical clusters.
What Happens When No Node Fits?
The Pod stays Pending. The scheduler emits an event explaining why:
Events:
Type Reason Message
---- ------ -------
Warning FailedScheduling 0/3 nodes are available:
1 Insufficient cpu,
2 node(s) had taint {key=dedicated:NoSchedule}
This is the first thing to check for stuck Pods: kubectl describe pod <name> and read the Events.
4. kube-controller-manager — The Reconciliation Engine
A single binary containing ~30 independent controllers, each running its own reconciliation loop. Think of it as 30 microservices compiled into one process for convenience.
The Controller Pattern
while true:
desired = read_spec_from_api_server()
actual = observe_current_state()
if actual != desired:
take_action_to_converge(actual → desired)
sleep(resync_interval) # or react to watch events
Key Controllers and What They Do
| Controller | Watches | Acts On | Production Impact |
|---|---|---|---|
| Deployment | Deployments | Creates/scales ReplicaSets | Drives all rolling updates |
| ReplicaSet | ReplicaSets + Pods | Creates/deletes Pods | Maintains desired replica count |
| Node Lifecycle | Node heartbeats | Taints/evicts from dead nodes | 40s default timeout → NotReady |
| EndpointSlice | Services + Pods | Updates EndpointSlices | Drives service discovery |
| ServiceAccount | Namespaces | Creates default SA per namespace | Ensures Pods can authenticate |
| PV Binder | PVCs + PVs | Binds claims to volumes | Storage provisioning |
| Job | Jobs + Pods | Creates Pods, tracks completions | Batch workloads |
| CronJob | CronJobs | Creates Jobs on schedule | Scheduled batch work |
| Garbage Collector | ownerReferences | Cascading deletes | Cleans up orphaned objects |
Leader Election
Only one controller-manager instance is active at a time (the leader). Others are hot standbys. Leader election uses a Lease object in the kube-system namespace:
kubectl get lease -n kube-system kube-controller-manager # holderIdentity: master-1_xxxx # leaseDurationSeconds: 15 # renewTime: 2024-01-15T10:30:45Z
If the leader fails to renew within the lease duration, another instance takes over.
--node-monitor-grace-period and --pod-eviction-timeout.
5. cloud-controller-manager — The Cloud Bridge
Introduced to decouple cloud-specific logic from the core Kubernetes codebase. It runs controllers that interact with your cloud provider's API:
| Controller | What It Does | Cloud API Used |
|---|---|---|
| Node | Detects node deletion in cloud, updates Node objects | EC2/Compute Engine |
| Route | Configures network routes between nodes | VPC routing |
| Service (LB) | Provisions cloud load balancers for type: LoadBalancer | ELB/ALB/Cloud LB |
In self-managed (bare-metal) clusters, there is no cloud-controller-manager. You either use MetalLB for LoadBalancer services or go without.
type: LoadBalancer "just works" on cloud but not on bare metal.
Control Plane Communication Map
Summary: What to Remember
| Component | Stateless? | Leader-Elected? | Talks to etcd? | Key Metric to Monitor |
|---|---|---|---|---|
| API Server | ✅ Yes | No (all active) | ✅ Only one | Request latency, watch count |
| etcd | ❌ Stateful | Raft leader | Is etcd | DB size, fsync latency, leader changes |
| Scheduler | ✅ Yes | ✅ Yes | No | Scheduling latency, pending pods |
| Controller Mgr | ✅ Yes | ✅ Yes | No | Work queue depth, reconcile errors |
| Cloud Ctrl Mgr | ✅ Yes | ✅ Yes | No | Cloud API errors, LB provision time |
📝 Quiz: Control Plane Deep-Dive
Q1: In the API server request pipeline, can a validating admission webhook modify the object?
Q2: You have a 5-node etcd cluster. How many nodes can fail simultaneously while the cluster still accepts writes?
Q3: The scheduler has 50 feasible nodes after filtering. What does it do next?
Q4: If the kube-controller-manager leader dies, how long before another instance takes over?
Q5: Why does Kubernetes use a star topology (everything → API server) instead of letting components talk directly?
Q6: Your etcd monitoring shows db_total_size approaching 2GB. What's about to happen, and how do you fix it?
etcdctl compact. (2) Defrag: etcdctl defrag. (3) Disarm the alarm: etcdctl alarm disarm. Long-term: reduce object churn (e.g., reduce Event TTL, use EndpointSlices instead of Endpoints).