This is the web edition of the Cloud Soft Solutions Kubernetes Master Handbook — a trainer-grade guide covering Kubernetes architecture, control-plane internals, CNI networking and the production workload objects, from basics to expert. Use the contents below to jump to any chapter. Preparing for interviews or the CKA/CKAD/CKS? Pair it with our AI & ML interview questions and the Cloud + DevOps roadmap.
Table of Contents
- What Is Kubernetes
- Why We Need Kubernetes in Real Time
- History and Evolution
- Docker Swarm vs Kubernetes
- Kubernetes Architecture — The Big Picture
- Control Plane Components in Depth
- Worker Node Components in Depth
- Cluster Add-ons
- CNI and Cluster Networking
- Namespace
- Pod — The Atomic Unit
- Deployment and ReplicaSet
- DaemonSet
- StatefulSet
- Choosing the Right Workload Object
- Troubleshooting Playbook
- Command and YAML Reference
What Is Kubernetes
Kubernetes is an open-source platform for automating the deployment, scaling, healing and operation of containerised applications across a fleet of machines. The name comes from the Greek κυβερνήτης — helmsman or pilot — and it is commonly abbreviated K8s, where the 8 stands for the eight letters between the K and the s.
1.1 The one-sentence definition, unpacked
That definition is easy to recite and hard to actually understand, so let us take it apart clause by clause. Every phrase in it carries weight.
"Containerised applications"
Kubernetes does not run your code directly. It runs containers — the images your teams build with Docker, Buildah, Kaniko or Jib. Kubernetes assumes the packaging problem is already solved. It concerns itself only with where those containers run, how many of them run, how they find each other, and what happens when they die.
"Across a fleet of machines"
This is the central idea. A single Docker host is a machine you must think about. A Kubernetes cluster is a pool of capacity you stop thinking about. You declare "I need six replicas of this service with 500m CPU each" and Kubernetes decides which of your forty nodes has room. You never write a hostname. When a node dies at 3 a.m., the workload lands somewhere else and nobody is paged.
"Automating deployment, scaling, healing and operation"
These are the four verbs that justify the entire platform:
- Deployment — roll a new image out gradually, watch health, stop if it fails, roll back to the previous version on one command.
- Scaling — add or remove replicas manually, on CPU/memory pressure, on custom metrics, or on a queue depth; and add or remove nodes underneath when the pods will not fit.
- Healing — restart crashed containers, replace pods on dead nodes, remove unhealthy pods from load-balancer rotation, reschedule when a node is drained.
- Operation — service discovery, configuration injection, secret distribution, storage attachment, rolling certificate updates, traffic policy.
1.2 Declarative intent and the reconciliation loop
If you remember one concept from this entire handbook, make it this one. Kubernetes is a declarative, level-triggered control system. Almost every question an interviewer asks about Kubernetes internals is really this idea in disguise.
In an imperative system you issue commands: start this container, stop that one, copy this file. The system does what you said and forgets. If the container dies afterwards, nothing happens, because nobody is watching.
In Kubernetes you do not issue commands. You submit a desired state — a document that says what the world should look like. A controller then runs forever in a loop:
for {
desired := readSpecFromAPIServer() // what the user asked for
actual := observeRealWorld() // what is actually running
if actual == desired {
continue // nothing to do, sleep and re-check
}
diff := computeDifference(desired, actual)
takeCorrectiveAction(diff) // create, delete, patch
writeStatusBackToAPIServer()
}
Three consequences follow from this design, and all three matter in production:
| Property | What it means in practice |
|---|---|
| Self-healing is free | Nobody wrote code that says "if a pod dies, restart it". The ReplicaSet controller simply notices that observed replicas (2) no longer equals desired replicas (3) and creates one. Healing is a side effect of the loop, not a feature. |
| Level-triggered, not edge-triggered | Kubernetes reacts to state, not to events. If a controller crashes and misses ten events, it recovers by comparing current state on restart. A missed event never causes permanent divergence. This is why Kubernetes is resilient to its own component failures. |
| Eventual consistency | Changes are not instantaneous. You apply a manifest and the API server returns success immediately — that only means the intent was recorded. Actual convergence happens asynchronously, over seconds or minutes. Pipelines that assume kubectl apply means "deployed" are the single most common CI/CD bug in Kubernetes shops. |
Spec and status: the shape of every object
Because of the loop, every Kubernetes object has the same two-part shape. This is not a convention; it is enforced by the API machinery.
apiVersion: apps/v1 # which API group and version defines this object kind: Deployment # which type of object metadata: # identity: name, namespace, labels, annotations name: payments-api namespace: production spec: # DESIRED state — written by you replicas: 3 status: # OBSERVED state — written by the controller, never by you readyReplicas: 3 availableReplicas: 3
You write spec. Kubernetes writes status. If you ever find yourself trying to edit status, you have misunderstood the model. And when you debug a workload, the first useful question is always: what does spec say, what does status say, and why do they differ?
1.3 What Kubernetes is not
Interviewers ask this deliberately, because candidates who cannot name the boundaries usually cannot design within them.
| Kubernetes is NOT… | Explanation |
|---|---|
| A PaaS like Heroku | It gives you primitives, not an opinionated developer experience. Teams build their PaaS on top of Kubernetes (that is what Argo CD, Backstage, Knative and OpenShift do). |
| A build system | It does not compile code or build images. Your CI does that. Kubernetes consumes finished images from a registry. |
| A CI/CD tool | It has no concept of a pipeline. Jenkins, GitHub Actions, Azure DevOps and Argo CD supply that. |
| A container runtime | It does not start containers itself. containerd or CRI-O does, through the CRI interface. |
| A logging or monitoring system | It exposes metrics and writes container stdout to disk. Prometheus, Grafana, Elasticsearch and New Relic supply the actual platform. |
| A database or message bus | It runs them, but provides none. Storage durability comes from CSI drivers and the underlying storage system. |
| A service mesh | It gives you basic L4 service discovery and load balancing. mTLS, retries, circuit breaking and traffic splitting come from Istio, Linkerd or Cilium Service Mesh. |
| A secrets manager | A Kubernetes Secret is base64-encoded, not encrypted, unless you enable encryption at rest. Real secret management means Vault, AWS Secrets Manager or Azure Key Vault with an external-secrets operator. |
EncryptionConfiguration at rest, restrict RBAC on the secrets resource, disable automatic ServiceAccount token mounting where unnecessary, and ideally source real secrets from an external vault. Candidates who answer a flat "yes" are marked down immediately.
Why We Need Kubernetes in Real Time
Kubernetes is genuinely complex. That complexity is only worth paying for if it removes a larger complexity somewhere else. This chapter shows exactly what that larger complexity looks like in a real production environment — and, honestly, when it does not exist and you should walk away.
2.1 Life before orchestration — the pain, concretely
Picture a mid-sized banking platform: twenty-two microservices, four environments, thirty virtual machines, three release windows a week. Without an orchestrator, here is what the team actually deals with.
Scenario A — a node dies at 02:40
Without Kubernetes: monitoring pages the on-call engineer. They log in, discover the host is gone, find which six services were pinned to it, locate spare capacity, SSH into replacement hosts, pull the right image tags from a wiki page that is two weeks stale, start containers with the right environment variables, update the load balancer pool by hand, and verify. Mean time to recovery: 45–90 minutes, with a tired human doing error-prone work.
With Kubernetes: the node controller marks the node NotReady after roughly 40 seconds. After the eviction timeout the pods on that node are marked for deletion. Their owning ReplicaSets observe a shortfall, create replacement pods, the scheduler places them on healthy nodes, kubelet pulls images and starts them, readiness probes pass, endpoints are updated, traffic flows. Mean time to recovery: 60–120 seconds, with nobody awake.
Scenario B — Friday evening traffic spike
Without Kubernetes: someone must predict the spike, provision VMs in advance, and pay for them all week. If the prediction is wrong in one direction you burn money; wrong in the other, you drop transactions.
With Kubernetes: the HorizontalPodAutoscaler watches CPU or a custom metric such as queue depth, scales the deployment from 6 replicas to 30, and the Cluster Autoscaler adds nodes when pods go Pending for lack of capacity. When the spike passes, both scale back down. You pay for the peak only during the peak.
Scenario C — releasing version 2.4.1
Without Kubernetes: a runbook with 40 manual steps, a maintenance window, a rollback plan that involves re-deploying old artefacts, and a genuine risk that hosts 1–4 are on the new version while hosts 5–6 are still on the old one because the script failed halfway.
With Kubernetes: kubectl set image or a Git commit. Pods are replaced a few at a time, each new pod must pass its readiness probe before the next old one is removed, and kubectl rollout undo returns you to the previous ReplicaSet in seconds. There is no partial state, because the controller will not proceed past a failing surge.
2.2 The ten problems Kubernetes actually solves
| # | Problem | How Kubernetes addresses it |
|---|---|---|
| 1 | Bin-packing and placement | The scheduler places pods on nodes based on requested CPU/memory, affinity rules, taints and topology spread — raising utilisation well above the 15–25% typical of one-app-per-VM estates. |
| 2 | Self-healing | Crashed containers restart; pods on dead nodes are recreated elsewhere; failing pods are pulled out of Service endpoints by readiness probes. |
| 3 | Horizontal scaling | HPA on CPU, memory, or custom/external metrics; VPA for right-sizing; Cluster Autoscaler / Karpenter for node capacity. |
| 4 | Service discovery | Every Service gets a stable virtual IP and a DNS name. Applications talk to payments.production.svc.cluster.local and never learn a pod IP. |
| 5 | Zero-downtime deployment | Rolling updates gated on readiness, with surge and unavailability budgets, plus versioned rollback through retained ReplicaSets. |
| 6 | Configuration and secret injection | ConfigMaps and Secrets decouple configuration from images, so the same image promotes unchanged from dev to production. |
| 7 | Storage abstraction | PersistentVolumeClaims request storage by class and size; the CSI driver provisions EBS, Azure Disk, NFS or Ceph without the manifest knowing which. |
| 8 | Multi-tenancy and isolation | Namespaces, ResourceQuota, LimitRange, RBAC and NetworkPolicy allow many teams to share one cluster with enforced boundaries. |
| 9 | Portability | The same manifests run on EKS, AKS, GKE, OpenShift and on-premises. Cloud-specific detail is confined to StorageClasses, LoadBalancer annotations and IAM integration. |
| 10 | Extensibility | Custom Resource Definitions plus controllers let you manage databases, certificates or entire environments with the same declarative model — this is what "operators" are. |
2.3 When you should NOT use Kubernetes
A world-class engineer is defined as much by knowing when not to reach for a tool. Kubernetes is a poor fit when:
- You have one or two services and no operations team. A managed PaaS, ECS Fargate, Azure App Service or Cloud Run will deliver the same outcome at a fraction of the operational cost.
- Your workload is a single stateful monolith with a vertical scaling profile. Kubernetes adds indirection without adding value.
- You cannot staff platform expertise. An unmaintained cluster — outdated version, unpatched CNI, no backup of etcd, no upgrade rehearsal — is more dangerous than the VMs it replaced.
- Your latency budget cannot absorb the network path. Overlay networking, kube-proxy and ingress add hops. Most applications never notice; ultra-low-latency trading systems do.
- Regulatory constraints forbid shared tenancy and you would end up running one cluster per application anyway, which removes the bin-packing benefit.
History and Evolution
Kubernetes did not emerge from a whiteboard. It is the third-generation product of roughly fifteen years of running containers at Google scale, and several design decisions that look arbitrary make perfect sense once you know where they came from.
3.1 Borg, Omega and the Google lineage
Borg (circa 2003 onwards)
Google's internal cluster manager, running essentially all of Google's workloads on shared machine pools. Borg introduced ideas that survive verbatim in Kubernetes: the allocation unit that groups co-located processes (which became the Pod), declarative job specifications, labels for grouping, bin-packing schedulers, and the crucial insight that batch and serving workloads should share the same fleet to raise utilisation.
Omega (circa 2013)
A research successor exploring a different architecture: instead of a monolithic scheduler, Omega used a shared, consistent, transactional store of cluster state that many independent components read and wrote optimistically. That is exactly the etcd-plus-API-server-plus-many-controllers architecture Kubernetes uses today. Omega is the direct ancestor of the control plane's shape.
Kubernetes (2014)
Announced by Google in June 2014, largely authored by engineers with deep Borg experience — Joe Beda, Brendan Burns, Craig McLuckie, Tim Hockin, Brian Grant and others. The deliberate decision was to build an open, API-driven system rather than open-sourcing Borg itself, and to make everything extensible so the community could grow it.
3.2 Timeline: 2013 to today
| When | Milestone |
|---|---|
| March 2013 | Docker released publicly. Containers become accessible to ordinary developers; the orchestration gap becomes obvious almost immediately. |
| June 2014 | Kubernetes announced and open-sourced by Google. |
| Late 2014 | Red Hat, Microsoft, IBM and others join development. Docker Swarm and Apache Mesos emerge as competitors — the "orchestration wars" begin. |
| July 2015 | Kubernetes 1.0 released. Google donates the project to the newly created Cloud Native Computing Foundation (CNCF) under the Linux Foundation — a deliberate move to signal vendor neutrality. |
| 2016 | Helm appears as the package manager. kubeadm makes cluster bootstrap reproducible. Minikube brings local clusters to laptops. |
| 2017 | The decisive year. RBAC goes stable, CRDs arrive, and the major clouds ship managed offerings — Amazon EKS, Azure AKS and GKE maturing. In October, Docker Inc. itself announces support for Kubernetes in Docker EE, widely read as the end of the orchestration wars. |
| 2018 | Kubernetes becomes the first CNCF project to graduate. The Container Storage Interface (CSI) and Container Runtime Interface (CRI) mature, decoupling storage and runtime from the core. |
| 2019–2020 | Operator pattern spreads. Service meshes (Istio, Linkerd) mature. Dockershim deprecation announced in 1.20 — containerd and CRI-O become the standard runtimes. |
| 2021–2022 | Dockershim removed in v1.24. GitOps consolidates around Argo CD and Flux, both reaching CNCF graduation. eBPF-based networking (Cilium) gains serious adoption. |
| 2023–2024 | Gateway API matures as the successor to Ingress. Sidecar containers become a first-class, natively-supported concept. Kubernetes becomes the default substrate for AI/ML workloads with device plugins and scheduling for GPUs. |
| 2025 onward | Focus on cost efficiency, multi-cluster fleet management, security supply chain (SBOM, signed images, admission policy), and running large-scale inference and training workloads. |
3.3 The CNCF, release cadence and API deprecation
Kubernetes ships approximately three minor releases per year. Each minor version receives patch support for roughly fourteen months. Managed providers typically support three to four minor versions concurrently, which in practice means you must upgrade at least once a year, and realistically twice.
API versioning — and why it bites people
Kubernetes APIs move through maturity stages, and the stage is visible in the apiVersion string:
| Stage | Example | Guarantees |
|---|---|---|
| Alpha | v1alpha1 | Disabled by default, may be dropped without notice, may be buggy, no upgrade guarantee. Never use in production. |
| Beta | v1beta1 | Enabled by default historically (no longer, for new APIs), well tested, but the schema may still change incompatibly. Widely used in production against advice. |
| Stable | v1, apps/v1 | Will not be removed within a major version. Safe. |
extensions/v1beta1 Deployments and Ingresses (removed in 1.16 and 1.22), policy/v1beta1 PodDisruptionBudget, or batch/v1beta1 CronJob. Run kubectl convert, or tools such as pluto or kubent, against your manifests before every upgrade. This single check prevents the majority of upgrade incidents.
Deployment live in apps/v1 while Pod lives in plain v1?" Answer: Pod, Service, Namespace, ConfigMap and Secret belong to the original core (or "legacy") API group, which has an empty group name — hence just v1. Everything added later lives in a named group: apps, batch, networking.k8s.io, rbac.authorization.k8s.io. The URL paths differ too: /api/v1/... for core, /apis/apps/v1/... for everything else.
Docker Swarm vs Kubernetes
Swarm lost the orchestration war, but it is not a bad system — it is a deliberately smaller one. Understanding precisely where the two diverge sharpens your understanding of what Kubernetes chose to make complicated, and why.
4.1 Architectural comparison
Docker Swarm architecture
- Manager nodes maintain cluster state using an embedded Raft log — no external datastore to operate.
- Worker nodes run the Docker Engine and execute tasks assigned by managers.
- The Service is the top-level abstraction; it produces Tasks, and each task is exactly one container.
- Networking uses a built-in overlay driver, with a routing mesh that makes any node accept traffic for any published port.
- The API is the Docker API you already know —
docker service create,docker stack deploy.
Kubernetes architecture
- A control plane of separable components — API server, etcd, scheduler, controller manager — each independently scalable and replaceable.
- etcd as an external, consistent datastore, itself Raft-based but operated separately.
- The Pod as the scheduling unit, which may hold multiple co-located containers sharing a network namespace and volumes.
- Pluggable everything: runtime via CRI, networking via CNI, storage via CSI, devices via device plugins, admission via webhooks.
- A rich, versioned, extensible REST API that users can add their own types to via CRDs.
4.2 Feature-by-feature matrix
| Dimension | Docker Swarm | Kubernetes |
|---|---|---|
| Setup effort | Minutes. docker swarm init, then join tokens. | Hours to days self-managed; minutes with EKS/AKS/GKE but with real configuration decisions. |
| Learning curve | Gentle — if you know Docker Compose you are most of the way there. | Steep. Dozens of object kinds, controllers, and an entire ecosystem. |
| Smallest unit | Task = one container. | Pod = one or more co-located containers sharing network and volumes. |
| State store | Built-in Raft inside managers. | External etcd cluster, operated and backed up separately. |
| Scaling | Manual: docker service scale. No native autoscaling. | Manual, HPA (CPU/memory/custom/external metrics), VPA, plus node-level Cluster Autoscaler and Karpenter. |
| Load balancing | Routing mesh with built-in IPVS; simple and effective for L4. | Service (ClusterIP/NodePort/LoadBalancer) plus Ingress and Gateway API for L7 routing, TLS, path/host rules. |
| Rolling updates | Yes, with --update-parallelism, delay, and failure action. | Yes, with maxSurge/maxUnavailable, readiness gating, revision history and one-command rollback; plus canary/blue-green via Argo Rollouts or Flagger. |
| Health checks | Docker HEALTHCHECK only. | Liveness, readiness and startup probes with independent semantics. |
| Storage | Docker volume plugins; limited dynamic provisioning. | Full CSI ecosystem: StorageClass, dynamic provisioning, snapshots, resizing, topology awareness. |
| Networking policy | No native micro-segmentation. | NetworkPolicy (with a supporting CNI), plus mesh-level mTLS and L7 policy. |
| Secrets | Encrypted at rest in the Raft log — arguably better by default. | Base64 in etcd unless encryption at rest is configured; but far richer RBAC and external integration. |
| RBAC | Minimal in Community Edition. | Comprehensive: Roles, ClusterRoles, bindings, ServiceAccounts, admission control. |
| Extensibility | Essentially none — the object model is fixed. | CRDs, operators, admission webhooks, custom schedulers, custom controllers. |
| Ecosystem | Small and largely static. | Vast: Helm, Argo CD, Istio, Prometheus, cert-manager, Kyverno, KEDA, and hundreds more. |
| Managed offerings | None from major clouds. | EKS, AKS, GKE, OpenShift, Rancher, and more. |
| Job market | Marginal. | The default expectation for DevOps/SRE roles. |
4.3 The same app in both — side by side
Three replicas of a web service, published on port 80, with a rolling update policy.
docker swarm init docker service create \ --name web \ --replicas 3 \ --publish published=80,target=8080 \ --update-parallelism 1 \ --update-delay 10s \ --limit-cpu 0.5 --limit-memory 512M \ nginx:1.27 docker service scale web=5 docker service update --image nginx:1.28 web docker service rollback web
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 8080
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "500m", memory: "512Mi" }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
The Swarm version is shorter. That is a real advantage and you should say so honestly. But notice what the Kubernetes version gained: a readiness probe that gates the rollout, a distinction between resource requests (used for scheduling) and limits (used for enforcement), a surge policy that guarantees no capacity dips below 100% during the update, and a manifest that lives in Git and can be reconciled continuously by Argo CD.
4.4 Verdict and interview answer
Kubernetes Architecture — The Big Picture
A Kubernetes cluster is two planes: a control plane that holds and reconciles desired state, and a data plane of worker nodes that actually runs containers. Every component belongs to one of these, and every component talks to exactly one other component — the API server.
5.1 Control plane vs data plane
| Plane | Components | Responsibility |
|---|---|---|
| Control plane | kube-apiserver, etcd, kube-scheduler, kube-controller-manager, cloud-controller-manager | Store desired state, decide what should happen, and drive the cluster towards that state. Runs no user workload in a well-designed cluster. |
| Data plane | kubelet, container runtime, kube-proxy, CNI plugin | Actually run containers, wire up networking, report status upward. |
5.2 The shared-state, level-triggered design
Three architectural rules govern everything. State them to a learner early and they will predict component behaviour correctly for the rest of the course.
Rule 1 — Only the API server touches etcd
No scheduler, no controller, no kubelet ever opens a connection to etcd. This single constraint gives Kubernetes its authentication, authorisation, validation, admission control, auditing and versioning — all enforced in one place. It also means etcd can be replaced (some distributions use SQLite via k3s' kine) without any other component knowing.
Rule 2 — Components never talk to each other
The scheduler does not call the kubelet. The controller manager does not call the scheduler. Every interaction is mediated: a component writes an object, and another component watches for that change. This is a message bus implemented as a database with watch semantics. The benefit is enormous — any component can crash and restart without a handshake protocol, because state is durable and observation is idempotent.
Rule 3 — Watch, do not poll
Components open long-lived HTTP watch streams against the API server and receive incremental updates. Client-go maintains a local informer cache so controllers read from memory rather than hammering the API. When you write a controller or operator, using informers rather than list loops is the difference between a cluster that scales to 5,000 nodes and one that melts at 200.
5.3 End-to-end request flow of kubectl apply
This is the single most valuable walkthrough in Kubernetes education. Interviewers ask it constantly because a complete answer proves you understand every component.
1. kubectl reads kubeconfig, resolves the API server endpoint and credentials.
Converts YAML to JSON, performs client-side discovery of the resource,
then sends POST/PATCH to /apis/apps/v1/namespaces/prod/deployments
2. AUTHENTICATION - API server verifies WHO you are:
client certificate, bearer token, OIDC, or webhook.
3. AUTHORIZATION - RBAC evaluates: may this subject 'create' 'deployments'
in namespace 'prod'?
4. ADMISSION (mutating) - MutatingAdmissionWebhooks and built-in plugins may
CHANGE the object: inject sidecars, add default
ServiceAccount, apply defaulting, set imagePullSecrets.
5. VALIDATION - schema validation against the OpenAPI definition.
6. ADMISSION (validating) - ValidatingAdmissionWebhooks / Policy (Kyverno, OPA,
Pod Security Admission) may REJECT the object.
7. PERSIST - object written to etcd. resourceVersion assigned.
API server returns 201 to kubectl. *** You are done. ***
Everything below happens asynchronously.
8. DEPLOYMENT CONTROLLER sees the new Deployment via its watch.
Creates a ReplicaSet with a pod-template-hash.
9. REPLICASET CONTROLLER sees a ReplicaSet with spec.replicas=3 and 0 existing
pods. Creates 3 Pod objects - with NO nodeName set.
10. SCHEDULER sees 3 Pods with spec.nodeName == "". For each pod:
FILTER (predicates): which nodes are even feasible?
SCORE (priorities): which feasible node is best?
BIND: writes spec.nodeName = node-07 via the API server.
11. KUBELET on node-07 watches for pods bound to itself. Sees the new pod.
- Calls CNI ADD -> pod gets an IP and a veth pair
- Calls CSI -> mounts any volumes
- Calls CRI -> pulls images, creates the pause (sandbox)
container, then app containers
- Runs startup/liveness/readiness probes
- PATCHes pod status back to the API server
12. ENDPOINT / ENDPOINTSLICE CONTROLLER sees the pod become Ready and adds its
IP to the EndpointSlice of any matching Service.
13. KUBE-PROXY on every node watches EndpointSlices and programs iptables/IPVS
rules so the Service ClusterIP now load-balances to the pod.
14. COREDNS already resolves the Service name; traffic now reaches the new pod.
kubectl apply returning "configured" tells you nothing about whether your application is running. The correct CI/CD gate is kubectl rollout status deployment/x --timeout=5m, which waits for the controller to report convergence.
Control Plane Components in Depth
6.1 kube-apiserver
The API server is the front door and the only door. It is a stateless REST server that exposes the entire Kubernetes object model over HTTPS, validates everything, and is the sole writer to etcd. Because it is stateless, you scale it horizontally behind a load balancer.
Responsibilities, in order of execution
- TLS termination and authentication — X.509 client certificates, bearer tokens (ServiceAccount JWTs), OIDC tokens, or an authentication webhook. The result is a username, UID and set of groups.
- Authorization — normally RBAC, optionally Node authorizer, ABAC, or a webhook. Evaluates verb + resource + namespace against Roles and ClusterRoles bound to the subject.
- Mutating admission — built-in plugins plus
MutatingAdmissionWebhook. This is where service meshes inject sidecars and where defaults get applied. - Schema validation — against the OpenAPI v3 schema for the resource, including CRD schemas.
- Validating admission —
ValidatingAdmissionWebhook,ValidatingAdmissionPolicy(CEL-based, built in), ResourceQuota enforcement, Pod Security Admission. - Persistence — write to etcd, assign a
resourceVersion, emit the change to all watchers. - Watch fan-out — maintain long-lived streams to every controller, kubelet and kubectl watch in the cluster.
Key flags you will actually touch
| Flag | Purpose |
|---|---|
--etcd-servers | etcd endpoints. Must be the full HA set. |
--encryption-provider-config | Enables encryption at rest for Secrets. Not on by default. |
--audit-policy-file / --audit-log-path | Audit logging — mandatory in BFSI and regulated environments. |
--enable-admission-plugins | e.g. NodeRestriction,PodSecurity,ResourceQuota,LimitRanger. |
--max-requests-inflight | Throttling. Raise on large clusters; the first knob when the API server saturates. |
--service-cluster-ip-range | CIDR from which Service ClusterIPs are allocated. Immutable after cluster creation. |
--anonymous-auth=false | Hardening. Should be false in production. |
kubectl, no deployments, no scheduling, no controller action, no scaling. But existing pods keep running — kubelet continues managing containers from its local state, and kube-proxy keeps its existing iptables rules. Traffic to healthy pods is unaffected. This is a deliberate and very important property: the control plane is not in the data path.
6.2 etcd
etcd is a distributed, strongly consistent key-value store using the Raft consensus algorithm. It holds the complete state of the cluster: every object, every status field, every secret. It is the only stateful component and therefore the only one that can lose your cluster permanently.
Raft, quorum and why you always use an odd number
Raft elects a leader; all writes go through the leader and are committed once a majority (quorum) of members have persisted them. Quorum for N members is (N/2)+1.
| Members | Quorum | Failures tolerated | Comment |
|---|---|---|---|
| 1 | 1 | 0 | Dev only. Any loss is total loss. |
| 2 | 2 | 0 | Worse than 1. Both must be up — you doubled failure probability for zero benefit. |
| 3 | 2 | 1 | Standard production minimum. |
| 5 | 3 | 2 | Large or highly critical clusters. |
Backup and restore
# --- BACKUP (run on a schedule; ship the file off-cluster) --- ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key # --- RESTORE (stop API server + etcd on ALL control plane nodes first) --- ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-2026-01-15.db \ --data-dir=/var/lib/etcd-restored # Then point the etcd static pod manifest at the new data dir and restart.
Operational rules for etcd
- Put it on fast disks. etcd is fsync-bound. SSD/NVMe is not optional.
- Watch the database size. Default quota is 2 GiB (often raised to 8 GiB). Exceeding it puts etcd into a maintenance alarm and read-only mode.
- Compact and defragment. Kubernetes auto-compacts revisions, but defragmentation must be scheduled — one member at a time, never simultaneously.
- Keep latency between members low. Stretching etcd across regions is a classic mistake; across AZs in one region is fine.
- Encrypt at rest. Secrets are plaintext-adjacent in etcd without
EncryptionConfiguration.
6.3 kube-scheduler
The scheduler watches for pods with an empty spec.nodeName and decides which node each should run on. It does not start anything — its entire output is a single write: the binding.
The two-phase algorithm
Phase 1 — Filtering (predicates): eliminate nodes that cannot run the pod — insufficient resources, hostPort conflict, nodeSelector/affinity mismatch, untolerated taints, zonal volume mismatch, anti-affinity violation, or node conditions (memory/disk/PID pressure, not Ready).
Phase 2 — Scoring (priorities): rank surviving nodes 0–100 across plugins (NodeResourcesFit, ImageLocality, InterPodAffinity, PodTopologySpread, TaintToleration, NodeAffinity), apply weights, pick the highest total.
spec:
priorityClassName: high-priority # enables preemption of lower priority
nodeSelector:
disktype: ssd
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # HARD
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["ap-south-1a", "ap-south-1b"]
podAntiAffinity: # never two replicas on one node
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels: { app: payments-api }
topologyKey: kubernetes.io/hostname
tolerations:
- key: "workload"
operator: "Equal"
value: "banking"
effect: "NoSchedule"
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels: { app: payments-api }
kubectl describe pod <name> and read the Events section. The scheduler writes exactly why each node was rejected — for example 0/12 nodes are available: 5 Insufficient cpu, 4 node(s) had untolerated taint. That one line usually solves the ticket.
6.4 kube-controller-manager
A single binary that runs roughly thirty independent control loops in separate goroutines. Each one watches a resource type and drives reality towards spec. Understanding that it is many controllers, not one, is what makes Kubernetes behaviour predictable.
| Controller | What it reconciles |
|---|---|
| Deployment | Creates and scales ReplicaSets to execute rollouts; manages revision history and rollback. |
| ReplicaSet | Creates/deletes Pods so that the count matching the selector equals spec.replicas. |
| Node | Watches node heartbeats (Leases). Marks nodes NotReady, applies NoExecute taints, triggers eviction after the toleration period (default 300s). |
| Endpoints / EndpointSlice | Maintain the list of ready pod IPs behind each Service. |
| Namespace | On namespace deletion, deletes every object inside it — the source of "namespace stuck in Terminating" when a finalizer blocks. |
| Garbage collector | Deletes objects whose ownerReferences point to a deleted parent — why deleting a Deployment removes its ReplicaSets and Pods. |
| HPA | Reads metrics, computes desired replicas, patches the scale subresource. |
Leader election
In an HA control plane you run three controller-manager instances, but only one is active. They compete for a Lease object; the holder renews it, and if it fails to renew, another takes over. Without this, three controllers would each create three replicas for a three-replica ReplicaSet. The same mechanism protects the scheduler.
6.5 cloud-controller-manager
Kubernetes was originally full of cloud-specific code. That was extracted into the cloud-controller-manager so the core could stay vendor-neutral. On EKS and AKS this component is managed for you, but you must understand what it does because it explains several everyday behaviours.
| Sub-controller | Behaviour |
|---|---|
| Node controller | Enriches Node objects with cloud metadata — instance type, region, zone labels, provider ID — and deletes Node objects when the underlying VM is terminated. |
| Route controller | Programs cloud VPC/VNet route tables so pod CIDRs are reachable between nodes. |
| Service controller | When you create a Service of type LoadBalancer, this creates the actual AWS NLB/ALB or Azure Load Balancer and writes the external IP/hostname into status.loadBalancer. |
kubernetes.io/role/elb tags on public subnets. On bare metal there is no cloud provider — install MetalLB or use NodePort/Ingress.
Worker Node Components in Depth
7.1 kubelet
The kubelet is the node agent — the only component that actually causes containers to exist. It watches the API server for pods bound to its own node, then drives the runtime, network and storage plugins until the observed state matches the pod spec.
The pause container — a question that catches people out
Every pod has a hidden container, usually called pause or the "sandbox" or "infra" container. It does almost nothing: it starts, reaps zombie processes, and sleeps. Its purpose is to own the pod's Linux namespaces. Application containers join the pause container's network and IPC namespaces, which is precisely why containers in a pod share one IP and can reach each other on localhost. If an app container crashes and restarts, the pause container survives, so the pod keeps the same IP.
Node-pressure eviction — the ranking that decides who dies
When a node runs low on memory or disk, kubelet evicts pods before the kernel OOM killer acts. The order is by QoS class, then by how far the pod exceeds its requests:
| QoS class | How you get it | Eviction order |
|---|---|---|
Guaranteed | Every container has requests == limits for both CPU and memory. | Evicted last. Use for critical stateful workloads. |
Burstable | At least one container has a request, but requests != limits. | Evicted second, ordered by usage above requests. |
BestEffort | No requests or limits set at all. | Evicted first. Never ship production workloads this way. |
Static pods
kubelet also reads manifests directly from /etc/kubernetes/manifests/ and runs them without any control plane involvement. This is the bootstrap trick: on a kubeadm cluster, the API server, etcd, scheduler and controller-manager are themselves static pods managed by kubelet.
7.2 Container runtime and the CRI
kubelet does not know how to start a container. It speaks the Container Runtime Interface — a gRPC API — to whatever runtime is installed. This is why "Kubernetes removed Docker" was true and yet changed nothing for developers.
| Layer | Examples | Role |
|---|---|---|
| Orchestrator | kubelet | Decides what should run. |
| CRI interface | gRPC over a unix socket | RuntimeService (sandboxes, containers, exec, logs) and ImageService (pull, list, remove). |
| High-level runtime | containerd, CRI-O | Image pull and management, snapshotting, sandbox lifecycle. |
| Low-level runtime (OCI) | runc, crun, gVisor, Kata | Creates the Linux namespaces, cgroups, seccomp and capabilities, then execs the process. |
docker ps became crictl ps — and nothing in the developer workflow."
7.3 kube-proxy and Service implementation
kube-proxy runs on every node and implements the Service abstraction. It does not proxy traffic itself in normal modes — it programs the kernel to do the load balancing, then gets out of the way.
| Mode | Mechanism | Characteristics |
|---|---|---|
iptables | Chains of NAT rules with statistical matching. | Default for years. Random selection. O(n) rule evaluation — degrades beyond a few thousand services. |
ipvs | Linux IPVS kernel load balancer with hash tables. | O(1) lookup, scales to tens of thousands of services. Recommended for large clusters. |
nftables | Newer kernel packet framework. | Better scaling and update performance than iptables mode. |
eBPF (no kube-proxy) | Cilium/Calico replace kube-proxy with eBPF programs. | Lowest latency, best scale, socket-level load balancing. Increasingly the production choice. |
| Service type | Behaviour |
|---|---|
ClusterIP | Default. Virtual IP reachable only inside the cluster. |
NodePort | Opens the same high port (30000–32767) on every node. |
LoadBalancer | NodePort plus a cloud load balancer provisioned by the cloud-controller-manager. |
ExternalName | No proxying — CoreDNS returns a CNAME to an external DNS name. |
Headless (clusterIP: None) | No virtual IP. DNS returns pod IPs directly. Essential for StatefulSets. |
Cluster (default) lets any node forward to a pod on any node — even spread, but an extra hop and the client source IP is lost to SNAT. Local forwards only to pods on the receiving node — preserves the client IP and removes the hop, but traffic is uneven if pods are not spread evenly. Choose Local when you need real client IPs; pair it with a topology spread constraint.
Cluster Add-ons
A cluster with only the core components can schedule pods — and almost nothing else. No DNS, no metrics, no ingress, no dynamic storage. Add-ons fill that gap. They are ordinary Kubernetes workloads that happen to provide platform capability — Kubernetes extends itself using its own primitives.
8.1 CoreDNS
CoreDNS is the cluster DNS server, running as a Deployment in kube-system fronted by a Service with a well-known ClusterIP (typically 10.96.0.10). Every kubelet writes that IP into every pod's /etc/resolv.conf.
| Record | Resolves to |
|---|---|
payments | The Service in the caller's own namespace (via search domains). |
payments.production.svc.cluster.local | The fully qualified name. ClusterIP of the Service. |
postgres-0.postgres.production.svc.cluster.local | A specific StatefulSet pod via a headless Service. |
ndots:5 means any name with fewer than five dots is first tried against every search domain. Looking up api.stripe.com (2 dots) generates four failing queries before the correct one. At high request rates this floods CoreDNS. Fix: use a trailing dot (api.stripe.com.), set dnsConfig.options: [{name: ndots, value: "2"}], or enable NodeLocal DNSCache.
8.2 metrics-server and the metrics pipeline
| Pipeline | Provider | Purpose |
|---|---|---|
Resource metrics (metrics.k8s.io) | metrics-server | Live CPU/memory only, not stored. Powers kubectl top and default HPA targets. |
| Custom/external metrics | Prometheus Adapter, KEDA | Any metric — request rate, queue depth, latency. Powers advanced HPA scaling. |
| Monitoring (not an API) | Prometheus, Grafana | Long-term storage, dashboards, alerting, SLOs. |
8.3 Ingress controllers
An Ingress object is only a set of L7 routing rules stored in the API. It does nothing on its own. An Ingress controller (ingress-nginx, AWS Load Balancer Controller, AGIC, Traefik, or a Gateway API implementation) is the workload that watches Ingress objects and configures a real proxy. Without a controller installed, creating an Ingress produces no effect whatsoever.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: banking-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: ["api.bank.example.com"]
secretName: bank-tls # cert-manager creates and renews this
rules:
- host: api.bank.example.com
http:
paths:
- path: /payments
pathType: Prefix
backend:
service:
name: payments
port:
number: 80
8.4 CSI storage drivers
The Container Storage Interface moved vendor code out of the Kubernetes tree into independently released drivers. A CSI driver has a controller Deployment (provision, delete, attach, snapshot) and a node DaemonSet (stage, mount, unmount).
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp3-encrypted provisioner: ebs.csi.aws.com parameters: type: gp3 encrypted: "true" reclaimPolicy: Delete allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer # bind only after scheduling
Immediate binding, the volume is provisioned in an arbitrary zone before the scheduler picks a node. If the pod later lands in a different zone, it is stuck Pending forever with a volume node affinity conflict. WaitForFirstConsumer delays provisioning until placement is known.
8.5 Autoscalers, dashboard and other add-ons
| Add-on | Purpose |
|---|---|
| Cluster Autoscaler | Adds nodes when pods are Pending for capacity; removes underutilised nodes. Node-level. |
| Karpenter | Provisions right-sized instances directly from pending pod requirements — usually faster and cheaper. |
| KEDA | Event-driven autoscaling from queue depth, Kafka lag, cron — including scale-to-zero. |
| cert-manager | Issues and auto-renews TLS certificates from Let's Encrypt or an internal CA. |
| external-secrets | Syncs secrets from Vault, AWS Secrets Manager or Azure Key Vault into the cluster. |
| Kyverno / OPA Gatekeeper | Policy as code: block untrusted registries, require limits, enforce labels. |
| kube-state-metrics | Exports the state of Kubernetes objects as Prometheus metrics. |
CNI and Cluster Networking
Kubernetes deliberately ships no networking implementation. It defines a model, and the Container Network Interface lets any plugin satisfy it. This is why a fresh cluster has every pod stuck in ContainerCreating until you install a CNI — and why "which CNI?" is one of the highest-consequence decisions in cluster design.
9.1 The four rules of the Kubernetes network model
- Every pod gets its own unique IP address across the whole cluster.
- Pods can communicate with all other pods on any node without NAT. The IP a pod sees itself as is the IP other pods see it as.
- Agents on a node (kubelet, system daemons) can reach all pods on that node.
- Pods in the host network namespace can communicate with all pods on all nodes without NAT.
9.2 What the CNI specification actually is
- Plugin binaries live in
/opt/cni/bin/. - Plugin configuration lives in
/etc/cni/net.d/as a JSON conflist, evaluated in lexical order — the first file wins. - The runtime invokes a binary with a verb via environment variables and JSON on stdin:
ADD,DEL,CHECK,VERSION. - The plugin returns JSON describing the interfaces, IPs, routes and DNS it configured.
1. kubelet asks the runtime to create the pod sandbox (pause container) -> a new, empty network namespace exists 2. Runtime invokes the CNI plugin with CNI_COMMAND=ADD and the namespace path 3. Plugin creates a veth pair: eth0 inside the pod, calixxxxx on the host 4. IPAM allocates an address from the node pod CIDR (or the VPC) 5. Plugin assigns the IP to eth0, sets the default route via the host end 6. Plugin programs host routing / eBPF maps so other nodes can reach this IP 7. Plugin returns JSON; kubelet records podIP in pod.status 8. On pod deletion, CNI_COMMAND=DEL tears it down and releases the IP
9.3 Plugin comparison
| Plugin | Data path | NetworkPolicy | Best suited to |
|---|---|---|---|
| Flannel | VXLAN overlay (or host-gw) | No (needs Calico) | Learning, simple clusters. |
| Calico | BGP routing, or VXLAN/IPIP overlay; eBPF mode available | Yes — rich policy | The most common production choice. |
| Cilium | eBPF end-to-end; can replace kube-proxy | Yes — including L7 | Large, performance/security-sensitive clusters. Hubble observability. |
| AWS VPC CNI | Pod IPs from the VPC subnet via ENI secondary addresses — no overlay | No natively (add Calico) | EKS default. Pods are first-class VPC citizens. |
| Azure CNI | Pod IPs from the VNet subnet (or Overlay mode) | Azure NP or Calico | AKS. Overlay mode when VNet space is scarce. |
(ENIs x (IPs per ENI - 1)) + 2. A t3.medium tops out around 17 pods regardless of free CPU/memory. Mitigations: enable prefix delegation (ENABLE_PREFIX_DELEGATION=true, assigning /28 blocks), use larger instances, add secondary CIDR blocks, or switch to custom networking.
9.4 NetworkPolicy deep dive
By default, every pod can reach every other pod in the cluster. NetworkPolicy stops that. Three rules govern its semantics:
- Policies are additive and allow-only. There is no deny rule. The union of all matching policies is what is permitted.
- A pod is "isolated" for a direction the moment any policy selects it for that direction. Before that, everything is allowed; after, only what is explicitly allowed.
- The CNI must implement it. Applying a NetworkPolicy with Flannel or plain AWS VPC CNI silently does nothing — one of the most dangerous false-security situations in Kubernetes.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # ALL pods in this namespace
policyTypes: [Ingress, Egress]
# no rules listed = nothing allowed
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payments-api-policy
namespace: production
spec:
podSelector:
matchLabels: { app: payments-api }
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector: { matchLabels: { app: web-frontend } }
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector: { matchLabels: { app: postgres } }
ports:
- protocol: TCP
port: 5432
from block, a namespaceSelector and podSelector as separate list items are OR; as two keys inside one item they are AND. The indentation changes the meaning — and the permissive interpretation is the accidental one.(2) NetworkPolicy works on pod IPs, not Service IPs. Never target a ClusterIP; target the pods behind it. Also remember to allow DNS egress (UDP/TCP 53 to kube-dns) or everything breaks.
Namespace
A Namespace is a virtual cluster inside a physical cluster: a scope for names, a target for RBAC and quotas, and a boundary for policy. It is the primary multi-tenancy primitive — and it is important to know exactly how weak that boundary is by default.
10.1 YAML anatomy and lifecycle
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
kubernetes.io/metadata.name: production # set automatically; you select on it
environment: production
team: payments
# Pod Security Admission — enforce the restricted profile
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
Namespaced vs cluster-scoped
| Namespaced | Cluster-scoped |
|---|---|
| Pod, Deployment, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob, Service, Ingress, ConfigMap, Secret, PVC, ServiceAccount, Role, RoleBinding, NetworkPolicy, HPA, ResourceQuota, LimitRange | Node, PersistentVolume, StorageClass, ClusterRole, ClusterRoleBinding, CustomResourceDefinition, Namespace itself, IngressClass, PriorityClass |
Terminating namespaces — the stuck-forever problem
Deleting a namespace triggers the namespace controller to delete every object inside it. If any object has a finalizer that never completes — commonly a CRD whose operator has already been uninstalled — the namespace sits in Terminating indefinitely.
/api/v1/namespaces/x/finalize makes the namespace disappear but orphans the real resources — cloud load balancers, disks and DNS records keep existing and keep billing. Always find and fix the actual blocking finalizer first.
10.2 ResourceQuota and LimitRange
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "100"
requests.memory: 200Gi
limits.cpu: "200"
limits.memory: 400Gi
pods: "300"
services.loadbalancers: "5" # cost control — each one is a real cloud LB
services.nodeports: "0" # forbid NodePort entirely
requests.cpu or limits.memory in a namespace, every new pod must declare those fields or creation is rejected. Existing pods keep running, but the next rollout fails with must specify limits.memory. Pair every ResourceQuota with a LimitRange so defaults are supplied automatically.
apiVersion: v1
kind: LimitRange
metadata:
name: production-limits
namespace: production
spec:
limits:
- type: Container
default: { cpu: "500m", memory: "512Mi" } # applied as limits
defaultRequest: { cpu: "100m", memory: "128Mi" } # applied as requests
max: { cpu: "4", memory: "8Gi" }
min: { cpu: "50m", memory: "64Mi" }
Pod — The Atomic Unit
11.1 Why a Pod and not a container
A Pod is one or more containers that are always co-scheduled on the same node, share a network namespace and IP, share IPC, and can share volumes. It is the smallest thing Kubernetes schedules — you cannot schedule a container.
Why introduce this extra layer? Because some processes are genuinely a single unit of deployment even though they are separate binaries — a web server and a log shipper that tails its files; an application and a proxy that terminates mTLS on its behalf. These must live on the same machine, start together, die together, and share a filesystem or loopback interface.
11.2 A production-grade Pod spec (abridged)
apiVersion: v1
kind: Pod
metadata:
name: payments-api
namespace: production
labels: { app: payments-api, tier: backend, version: "2.4.1" }
spec:
serviceAccountName: payments-sa
automountServiceAccountToken: false # only mount if the app calls the API
terminationGracePeriodSeconds: 60
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile: { type: RuntimeDefault }
initContainers:
- name: wait-for-db # run to completion, before app starts
image: busybox:1.36
command: ["sh","-c","until nc -z postgres 5432; do sleep 2; done"]
containers:
- name: app
image: registry.example.com/payments-api:2.4.1 # NEVER use :latest
ports:
- { name: http, containerPort: 8080 }
env:
- name: POD_NAME # downward API
valueFrom: { fieldRef: { fieldPath: metadata.name } }
- name: DB_PASSWORD
valueFrom: { secretKeyRef: { name: payments-db, key: password } }
resources:
requests: { cpu: "250m", memory: "512Mi" } # used for SCHEDULING
limits: { cpu: "1", memory: "1Gi" } # used for ENFORCEMENT
startupProbe: # protects slow starters from liveness
httpGet: { path: /healthz/startup, port: http }
failureThreshold: 30
periodSeconds: 5
livenessProbe: # failing => container RESTARTED
httpGet: { path: /healthz/live, port: http }
periodSeconds: 10
readinessProbe: # failing => removed from endpoints
httpGet: { path: /healthz/ready, port: http }
periodSeconds: 5
lifecycle:
preStop:
exec: { command: ["sh","-c","sleep 10"] }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
11.3 Lifecycle, phases and conditions
Phases (Pending, Running, Succeeded, Failed, Unknown) are coarse. Conditions carry the useful detail: PodScheduled, Initialized, ContainersReady, Ready. A pod can be Running and not Ready — exactly the state where readiness probes are keeping it out of the load balancer.
CrashLoopBackOff is not an error — it is a backoff timer. The container is exiting, kubelet is restarting it, and the delay doubles: 10s, 20s, 40s, 80s, 160s, capped at 5 minutes. The actual failure reason is in the previous container's logs.
kubectl logs payments-api-7d9f --previous # logs of the CRASHED instance kubectl describe pod payments-api-7d9f # events + exit codes + reasons # Exit code meanings # 0 clean 1 app error 137 SIGKILL (usually OOMKilled) # 139 SIGSEGV 143 SIGTERM 127 command not found
11.4 Probes, init containers, sidecars
| Probe | On failure | Use it for |
|---|---|---|
| startupProbe | Kills the container after threshold; suppresses liveness/readiness until it first succeeds. | Slow-starting apps — JVM warm-up, large cache load, migrations. |
| livenessProbe | Container is restarted. | Deadlock detection only. Test that this process is alive — never a dependency. |
| readinessProbe | Pod IP removed from Service endpoints. No restart. | "Can I serve traffic right now?" Legitimately checks dependencies and warm caches. |
11.5 Resources, QoS classes and eviction
| Resource | Exceeding the limit | Implication |
|---|---|---|
| CPU | Throttled by the CFS scheduler. Slowed, not killed. | CPU is compressible. Symptom of a too-low limit is latency, not crashes — invisible unless you watch container_cpu_cfs_throttled_seconds_total. |
| Memory | OOMKilled — the kernel terminates the process immediately. | Memory is incompressible. No graceful degradation. Exit code 137. |
11.6 Scheduling controls and security context
- Taints are on nodes and repel pods: "nothing runs here unless it explicitly tolerates me."
- Tolerations are on pods and merely permit scheduling onto a tainted node — they do not attract.
- Node affinity is on pods and attracts: "put me on nodes that look like this."
- To dedicate nodes to a workload you need both: taint the nodes and add node affinity/nodeSelector to the workload.
kubectl taint nodes node-07 workload=banking:NoSchedule # NoSchedule - no new pods without a matching toleration # PreferNoSchedule - soft; scheduler avoids if it can # NoExecute - as NoSchedule, AND evicts running pods that do not tolerate kubectl taint nodes node-07 workload=banking:NoSchedule- # trailing dash removes
Deployment and ReplicaSet
12.1 The three-object chain
You create a Deployment. It creates a ReplicaSet. That creates Pods. Three objects, three controllers, one purpose — and understanding the division of labour explains every rollout behaviour you will ever debug.
Deployment payments-api owns rollout strategy, revision history, rollback | |-- ReplicaSet ...-6d4b8f9c7 (revision 3 - CURRENT, replicas: 3) | |-- Pod ...-x7k2p | |-- Pod ...-m9q4r | |-- Pod ...-t2v8n |-- ReplicaSet ...-5f9c7d8b6 (revision 2 - OLD, replicas: 0) |-- ReplicaSet ...-4c8b6a5d9 (revision 1 - OLD, replicas: 0) Old ReplicaSets are kept, scaled to zero, so rollback is instant. revisionHistoryLimit controls how many are retained (default 10).
The pod-template-hash label makes this work. The Deployment controller hashes the pod template and adds it to the ReplicaSet's selector and pods' labels. Change the template, get a new hash, get a new ReplicaSet. Change only replicas and the hash is unchanged — which is why scaling does not create a new revision.
12.2 Production Deployment YAML (abridged)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
namespace: production
annotations:
kubernetes.io/change-cause: "Upgrade to 2.4.1 - fixes settlement retry bug"
spec:
replicas: 6
selector:
matchLabels: { app: payments-api } # IMMUTABLE after creation
revisionHistoryLimit: 5
progressDeadlineSeconds: 600 # mark Failed if no progress in 10 min
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 0 # never dip below replicas ready - safest
template:
metadata:
labels: { app: payments-api, version: "2.4.1" }
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector: { matchLabels: { app: payments-api } }
containers:
- name: app
image: registry.example.com/payments-api:2.4.1
resources:
requests: { cpu: "250m", memory: "512Mi" }
limits: { cpu: "1", memory: "512Mi" }
---
apiVersion: policy/v1
kind: PodDisruptionBudget # protects you during VOLUNTARY disruption
metadata:
name: payments-api-pdb
namespace: production
spec:
minAvailable: 4
selector:
matchLabels: { app: payments-api }
spec.selector on an existing Deployment — it fails with field is immutable. The only path is delete and recreate. Choose selector labels carefully on day one and keep them minimal (app: name is enough; do not put version in the selector).
12.3 Rolling update mathematics
With replicas: 10, maxSurge: 2, maxUnavailable: 2: maximum total pods at any moment is replicas + maxSurge = 12; minimum available is replicas - maxUnavailable = 8. The controller never proceeds past a batch whose new pods are not Ready — a broken image or failing readiness probe stalls the rollout and leaves the old pods serving traffic, which is exactly the desired behaviour.
| Configuration | Behaviour |
|---|---|
maxSurge: 1, maxUnavailable: 0 | Safest default. Full capacity maintained throughout. Requires headroom for one extra pod. |
maxSurge: 0, maxUnavailable: 1 | Never exceeds the replica count. Use when capacity is hard-capped. Capacity dips during rollout. |
strategy.type: Recreate | Terminate everything, then start the new version. Causes downtime. Necessary for RWO volumes or incompatible schema migrations. |
12.4 Rollout, rollback and deployment strategies
kubectl set image deployment/payments-api app=payments-api:2.4.2 -n production kubectl rollout status deployment/payments-api -n production --timeout=10m # gate CI on this kubectl rollout history deployment/payments-api -n production kubectl rollout undo deployment/payments-api -n production # previous kubectl rollout undo deployment/payments-api --to-revision=3 -n production kubectl rollout restart deployment/payments-api -n production # re-roll same image
Beyond native rolling updates: Blue-Green (two full Deployments, flip the Service selector — instant switch and rollback, needs double capacity); Canary (small traffic percentage to the new version, promoted on metrics via Argo Rollouts or Flagger); and Shadow (duplicate real traffic to the new version, discard responses).
configMapGenerator with a hash suffix).
DaemonSet
13.1 Use cases and scheduling behaviour
A DaemonSet guarantees that a copy of a pod runs on every node matching its selector. When a node joins the cluster, a pod appears on it automatically. When a node leaves, the pod is garbage collected. There is no replicas field — the node count is the replica count.
| Category | Examples |
|---|---|
| Networking | calico-node, cilium-agent, aws-node (VPC CNI), kube-proxy, NodeLocal DNSCache |
| Storage | CSI node drivers (ebs-csi-node, azuredisk-csi-node) |
| Logging | Fluent Bit, Fluentd, Filebeat, Vector, Datadog Agent |
| Metrics | node-exporter, New Relic infrastructure agent |
| Security | Falco, Sysdig, vulnerability scanners |
Since v1.12 DaemonSet pods are scheduled by the default scheduler using node affinity the controller injects, so they respect taints, priority, preemption and resource availability — and can be left Pending if a node is full.
priorityClassName: system-node-critical for CNI/CSI/kube-proxy-class workloads and system-cluster-critical for cluster-wide essentials.
13.2 Production DaemonSet YAML (abridged)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels: { app: fluent-bit }
# NOTE: there is NO spec.replicas — node count determines it
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # how many nodes updated at once
template:
metadata:
labels: { app: fluent-bit }
spec:
tolerations:
- operator: Exists # tolerate EVERYTHING (run on every node)
priorityClassName: system-node-critical
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.0.7
resources:
requests: { cpu: "50m", memory: "96Mi" } # SMALL - charged on every node
limits: { cpu: "300m", memory: "256Mi" }
volumeMounts:
- { name: varlog, mountPath: /var/log, readOnly: true }
volumes:
- name: varlog # hostPath is the defining characteristic:
hostPath: # DaemonSets exist to read/write the NODE
path: /var/log
type: Directory
13.3 Tolerations, host access and update strategy
tolerations: [{operator: Exists}] means "tolerate every taint, including ones invented later." For a logging or security agent that must cover every node, that is correct. For anything else it is dangerous — your pod lands on cordoned nodes, reserved GPU nodes, and nodes under disk pressure. Prefer enumerating specific taints.
kubectl -n logging get pods -l app=fluent-bit -o wide
kubectl get nodes -o name | sed 's|node/||' | sort > /tmp/all-nodes
kubectl -n logging get pods -l app=fluent-bit \
-o jsonpath='{.items[*].spec.nodeName}' | tr ' ' '\n' | sort > /tmp/covered
comm -23 /tmp/all-nodes /tmp/covered
StatefulSet
14.1 Stable identity, ordering and storage
A Deployment treats its pods as interchangeable cattle: random names, random start order, shared storage or none. A StatefulSet treats them as individuals with permanent identities. That is exactly what clustered databases, queues and consensus systems require.
| Guarantee | Detail |
|---|---|
| Stable network identity | Pods are named <sts>-<ordinal> — postgres-0, postgres-1. The name survives rescheduling. With a headless Service, each pod gets its own DNS record. |
| Stable storage | volumeClaimTemplates creates one PVC per pod. If postgres-1 is rescheduled, it reattaches to its own volume. |
| Ordered lifecycle | Creation is sequential 0 → 1 → 2, each waiting for the previous to be Ready. Deletion and scale-down are reverse order. Updates go highest ordinal to lowest. |
14.2 Production StatefulSet YAML (abridged)
apiVersion: v1
kind: Service
metadata: { name: postgres, namespace: production }
spec:
clusterIP: None # HEADLESS: DNS returns individual pod IPs
selector: { app: postgres }
ports: [{ name: postgres, port: 5432 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: postgres, namespace: production }
spec:
serviceName: postgres # MUST match the headless Service name
replicas: 3
selector:
matchLabels: { app: postgres }
podManagementPolicy: OrderedReady # OrderedReady | Parallel
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # only ordinals >= partition are updated
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain # keep data if the STS is deleted
whenScaled: Retain
template:
metadata:
labels: { app: postgres }
spec:
terminationGracePeriodSeconds: 120 # give the DB time to checkpoint
affinity:
podAntiAffinity: # never two DB replicas on one node
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: { matchLabels: { app: postgres } }
topologyKey: kubernetes.io/hostname
containers:
- name: postgres
image: postgres:16.4
resources:
requests: { cpu: "1", memory: "4Gi" }
limits: { cpu: "2", memory: "4Gi" } # Guaranteed QoS
volumeClaimTemplates:
- metadata: { name: data } # produces data-postgres-0, -1, -2
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-encrypted
resources: { requests: { storage: 500Gi } }
14.3 Headless Services and DNS records
# Headless Service name -> A records for ALL ready pod IPs postgres.production.svc.cluster.local -> 10.244.1.7, 10.244.2.9, 10.244.3.4 # Per-pod stable record - THIS is the point of a StatefulSet postgres-0.postgres.production.svc.cluster.local -> 10.244.1.7 postgres-1.postgres.production.svc.cluster.local -> 10.244.2.9
Because names are deterministic, a clustered application configures itself from its own ordinal — a Kafka broker's ID is the ordinal, a Postgres replica knows the primary is always -0. No service registry, no discovery protocol.
14.4 Scaling, updates and the operational traps
| Trap | What happens and what to do |
|---|---|
| PVCs are never deleted automatically | Deleting the StatefulSet leaves every PVC and its cloud disk behind — and its bill. Handle deliberately with persistentVolumeClaimRetentionPolicy or explicit cleanup. |
| Scaling down destroys quorum | Scaling a 3-node etcd/Zookeeper/Kafka to 1 breaks consensus. Kubernetes will happily do it. Scale stateful systems only with a documented procedure. |
| volumeClaimTemplates is immutable | You cannot resize storage by editing the template. Patch each PVC individually (with allowVolumeExpansion: true), then recreate the STS with --cascade=orphan. |
| A stuck pod blocks everything | With OrderedReady, if postgres-1 never becomes Ready, postgres-2 is never created. Look at the lowest unhealthy ordinal. |
| Node failure with RWO volumes | An EBS/Azure Disk cannot detach from an unreachable node immediately. The pod stays Terminating and the replacement Pending until the attach-detach controller forces detachment (six minutes by default). |
| StatefulSet is not a database operator | It gives identity and storage, nothing about replication, failover or backup. For production databases use an operator — CloudNativePG, Percona, Strimzi for Kafka. |
Choosing the Right Workload Object
| Object | Use when | Do not use when |
|---|---|---|
| Pod | Debugging, one-off exec containers, teaching. | Anything in production — a bare Pod is never recreated if its node dies. |
| ReplicaSet | Almost never created directly. | Use a Deployment, which manages ReplicaSets and adds rollout/rollback. |
| Deployment | Stateless services: APIs, web front ends, workers with no local state. The default. | The workload needs stable names, ordered startup, or per-replica storage. |
| StatefulSet | Databases, message brokers, consensus systems. | The app is stateless — StatefulSets are slower to scale and recover. |
| DaemonSet | Node-level agents: logging, metrics, CNI, CSI, security. | You want N replicas independent of node count. |
| Job | Run-to-completion batch: migrations, reports, ETL, backfills. | The work must run continuously. |
| CronJob | Scheduled Jobs: nightly backups, periodic reconciliation. | Sub-minute scheduling, or work that must never overlap (without concurrencyPolicy: Forbid). |
Does it run once and finish?
|- YES -> on a schedule? --- YES -> CronJob
| --- NO -> Job
|- NO -> does it need one pod on EVERY node?
|- YES -> DaemonSet
|- NO -> does any replica need a stable name,
a stable disk, or ordered startup?
|- YES -> StatefulSet (and consider an Operator)
|- NO -> Deployment <- the answer ~80% of the time
Troubleshooting Playbook
16.1 Pod will not start
| Status | Likely cause | First command |
|---|---|---|
Pending | No node satisfies the pod: insufficient CPU/memory, untolerated taint, affinity conflict, unbound PVC, zonal volume mismatch. | kubectl describe pod X — read the scheduler's rejection line. |
ImagePullBackOff | Wrong image/tag, private registry without imagePullSecrets, registry rate limit, no route. | kubectl describe pod X; then crictl pull <image>. |
CrashLoopBackOff | Process exits immediately: bad config, missing env var, failed dependency, over-aggressive liveness. | kubectl logs X --previous |
OOMKilled (137) | Memory limit too low, or a leak. Heap not aligned with the container limit. | kubectl describe pod X then check memory over time. |
CreateContainerConfigError | A referenced ConfigMap or Secret key does not exist. | kubectl describe pod X names the missing object. |
ContainerCreating (stuck) | CNI failure (no IP), volume mount failure, or image still pulling. | kubectl describe pod X; then journalctl -u kubelet. |
Terminating (stuck) | A finalizer, a preStop hook that never returns, or a volume that will not detach. | kubectl get pod X -o yaml | grep -A5 finalizers |
16.2 Service or DNS problems
# 1. Does the Service have endpoints at all?
kubectl get endpointslices -l kubernetes.io/service-name=payments -n production
# EMPTY => selector matches no READY pod (label mismatch OR readiness failing)
# 2. Compare selector to actual pod labels
kubectl get svc payments -n production -o jsonpath='{.spec.selector}'
kubectl get pods -n production --show-labels | grep payments
# 3. Reach the pod directly, bypassing the Service?
kubectl -n production port-forward pod/payments-abc123 8080:8080
# 4. Reach the ClusterIP from another pod?
kubectl -n production run tester --rm -it --image=nicolaka/netshoot -- \
curl -v http://payments:80/healthz
# 5. Is DNS working?
kubectl -n production run tester --rm -it --image=nicolaka/netshoot -- \
nslookup payments.production.svc.cluster.local
# 6. Is a NetworkPolicy blocking it?
kubectl get networkpolicy -n production
16.3 Node and cluster level
kubectl get nodes -o wide kubectl describe node node-07 # conditions, allocatable, taints, pods kubectl top nodes kubectl get pods -A --field-selector spec.nodeName=node-07 -o wide # Safely take a node out of service kubectl cordon node-07 kubectl drain node-07 --ignore-daemonsets --delete-emptydir-data --grace-period=60 kubectl uncordon node-07 # Control plane health kubectl get --raw='/readyz?verbose' kubectl get events -A --sort-by='.lastTimestamp' | tail -50
16.4 The universal debugging toolkit
# Ephemeral debug container on a running pod (image has no shell? fine) kubectl debug -it payments-abc123 --image=nicolaka/netshoot --target=app # Debug pod on a specific node, host filesystem access kubectl debug node/node-07 -it --image=busybox # What can this ServiceAccount actually do? kubectl auth can-i --list --as=system:serviceaccount:production:payments-sa # Diff before applying — essential in GitOps reviews kubectl diff -f deployment.yaml
Command and YAML Reference
17.1 Discovery and self-documentation
kubectl api-resources # every kind, its group, short name kubectl api-versions # every group/version served kubectl explain deployment.spec.strategy # the schema, from the live server kubectl explain pod.spec.containers.resources --recursive kubectl create deployment web --image=nginx --dry-run=client -o yaml > deploy.yaml
17.2 Imperative generators (scaffold fast, then commit the YAML)
kubectl create ns production kubectl create deployment web --image=nginx:1.27 --replicas=3 --dry-run=client -o yaml kubectl expose deployment web --port=80 --target-port=8080 --dry-run=client -o yaml kubectl create configmap app-config --from-file=./config/ --dry-run=client -o yaml kubectl create secret generic db-creds --from-literal=password=s3cr3t --dry-run=client -o yaml kubectl create job migrate --image=myapp:1.0 -- /app/migrate kubectl create cronjob backup --image=backup:1.0 --schedule="0 2 * * *" -- /backup.sh kubectl create rolebinding dev-binding --role=dev --serviceaccount=production:payments-sa
17.3 Everyday operations
kubectl get pods -A -o wide
kubectl get pods --field-selector status.phase=Failed -A
kubectl get pods --sort-by=.status.containerStatuses[0].restartCount -A
kubectl logs -f deployment/payments-api --all-containers --max-log-requests=10
kubectl logs -l app=payments-api --tail=100 --since=15m --prefix
kubectl exec -it payments-abc123 -c app -- sh
kubectl port-forward svc/payments 8080:80 -n production
kubectl apply -k ./overlays/production # Kustomize
kubectl patch deployment payments-api -p '{"spec":{"replicas":8}}'
17.4 The manifest skeletons
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: payments-api, namespace: production }
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: payments-api }
minReplicas: 4
maxReplicas: 40
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }
behavior:
scaleDown: { stabilizationWindowSeconds: 300 } # avoid flapping
---
apiVersion: batch/v1
kind: CronJob
metadata: { name: nightly-backup, namespace: production }
spec:
schedule: "0 2 * * *"
timeZone: "Asia/Kolkata"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: backup-tool:2.1
command: ["/backup.sh"]
Where to go next
The natural progression from this handbook: Helm for packaging, Kustomize for environment overlays, Argo CD for GitOps delivery, Prometheus and Grafana for SLO-driven operations, and Operators and CRDs for extending the control plane. Certification paths worth targeting are CKA (operations), CKAD (workloads) and CKS (security) — all hands-on. Cloud Soft Solutions runs these in the APEX & NEXUS programs in Ameerpet, Hyderabad.
How to Prepare and Where to Go Next
This handbook covers the fundamentals interviewers and the CKA/CKAD/CKS exams test hardest. To go job-ready, pair it with hands-on practice and our related guides: the Cloud + DevOps & Agentic AI roadmap, the Fresher-to-Hired 2026 roadmap, and our AI & ML and RAG interview-question sets. Browse live openings in the 2026 fresher jobs hub.
Learn Kubernetes Hands-On
APEX — AI, ML, Cloud & Cyber Security Engineering Program
Docker, Kubernetes, Terraform, CI/CD and cloud-native deployment on real projects, with interview prep and a 100% placement guarantee — in Ameerpet, Hyderabad (classroom + live online).
Explore the APEX Program →📞 Want a structured DevOps/Kubernetes learning path or placement help? Call or WhatsApp +91 96660 19191 / +91 99496 16388, or email info@cloudsoftsol.com. Explore our Cloud & DevOps training and full course catalogue.
Frequently Asked Questions
What is Kubernetes in simple terms?
Kubernetes (K8s) is an open-source platform that automates the deployment, scaling, healing and operation of containerised applications across a fleet of machines. You declare a desired state — how many replicas, how much CPU, which config — and Kubernetes continuously compares that to reality and closes the gap, restarting crashed containers, rescheduling pods off dead nodes and rolling out new versions safely.
What are the main Kubernetes control plane components?
The control plane is the API server (the only front door and the sole writer to etcd), etcd (the strongly-consistent key-value store holding all cluster state), the scheduler (which binds pods to nodes via filtering and scoring), the controller-manager (roughly 30 reconciliation loops such as Deployment, ReplicaSet and Node controllers), and the cloud-controller-manager (which provisions cloud load balancers and enriches node metadata).
Deployment vs StatefulSet — when do I use which?
Use a Deployment for stateless services (APIs, web front ends, workers) where pods are interchangeable. Use a StatefulSet when replicas need a stable identity, ordered startup, or per-replica persistent storage — databases, message brokers and consensus systems like Kafka, PostgreSQL or etcd. For production databases, prefer an operator (CloudNativePG, Strimzi) that builds on StatefulSets.
Why is a Pod not just a container?
A Pod is one or more containers that are always co-scheduled on the same node and share a network namespace (one IP, mutual localhost), IPC and volumes. This lets genuinely-coupled processes — an app plus a log shipper or an mTLS proxy sidecar — start, stop and communicate as one unit, without running a process supervisor inside a container. The Pod, not the container, is the smallest unit Kubernetes schedules.
Is Kubernetes worth learning in 2026?
Yes. Kubernetes is the default expectation for DevOps, SRE, Cloud and platform-engineering roles, and is now also the standard substrate for AI/ML and LLM workloads. Command fluency (kubectl), understanding the control plane, and hands-on work with Deployments, Services, networking and the CKA/CKAD/CKS certifications remain among the highest-return skills for cloud engineers.
