Kubernetes Fundamentals
What Is Kubernetes and Why Do We Need It
Kubernetes (K8s) is a container orchestration platform that automates deployment, scaling, networking, and lifecycle management of containerized applications across a cluster of machines.
The problem it solves: Once you move beyond a handful of containers on a single host, you hit problems Docker alone doesn’t address:
- What happens when a container crashes? (self-healing)
- How do you run 50 instances of a service across 10 machines? (scheduling, bin-packing)
- How do instances find each other? (service discovery)
- How do you roll out a new version with zero downtime? (rolling updates, rollback)
- How do you scale up under load and back down when idle? (autoscaling)
- How do you handle a machine dying with containers on it? (rescheduling, fault tolerance)
Kubernetes provides a declarative control plane: you describe the desired state (“I want 5 replicas of this app, exposed on port 80, with this much CPU”), and Kubernetes continuously works to make reality match that description — restarting failed containers, rescheduling them onto healthy nodes, and adjusting resources as conditions change. This reconciliation-loop model is the single most important concept in the entire platform — nearly everything else is an instance of it.
Why it won out over alternatives (Docker Swarm, Mesos, Nomad): a vendor-neutral, extensible API (CRDs let you extend the API itself), a massive ecosystem (Helm, Istio, ArgoCD, Prometheus), and being the de facto standard across all major clouds (EKS, GKE, AKS) — meaning architecture decisions made on K8s are largely portable.
Containers vs Pods
A container is a single packaged process plus its dependencies, running in isolation via Linux namespaces and cgroups (Docker’s domain — Kubernetes doesn’t reinvent this layer).
A Pod is Kubernetes’ smallest deployable unit — and it’s not the same as a container. A Pod is a wrapper around one or more containers that:
- Share the same network namespace (same IP address, same
localhost— containers in a pod can reach each other onlocalhost:port) - Share the same storage volumes (if defined)
- Are always scheduled together, on the same node, and live/die together
Pod: payment-service-7d9f8
├── Container: payment-app (main application)
└── Container: log-shipper (sidecar, ships logs to backend)
— both share network namespace: localhost:8080 reachable by both
— both share a mounted volume for log files
Why pods exist instead of just running containers directly: the sidecar pattern. A main application container is frequently paired with a helper container — log shippers, service mesh proxies (Envoy in Istio), config reloaders — that needs tight coupling (shared network/storage) without being baked into the main image. Most production pods, though, run a single container; multi-container pods are the exception for specific patterns, not the default.
Key distinction for interviews: containers are the unit of packaging and isolation (Docker’s job); pods are the unit Kubernetes schedules, networks, and manages (Kubernetes’ job). You never “scale a container” in K8s — you scale pods (replicas).
Kubernetes Architecture: Control Plane & Worker Nodes
CONTROL PLANE
┌─────────────────────────────────────────┐
│ API Server │ etcd │ Scheduler │
│ │ │ Controller Mgr │
└─────────────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
WORKER NODE 1 WORKER NODE 2 WORKER NODE 3
┌───────────┐ ┌───────────┐ ┌───────────┐
│ kubelet │ │ kubelet │ │ kubelet │
│ kube-proxy│ │ kube-proxy│ │ kube-proxy│
│ pods... │ │ pods... │ │ pods... │
└───────────┘ └───────────┘ └───────────┘
Control plane — the “brain.” Decides what should run where, and continuously checks that reality matches the declared desired state. In managed services (EKS, GKE, AKS), the cloud provider runs and hides the control plane from you — you typically only interact with it via the API server.
Worker nodes — the “muscle.” Actual machines (VMs or bare metal) where your application containers run, inside pods, managed by the kubelet on each node.
This separation is what allows the control plane to survive individual node failures, and to schedule/reschedule pods onto healthy nodes transparently — the architectural foundation for self-healing.
Key Components
| Component | Location | Role |
|---|---|---|
| API Server | Control plane | The single front door — every kubectl command, every internal component, every controller talks to the cluster only through the API server’s REST API. It validates and persists changes to etcd. |
| etcd | Control plane | A distributed, consistent key-value store holding the entire cluster’s state — every object, every spec, every status. If etcd is lost, the cluster’s state is lost — this is why etcd backup is a standard production runbook item. |
| Scheduler | Control plane | Watches for newly created pods with no assigned node, and decides which node each should run on, based on resource requests, affinity rules, taints/tolerations, and constraints. It only decides placement — it doesn’t actually start anything. |
| Controller Manager | Control plane | Runs the reconciliation loops — Deployment controller, ReplicaSet controller, Node controller, etc. Each watches the API server for a type of resource and works to drive actual state toward desired state (e.g., “Deployment says 5 replicas, only 3 exist → create 2 more”). |
| Kubelet | Worker node | The per-node agent. Talks to the API server, receives pod specs assigned to its node, and ensures the actual containers are running (via the container runtime) and healthy — reporting status back. |
| Kube-proxy | Worker node | Implements the networking rules that make Kubernetes Services work — maintaining iptables/IPVS rules so traffic to a Service’s virtual IP gets routed to one of the matching pod IPs. |
The flow, end to end: you kubectl apply a Deployment → API server validates and writes it to etcd → controller manager’s Deployment controller notices and creates a ReplicaSet → ReplicaSet controller creates Pod objects → scheduler assigns each pod to a node → that node’s kubelet sees the assignment and starts the containers → kube-proxy updates routing rules once the pod has an IP. No single component does everything — it’s a pipeline of small, focused controllers, each watching and reacting through the API server.
Declarative vs Imperative Configuration
Imperative — you tell Kubernetes exactly what action to take, step by step:
kubectl run nginx --image=nginx
kubectl scale deployment nginx --replicas=5
kubectl delete pod nginx-7d9f8
Fast for one-off debugging and learning, but not reproducible, not version-controllable, and easy to drift from what’s actually intended.
Declarative — you describe the desired end state in a YAML manifest, and let Kubernetes figure out how to get there:
kubectl apply -f deployment.yaml
Re-running apply is idempotent — if nothing changed, nothing happens; if you edit the file and re-apply, Kubernetes computes the diff and reconciles only what’s different. This is the production-standard approach: manifests live in Git, get reviewed via PR, and are applied automatically (this is the foundation of GitOps — ArgoCD/Flux just continuously apply what’s in Git).
Architect-level distinction for interviews: imperative is kubectl create/run/scale/delete (action verbs); declarative is kubectl apply -f against a YAML file (describes state). Production clusters are managed declaratively almost exclusively — imperative commands are for kubectl exec-style debugging, not day-2 operations.
YAML Basics for Kubernetes
Every Kubernetes object manifest follows the same four top-level fields:
apiVersion: apps/v1 # which API group/version this object belongs to
kind: Deployment # the type of object
metadata: # identifying info — name, namespace, labels
name: payment-service
namespace: production
labels:
app: payment-service
spec: # the desired state — what YOU declare
replicas: 3
selector:
matchLabels:
app: payment-service
template: # pod template — spec for the pods this Deployment manages
metadata:
labels:
app: payment-service
spec:
containers:
- name: payment-app
image: myrepo/payment-service:1.4.2
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
Key conventions to internalize:
apiVersionvaries by object maturity: core objects like Pod/Service usev1; Deployments/ReplicaSets useapps/v1; networking objects usenetworking.k8s.io/v1. Getting this wrong is the #1 YAML error beginners hit.labelsvsselector— labels are arbitrary key-value tags on objects; selectors are how one object (a Deployment, a Service) finds other objects by matching their labels. Thespec.selector.matchLabelsin a Deployment must matchspec.template.metadata.labelsexactly, or the Deployment can’t “see” its own pods.specis always what you want;statusis always what Kubernetes reports back — never writestatusyourself in a manifest; it’s populated by the system.- Indentation is structural, not cosmetic — YAML uses whitespace to define nesting; a misaligned
resources:block under the wrong container is a very common production debugging session. ---separates multiple objects in a single YAML file, letting youkubectl apply -fa Deployment + Service + ConfigMap together in one file.