Control Plane
The brain of the cluster — API server, etcd, scheduler, and controllers
Architecture Overview
A Kubernetes cluster is split into the control plane (the brain) and worker nodes (the muscle). The control plane makes global decisions — scheduling, detecting failures, responding to events — while worker nodes run your actual workloads.
Note
Key insight: All communication between components goes through the API server. The scheduler, controllers, and kubelets never talk to etcd directly — only the API server does.
API Server (kube-apiserver)
The API server is the front door to the cluster. It's the only component that communicates with etcd directly. Every operation — whether from kubectl, a kubelet heartbeat, or a controller reconciliation loop — goes through the API server's RESTful interface.
- Exposes a RESTful API over HTTPS (port 6443 by default)
- Stateless — can be horizontally scaled behind a load balancer for HA
- Every
kubectlcommand, every kubelet heartbeat, every controller loop goes through it - Acts as the single gateway to etcd — no other component reads/writes etcd directly
Admission Pipeline
Every API request passes through a strict pipeline before anything is written to etcd. Understanding this pipeline is essential for debugging "why was my resource rejected?" or "how did my pod get a sidecar injected?"
Authentication
Who are you?
certs, tokens, OIDCAuthorization
Can you do this?
RBAC policiesMutating Webhooks
Modify the request
inject sidecars, add labelsSchema Validation
Is the object valid?
field types, required fieldsValidating Webhooks
Accept or reject?
policy checks, OPAWrite to etcd
Persisted
resource stored
Tip
Mutating vs. validating webhooks: Mutating webhooks run before validation — they can change the object (e.g., Istio injecting a sidecar container). Validating webhooks run after validation — they can only accept or reject (e.g., a policy engine blocking privileged containers). Order matters: mutate first, then validate the final result.
Watch Mechanism
The API server supports watch — clients open long-lived HTTP connections and receive chunked responses whenever a watched resource changes. This is the fundamental mechanism behind Kubernetes' reactive architecture.
- Controllers, kubelets, and other components use watches to react to changes in near-real-time
- Backed by etcd's own watch feature
- Each watch has a
resourceVersion— enables resumable watches after disconnection - API server maintains a watch cache to avoid hammering etcd with redundant watches
- Informers (client-go) combine watch + in-memory cache + index for efficient access
How a Controller Uses Watch
- Controller starts an informer that lists all resources of interest (initial sync)
- Informer opens a watch connection to the API server
- API server pushes events (ADDED, MODIFIED, DELETED) as they happen
- Informer updates its local cache and enqueues the object key onto a work queue
- Worker goroutines dequeue keys and run the reconcile function
etcd
etcd is a distributed, consistent key-value store that serves as the single source of truth for all cluster state. If etcd is lost and unrecoverable, the cluster is gone.
Core Properties
- Strongly consistent (linearizable reads/writes)
- Uses Raft consensus — requires a quorum (majority) for writes
- All data is protobuf-encoded for efficiency
- Supports watches natively — the API server leverages this
- Typical cluster sizes: 3 nodes (tolerates 1 failure) or 5 nodes (tolerates 2 failures)
Operational Concerns
- etcd is the cluster's bottleneck — keep write latency under 10ms
- Use fast SSDs for etcd storage (avoid network-attached storage)
- Regular compaction to reclaim space from old revisions
- Regular defragmentation to reclaim freed disk space
- Back up etcd regularly —
etcdctl snapshot save - Default DB size limit: 2 GB (configurable up to 8 GB)
Key Organization
etcd stores Kubernetes objects under a hierarchical key structure:
// Key format
/registry/<resource-type>/<namespace>/<name>
// Examples
/registry/pods/default/nginx-deployment-abc123
/registry/services/kube-system/kube-dns
/registry/deployments/production/web-app
/registry/nodes/worker-node-01 // cluster-scoped (no namespace)
/registry/secrets/default/db-credentialsRaft Consensus
Note
Quorum: With 3 nodes, quorum = 2. The leader must replicate a write to at least 1 follower before acknowledging. If the leader dies, the remaining 2 nodes elect a new leader. With 5 nodes, quorum = 3 — tolerates 2 simultaneous failures.
Scheduler (kube-scheduler)
The scheduler watches for newly created pods that have no node assigned (.spec.nodeName is empty). It selects the optimal node for each pod through a two-phase process.
Watch
Detect unscheduled pods
(.spec.nodeName is empty)Filter
Eliminate unsuitable nodes
resources, taints, affinityScore
Rank remaining nodes
0-100 per pluginBind
Set .spec.nodeName
kubelet picks it up
Filtering Phase (Predicates)
Nodes that cannot run the pod are eliminated:
- PodFitsResources — enough CPU/memory available?
- PodFitsHostPorts — requested host ports free?
- NodeSelector — node labels match?
- NodeAffinity — required affinity rules satisfied?
- TaintToleration — pod tolerates node taints?
- PodTopologySpread — would placing here violate spread constraints?
- VolumeZone — PV in same zone as node?
Scoring Phase (Priorities)
Remaining nodes are ranked by weighted scores:
- LeastRequestedPriority — prefer nodes with most free resources
- BalancedResourceAllocation — prefer balanced CPU/memory usage
- NodeAffinityPriority — prefer nodes matching preferred affinity
- PodTopologySpread — prefer nodes that improve distribution
- ImageLocality — prefer nodes that already have the container image
- InterPodAffinity — prefer nodes near preferred co-located pods
Tip
Extensibility: The scheduler is plugin-based. You can write custom scheduler plugins, create multiple scheduler profiles, or run multiple schedulers. Pods can specify .spec.schedulerName to target a specific scheduler.
Controller Manager (kube-controller-manager)
The controller manager runs approximately 30 controllers as goroutines within a single process. Each controller implements the same fundamental pattern: watch resources, compare desired state vs. actual state, and reconcile the difference.
The Reconciliation Loop — Kubernetes' Core Pattern
Observe
Watch API server for
resource changesDiff
Compare desired state
vs. actual stateAct
Take action to converge
actual towards desiredRepeat
Continuously loop
forever
This is the declarative model: you declare what you want (desired state), and controllers work to make reality match. You never say "start 3 pods" — you say "I want 3 replicas" and the controller makes it happen.
Key Controllers
| Controller | Watches | Reconciles |
|---|---|---|
| Deployment | Deployment objects | Creates/updates ReplicaSets to match the deployment spec. Manages rolling updates by scaling new RS up and old RS down. |
| ReplicaSet | ReplicaSet objects, Pods | Ensures exactly N pod replicas exist. Creates or deletes pods as needed. |
| Node | Node objects | Monitors node health via heartbeats. Taints unresponsive nodes, evicts pods from unreachable nodes after a grace period. |
| Service Account | Namespace objects | Creates a default service account in every new namespace. |
| Endpoint / EndpointSlice | Services, Pods | Populates Endpoints/EndpointSlice objects with the IPs of pods matching a service's selector. |
| Job | Job objects, Pods | Creates pods for jobs, tracks completions, handles failures and retries. |
| Namespace | Namespace objects | When a namespace is deleted, cleans up all resources within it. |
| PersistentVolume | PVs, PVCs | Binds PVCs to matching PVs. Handles reclaim policies on release. |
Warning
Leader election: In HA setups, multiple controller-manager instances may run, but only the leader actively reconciles. Others are standby. Leader election uses a Lease object in the API server. Same applies to the scheduler.
Cloud Controller Manager
Separated from the main controller manager since Kubernetes 1.6, the cloud controller manager runs controllers that integrate with cloud provider APIs. Each cloud provider (AWS, GCP, Azure) ships their own implementation.
Cloud-Specific Controllers
- Node controller — detects when a cloud VM is terminated and removes the corresponding K8s node object
- Route controller — configures cloud network routes so pods on different nodes can communicate
- Service controller — creates cloud load balancers for services of
type: LoadBalancer(e.g., AWS ELB, GCP LB)
Why Separate?
- Kubernetes core remains cloud-agnostic
- Cloud providers can release on their own schedule, independent of K8s releases
- Cleaner separation of concerns — cloud APIs evolve independently
- Different clouds have different capabilities (zones, LB features, network models)
Note
Managed Kubernetes: In managed services (EKS, GKE, AKS), the control plane is fully managed by the cloud provider. You never see the API server, etcd, or controllers — the provider runs and maintains them. You only manage worker nodes (or use managed node pools / Fargate / Autopilot).