Interview Q&A Kubernetes All Levels

Kubernetes Interview Questions & Answers — Compute & Storage

Kubernetes interview questions on scheduling, resource requests/limits, StatefulSets, DaemonSets, Jobs and CronJobs, Volumes, PersistentVolumes/Claims, StorageClasses, and autoscaling with HPA/VPA/Cluster Autoscaler.

43 min read 30 Questions
30 Total Questions
30 Basic
Level:

Kubernetes Workloads

Q1
What is a Pod? Explain its purpose, lifecycle, networking, storage, and relationship with containers.
Basic

Ans: A Pod is the smallest deployable unit in Kubernetes, and understanding it well really means understanding four separate things it bundles together: containers, networking, storage, and a lifecycle that governs all of it.

Purpose: Kubernetes never schedules a bare container - it always schedules a Pod, which wraps one or more containers that are meant to be co-located and treated as a single unit of deployment. Most Pods run exactly one container, but a Pod exists as a concept specifically so that tightly-coupled helper containers (sidecars, log shippers, proxies) can share the same execution context as the main application without being merged into one image.

Relationship with containers: every container inside a Pod is scheduled together, starts and stops together, and lives on the same node - you can’t have containers from the same Pod spread across different nodes. Init containers run first, sequentially, to completion, before any regular containers start; regular containers then run concurrently for the life of the Pod.

Networking: all containers in a Pod share the same network namespace - the same IP address and the same localhost. That means two containers in one Pod can reach each other over localhost:<port> directly, no Service or DNS lookup required, which is exactly what makes patterns like a sidecar proxy intercepting the main container’s traffic possible.

Storage: a Pod can define volumes at the Pod level, and any container that wants access mounts that same volume into its own filesystem at whatever path it chooses. This is how containers in the same Pod share files - they’re literally mounting the same underlying volume, just possibly at different paths.

Lifecycle: a Pod moves through phases - Pending (accepted, not yet fully running, maybe still pulling images), Running (at least one container is up), Succeeded (every container exited 0 and none will restart), Failed (a container exited non-zero and won’t restart), and Unknown (the control plane has lost contact with the node). Pods are also fundamentally not self-healing on their own - a bare Pod that dies stays dead. It’s a higher-level controller (a Deployment, a StatefulSet) watching over it that actually creates a replacement, which is exactly why you almost never create a Pod directly in practice.

graph TD subgraph "Pod (shared network namespace + volumes)" C1["Container: app"] C2["Container: sidecar"] VOL[("Shared Volume")] end C1 <-->|localhost| C2 C1 --> VOL C2 --> VOL
Q2
What is a container in Kubernetes? Explain how containers run inside Pods and how Kubernetes manages them.
Basic

Ans: A container is an isolated, running process built from a container image, and in Kubernetes it never exists on its own - it’s always defined as part of a Pod’s spec and always runs inside that Pod’s shared context.

How they run inside Pods: when a Pod is scheduled to a node, kubelet calls the container runtime (over CRI) to first create the Pod’s sandbox - a shared network namespace every container in the Pod will use - and then pulls each container’s image and starts it inside that sandbox. Init containers run one at a time, in the order they’re listed, each required to succeed before the next (or the first regular container) starts; regular containers then all run concurrently.

How Kubernetes manages them: kubelet is responsible for the entire lifecycle of every container in a Pod assigned to its node - starting them, running configured liveness/readiness/startup probes against them for as long as the Pod exists, restarting any that fail (per the Pod’s restartPolicy), and reporting status back to the API server continuously. None of this happens through direct manipulation of the container itself - kubelet only ever issues CRI calls, and the actual low-level work (namespaces, cgroups, process management) happens entirely on the runtime’s side of that interface.

Resource boundaries: each container gets its own resource requests and limits, independent of other containers in the same Pod - so a sidecar with modest limits and a main app container with much larger ones can coexist in the same Pod without one starving the other, even though they share networking and can share storage.

Q3
What is a multi-container Pod? Explain why multiple containers are placed in the same Pod and how they share networking and storage.
Basic

Ans: A multi-container Pod is exactly what it sounds like - a Pod running more than one container - but the interesting part is why you’d do that instead of just running two separate Pods.

Why containers get placed together: the containers in a multi-container Pod are meant to be genuinely coupled - they need to be scheduled to the same node, start and stop together, and communicate with near-zero latency and no network hop. Common patterns include a sidecar extending the main app (a log shipper, a service mesh proxy), an ambassador proxying outbound connections on the main container’s behalf, or an adapter normalizing the main container’s output into a format something else expects. If two containers don’t need this tight coupling, they should almost always be separate Pods (and probably separate Deployments) instead - cramming unrelated things into one Pod just to save on Pod count is a common anti-pattern, since it also means they can’t be scaled or updated independently.

How they share networking: every container in a Pod is placed into the same network namespace when the Pod’s sandbox is created, so they all share one IP address and can talk to each other over localhost directly, with zero DNS lookups or Service routing involved.

How they share storage: volumes are defined once at the Pod level, and each container that needs access declares its own volumeMounts entry pointing at that same volume - possibly at a different path than another container uses. This is what lets one container write files that another one reads, without either of them needing any awareness of where the data is physically coming from.

graph TD subgraph "Pod (one shared network namespace + volumes)" MAIN["Main container: app"] SC["Sidecar\n(log shipper, proxy)"] AMB["Ambassador\n(proxies outbound calls)"] ADP["Adapter\n(normalizes output)"] VOL[("Shared Volume")] end MAIN <-->|localhost| SC MAIN <-->|localhost| AMB MAIN <-->|localhost| ADP MAIN --> VOL SC --> VOL

The tradeoff worth remembering: because containers in the same Pod scale, restart, and get scheduled together as one unit, they lose independence - you can’t scale just the sidecar without scaling the main app right along with it.

Q4
What are sidecar, init, and ephemeral containers? Explain their purpose, differences, and typical use cases.
Basic

Ans: These are three distinct ways of adding a container to a Pod beyond the main application container, each solving a different problem.

TypeWhen it runsPurposeTypical use case
SidecarAlongside the main container, for the Pod’s whole lifetimeExtend or support the main app without touching its codeLog shipper, service mesh proxy, config sync
Init containerBefore any main containers, sequentially, to completionOne-time setup workWait for a dependency, run a migration, fetch config
Ephemeral containerInjected into an already-running Pod, temporarilyDebugging onlyGet a shell/toolset into a minimal or distroless Pod

The main container is the one actually running your application - it’s the default, ever-present piece every Pod is built around, and everything else (init, sidecar, ephemeral) exists purely to set up for it, support it, or debug it.

flowchart TD TRAFFIC["Traffic / Requests"] --> MAIN["Main container\n(your application logic)"] MAIN --> VOL[("Shared Volume")] MAIN -->|reports status| KUBELET["kubelet"]

Init containers run once, sequentially, and have to succeed before the next one (or the main container) starts. They’re perfect for setup work you’d rather not bake into your main image - waiting for a database to accept connections, running a schema migration, or pulling configuration from an external source before the app boots.

flowchart LR I1["Init container 1\ne.g. wait-for-db"] --> I2["Init container 2\ne.g. run-migration"] --> MAIN["Main container starts"]

Sidecar containers run for as long as the Pod does, doing work that supports the main container without being merged into its image - a log shipper reading files the main container writes, or a proxy handling mTLS and traffic routing (this is exactly how Istio injects Envoy). Since Kubernetes 1.28, you can explicitly mark a container as a sidecar by putting it in initContainers with restartPolicy: Always, which tells Kubernetes to start it before the main container and keep it running for the Pod’s full lifetime, rather than treating it as a one-shot init step.

flowchart LR MAIN["Main container"] <-->|localhost| SIDE["Sidecar container\n(runs for Pod's full lifetime)"]

Ephemeral containers are the odd one out - they’re not part of a Pod’s original spec at all, but something you inject into an already-running Pod purely for debugging (kubectl debug), and they can’t have resource guarantees, ports, or probes, and won’t restart if they exit. They exist specifically for troubleshooting minimal or distroless production images that don’t even ship a shell, without needing to modify or restart the Pod to get a debugging toolset temporarily attached to it.

Q5
What is a Deployment? Explain its purpose, relationship with ReplicaSets and Pods, and when you should use it.
Basic

Ans: A Deployment is the standard way to run a stateless application in Kubernetes, and it’s actually a two-layer abstraction - it doesn’t manage Pods directly at all.

Purpose: you describe the Pod template you want and how many replicas, and the Deployment takes care of keeping that many Pods running, handling rolling updates when you change the template, and giving you rollback if a rollout goes wrong.

Relationship with ReplicaSets and Pods: a Deployment creates and owns a ReplicaSet, and it’s that ReplicaSet, not the Deployment itself, that actually creates and owns the Pods. When you update a Deployment’s Pod template (a new image version, say), it doesn’t modify the existing ReplicaSet - it creates a brand-new ReplicaSet with the updated template, and gradually shifts replica count from the old ReplicaSet down to zero while scaling the new one up to the desired count. This is the entire mechanism behind rolling updates, and it’s also why old ReplicaSets stick around (scaled to zero) after a rollout - they’re what kubectl rollout undo actually reverts back to.

graph TD D["Deployment"] -->|creates/owns| RS1["ReplicaSet\n(old revision, scaled to 0)"] D -->|creates/owns| RS2["ReplicaSet\n(current revision, active)"] RS2 -->|creates/owns| P1["Pod"] RS2 -->|creates/owns| P2["Pod"] RS2 -->|creates/owns| P3["Pod"]

When to use it: Deployments are the right default for any stateless workload - web servers, API services, background workers - anything where individual Pod replicas are interchangeable and don’t need a stable identity or dedicated storage of their own. If your workload genuinely needs stable network identity or per-replica persistent storage, a StatefulSet is the better fit; if it needs to run on every node, that’s a DaemonSet’s job instead.

Q6
What is a ReplicaSet? Explain how it maintains the desired number of Pod replicas and how it relates to Deployments.
Basic

Ans: A ReplicaSet has one job: make sure a specified number of Pods matching a given label selector are running at all times, and it does that through the same reconciliation pattern every Kubernetes controller uses.

How it maintains the count: the ReplicaSet controller continuously watches for Pods matching its selector, compares how many currently exist against the desired replicas count, and creates or deletes Pods to close any gap. If a Pod crashes or is deleted, the controller notices the count dropped below desired and immediately creates a replacement - this is the actual mechanism behind what people casually call Kubernetes’ “self-healing” at the Pod level.

How it relates to Deployments: you’ll rarely, if ever, create a ReplicaSet directly. A Deployment creates and manages ReplicaSets on your behalf, which is what layers rolling updates and rollback on top of the ReplicaSet’s basic “keep N replicas running” guarantee. When a Deployment’s Pod template changes, it doesn’t edit the existing ReplicaSet in place - it creates a new one and orchestrates shifting replica counts between old and new, which is exactly what makes a rolling update possible without any downtime.

One subtlety worth knowing: because a ReplicaSet finds “its” Pods purely by label selector, not by any list of names it created, a Pod with matching labels that was created by something else entirely can accidentally get claimed by an unrelated ReplicaSet - which is a real, if uncommon, source of confusing bugs when label selectors overlap between unrelated objects.

Q7
What is a StatefulSet? Explain stable identity, ordered deployment, networking, and persistent storage for stateful applications.
Basic

Ans: A StatefulSet exists for workloads that a Deployment’s interchangeable-Pod model genuinely doesn’t fit - databases, message brokers, anything where each replica has its own identity and its own data that can’t just be thrown away and recreated from scratch.

Stable identity: each Pod gets a predictable, stable name built from the StatefulSet’s name plus an ordinal index - postgres-0, postgres-1, postgres-2 - and critically, that identity is preserved across restarts and rescheduling. If postgres-1 is deleted or its node fails, its replacement comes back as postgres-1 again, not some new randomly-suffixed name, the way a Deployment’s Pods would.

Ordered deployment and scaling: Pods are created, scaled, and deleted strictly in order - postgres-0 has to become Ready before postgres-1 starts, and scaling down removes postgres-2 before postgres-1. This matters enormously for things like initializing a database cluster’s first node before replicas try to join it.

Networking: a StatefulSet is paired with a headless Service (clusterIP: None), which gives each Pod its own stable, individually-addressable DNS name (postgres-0.postgres.default.svc.cluster.local), instead of one shared virtual IP load-balancing across all replicas indiscriminately. This is what lets a client (or another replica) target one specific member of the set directly.

Persistent storage: each Pod gets its own PersistentVolumeClaim, generated from a volumeClaimTemplates definition, and that PVC follows the same ordinal identity - postgres-1’s volume stays postgres-1’s volume even if the Pod is deleted and recreated, which is exactly what lets a stateful workload’s data survive a restart.

graph TD SS["StatefulSet: postgres"] --> P0["postgres-0\n+ its own PVC"] SS --> P1["postgres-1\n+ its own PVC"] SS --> P2["postgres-2\n+ its own PVC"] P0 -.ordered startup.-> P1 -.ordered startup.-> P2
Q8
What is a DaemonSet? Explain how it ensures Pods run on eligible nodes and give common use cases.
Basic

Ans: A DaemonSet’s job is fundamentally different from a Deployment’s - instead of running a fixed number of Pods, it makes sure exactly one Pod runs on every node that matches its criteria, with the count automatically tracking however many eligible nodes the cluster currently has.

How it ensures Pods run on eligible nodes: the DaemonSet controller watches both the set of nodes and its own Pods, and reconciles the two - when a new node joins the cluster (and matches whatever nodeSelector or affinity rules the DaemonSet specifies), a Pod is automatically created on it; when a node is removed, its DaemonSet Pod goes with it. Unlike regular Pods, DaemonSet Pods automatically tolerate the node-role.kubernetes.io/control-plane:NoSchedule taint by default, which is exactly why things like kube-proxy and CNI plugins - which genuinely need to run everywhere, including on control-plane nodes - can do so without you manually adding tolerations.

Common use cases:

  • Log collectors - Fluentd, Filebeat, Fluent Bit, shipping every node’s logs somewhere central.
  • Monitoring agents - Prometheus Node Exporter, Datadog agent, collecting node-level metrics.
  • Network plugins - CNI implementations like Calico or Cilium, which need a presence on every node to program networking.
  • Storage daemons - things like Ceph or GlusterFS components that need direct access to every node’s local disks.

The common thread across all of these is that they’re infrastructure that fundamentally needs a presence on every node, not a workload you’d want to scale independently of your node count - which is exactly the distinction that separates “use a DaemonSet” from “use a Deployment.”

Q9
What are Jobs and CronJobs? Explain the difference between one-time and scheduled workloads.
Basic

Ans: Both are built for work that has a defined end, in contrast to a Deployment’s Pods, which are expected to run forever - but they solve different framing of that same problem.

Job: creates one or more Pods and tracks them until the required number complete successfully. If a Pod fails, the Job creates a replacement and retries, up to a configurable backoffLimit; once enough Pods have completed, the Job itself is marked Complete and stops creating new ones. You control parallelism with parallelism (how many Pods run at once) and completions (how many total successful completions are needed) - a data migration script or a one-off report generation task are classic examples.

CronJob: doesn’t run Pods directly at all - it creates a new Job every time its schedule fires (standard cron syntax, like 0 2 * * * for 2 AM daily), and that Job then creates the actual Pods, exactly as it would if you’d created the Job by hand. concurrencyPolicy decides what happens if a previous run is still going when the next scheduled time arrives: Allow (run both), Forbid (skip the new one), or Replace (kill the old one and start the new one).

flowchart LR CRON["CronJob\n(schedule fires)"] -->|creates| JOB["Job"] JOB -->|creates| POD["Pod(s)\nrun to completion"]

The core difference: a Job is a one-time, run-to-completion unit of work you either create directly or that gets created for you; a CronJob is purely a scheduler that repeatedly creates fresh Jobs on a timer. If you need “run this once,” use a Job. If you need “run this every night,” use a CronJob, which is really just automating the repeated creation of Jobs you could otherwise be creating by hand on a schedule.

Q10
What are Kubernetes workload controllers? Explain how Deployments, StatefulSets, DaemonSets, Jobs, and CronJobs manage workloads.
Basic

Ans: “Workload controller” is the umbrella term for any controller whose job is managing a set of Pods on your behalf, running its own reconciliation loop to keep reality matching whatever you declared. Which one you pick depends entirely on the shape of the workload you’re running:

ControllerManagesBest fit for
DeploymentA fixed number of interchangeable Pod replicas, via a ReplicaSetStateless apps - web servers, APIs
StatefulSetPods with stable identity, ordered startup, and per-replica storageDatabases, message brokers, anything stateful
DaemonSetExactly one Pod per eligible nodeNode-level infrastructure - log agents, CNI, monitoring
JobPods that need to run to completionOne-time batch work, migrations
CronJobJobs created on a recurring scheduleNightly backups, periodic cleanup

How they actually manage workloads: every one of these follows the same underlying pattern - watch the API server for changes to objects it owns, compare current state against desired state, and create/update/delete Pods (or, in a Deployment’s case, ReplicaSets which in turn manage Pods) to close the gap. What differs between them is entirely the policy layered on top of that basic loop: a Deployment’s policy includes rolling updates and rollback; a StatefulSet’s policy enforces ordering and stable identity; a DaemonSet’s policy ties Pod count to node count instead of a fixed number; a Job’s policy tracks completions instead of maintaining a steady-state count at all.

Picking the wrong controller for a workload’s actual shape is a common source of pain - trying to run a database on a Deployment (losing stable identity and dedicated storage per replica) or trying to run a batch script as a Deployment (fighting its “keep this running forever” assumption) are both classic mismatches that a quick look at this table would have avoided.

Q11
What are Pod restart policies? Explain Always, OnFailure, and Never, and how they affect container restarts.
Basic

Ans: restartPolicy is set once, at the Pod level (not per-container), and it tells kubelet what to do whenever any container in the Pod exits, regardless of exit code, unless the policy itself distinguishes between success and failure.

PolicyRestarts on success (exit 0)?Restarts on failure (non-zero exit)?Typical user
Always (default)YesYesDeployments, StatefulSets, DaemonSets
OnFailureNoYesJobs
NeverNoNoJobs where you want failures recorded, not retried

Always is the default and what every long-running workload controller (Deployment, StatefulSet, DaemonSet) relies on - a container that exits for any reason, even a clean exit 0, gets restarted, because these workloads are expected to run indefinitely and any exit is treated as unexpected.

OnFailure only restarts a container if it exited with a failure status, which makes sense for Jobs - a container finishing successfully (exit 0) means the work is done and shouldn’t be repeated, but a crash should be retried.

Never means kubelet won’t restart the container at all, regardless of how it exited - useful for Jobs where you specifically want a failure recorded as-is (maybe because the Job’s own backoffLimit is handling retries at a higher level, by creating a brand-new Pod rather than kubelet restarting the same container in place).

It’s worth keeping straight that this is a separate mechanism from a Job’s backoffLimit - restartPolicy controls what kubelet does with containers inside an existing Pod, while backoffLimit controls how many times the Job itself creates an entirely new Pod after a failure. Restarts also follow exponential backoff (capping at 5 minutes between attempts) specifically to avoid hammering a container that’s stuck in a genuine crash loop.

Q12
What are rolling updates and rollbacks in Kubernetes? Explain why they are used during application deployments.
Basic

Ans: Both are Deployment features (StatefulSets support a similar rolling update too), and together they’re what makes shipping a new version of your app a routine, low-risk operation instead of a maintenance-window event.

Rolling updates: instead of stopping every old Pod and starting every new one at once (which would mean real downtime), a rolling update replaces Pods gradually - a few new ones come up and pass their readiness checks, an equivalent number of old ones are removed, and this repeats until every Pod is on the new version. Two settings control exactly how aggressive this is: maxSurge (how many extra Pods above the desired count are allowed temporarily) and maxUnavailable (how many Pods can be missing at any point during the rollout). Setting maxUnavailable: 0 guarantees you never drop below full capacity during a deploy, at the cost of briefly running more Pods than usual.

flowchart LR V1["v1 v1 v1"] --> S1["v1 v1 v2"] S1 --> S2["v1 v2 v2"] S2 --> V2["v2 v2 v2"]

Rollbacks: because Kubernetes keeps a revision history (each one is really just a previous ReplicaSet, scaled to zero but not deleted), reverting to an earlier version is just triggering another rolling update, this time back to the old Pod template - kubectl rollout undo deployment/my-app, or --to-revision=N to jump back further than one step.

Why they matter: without a rolling update, every deploy would mean real, user-visible downtime while old Pods stop and new ones start. Without rollback, discovering a bad deploy in production would mean manually reconstructing the previous working configuration from scratch, under pressure, instead of running one command that Kubernetes already knows how to execute safely - readiness probes gate whether new Pods actually start receiving traffic either way, so a broken new version that fails its readiness checks never gets traffic in the first place, rollout or rollback alike.

Q13
What is a container image? Explain image repositories, tags, digests, and how Kubernetes uses images to start containers.
Basic

Ans: A container image is the packaged, read-only bundle of your application, its dependencies, and everything needed to run it - the template a container gets instantiated from, never modified once built.

Repositories: an image lives in a registry (Docker Hub, Amazon ECR, GCR) under a repository name, which is basically the image’s identity - myorg/my-app, for instance. A registry can host many repositories, and each repository can hold many versions of that same image.

Tags: a tag is a human-friendly, mutable label pointing at a specific image version within a repository - myorg/my-app:v2.0, or the notoriously risky :latest. Tags are mutable by design, meaning myorg/my-app:v2.0 today could theoretically point at a different actual image tomorrow if someone pushes a new build under that same tag - which is exactly the ambiguity that makes tags alone unreliable for anything you need to be certain hasn’t changed.

Digests: a digest is a cryptographic hash (SHA-256) of the image’s actual content - myorg/my-app@sha256:abc123... - and unlike a tag, it’s immutable by definition: the same digest always refers to the exact same bytes, forever. Pinning a deployment to a digest instead of a tag is the way to guarantee you’re running precisely the image you think you are, with zero ambiguity, which matters a lot for reproducibility and for anything security-sensitive.

spec:
  containers:
  - name: app
    image: myorg/my-app@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

How Kubernetes uses images: when a Pod is scheduled to a node, kubelet checks whether the referenced image (by tag or digest) is already cached locally, and if not, calls the container runtime’s ImageService to pull it from the registry, authenticating with any configured imagePullSecrets if it’s a private one. Once pulled, the runtime creates a container from that image’s filesystem layers, and the image itself is never altered - every container started from it gets its own writable layer on top, which disappears the moment that specific container is removed.

Q14
What are imagePullPolicy and imagePullSecrets? Explain when Kubernetes pulls an image and how private registries are accessed.
Basic

Ans: These two settings control two separate questions - when an image gets pulled, and how Kubernetes authenticates to pull it at all if the registry isn’t public.

imagePullPolicy decides when a pull actually happens:

  • Always - pull every time the Pod starts, even if a matching image tag is already cached locally. This is the default whenever your image reference uses the :latest tag (or no tag at all).
  • IfNotPresent - only pull if the image isn’t already present on the node. This is the default for any other, non-:latest tag.
  • Never - never pull under any circumstance; the image has to already exist locally, or the Pod fails to start with an image-not-found error.

The classic trap here is combining :latest with IfNotPresent explicitly set - a node can end up with a stale cached copy of whatever :latest pointed to when it was last pulled, and never know a newer image exists, since IfNotPresent will happily keep using what’s already cached. This is exactly why pinning a specific, immutable tag (or better, a digest) is the recommended practice for anything beyond local experimentation.

imagePullSecrets handles authentication to private registries: by default, kubelet has no credentials to pull from anything requiring authentication, so pulling from a private ECR repository, a private Docker Hub repo, or an internal registry fails with ImagePullBackOff unless credentials are provided. You create a Secret holding registry credentials and reference it in the Pod spec:

spec:
  imagePullSecrets:
  - name: my-registry-creds
  containers:
  - name: app
    image: private-registry.io/my-org/my-app:v1

You can also attach imagePullSecrets to a ServiceAccount instead of every individual Pod spec, so every Pod running under that ServiceAccount automatically inherits the credentials without needing to reference them explicitly each time - a common pattern for keeping manifests clean when many Pods across a namespace all need to pull from the same private registry.

Resource Management

Q15
What are Kubernetes resource requests and limits? Explain how they affect scheduling, resource allocation, and container execution.
Basic

Ans: Requests and limits are how you tell Kubernetes how much CPU and memory a container needs, and they do two genuinely different jobs even though they’re set right next to each other in a Pod spec.

Resource requests are the amount a container is guaranteed to get, and they’re what kube-scheduler actually uses to decide placement - a node has to have at least this much unallocated CPU and memory free before the Pod is even eligible to land there. Requests are a floor, not a cap: a container can use more than it requested if the node happens to have spare capacity sitting around, but the requested amount is what’s reserved for it regardless of what else is running.

Resource limits are the hard ceiling on what a container is allowed to use. What happens when you hit that ceiling depends entirely on which resource it is - CPU and memory behave very differently, which is worth knowing before you set either one.

resources:
  requests:
    cpu: "250m"      # Scheduler needs a node with 250m CPU free
    memory: "512Mi"  # Scheduler needs a node with 512Mi RAM free
  limits:
    cpu: "1000m"      # Throttled if exceeded, never killed
    memory: "1Gi"     # OOMKilled if exceeded

How this affects execution once a Pod is running: the scheduler’s decision only happens once, at placement time - after that, it’s the node’s kernel enforcing the actual limits continuously, throttling or killing containers as needed without any further involvement from the scheduler at all. Getting requests right also affects more than just “will this Pod schedule” - accurate requests are the foundation for a Pod’s QoS class, which in turn decides eviction priority the moment a node comes under resource pressure.

Q16
How do CPU and memory requests and limits work in Kubernetes? Explain the behavior of CPU limits versus memory limits.
Basic

Ans: This is the detail that trips people up most often: CPU and memory limits are enforced in completely different ways, because the two resources have fundamentally different physical characteristics.

CPU is a compressible resource. If a container tries to use more CPU than its limit allows, the kernel’s CFS (Completely Fair Scheduler) simply throttles it - the container isn’t killed, it just gets slower, since it’s forced to share less CPU time than it’s actually asking for. This can be a quiet, hard-to-spot problem in practice: a container that’s being throttled doesn’t crash or restart, it just gets sluggish, which can look like a completely unrelated performance issue unless you’re specifically checking for CPU throttling metrics.

Memory is an incompressible resource. There’s no equivalent “slow down” option for RAM - a process either fits in the memory it’s been given or it doesn’t. If a container tries to allocate more memory than its limit, the kernel’s OOM killer terminates it immediately, and kubelet reports the container as OOMKilled. Depending on the Pod’s restartPolicy, it then gets restarted, likely to run straight into the exact same limit again if the underlying memory need hasn’t actually changed.

CPU limit exceededMemory limit exceeded
What happensThrottled (slowed down)Killed (OOMKilled)
Container survives?YesNo
How it looksSlow response times, hard to noticeSudden restart, easy to notice

Why this asymmetry matters practically: it’s usually safer to set CPU limits a bit generously (since exceeding them just costs performance, not availability), while memory limits deserve more careful, deliberate sizing, since getting them wrong means outright container termination rather than a graceful slowdown.

Q17
What are Kubernetes QoS classes? Explain Guaranteed, Burstable, and BestEffort Pods and when each class is assigned.
Basic

Ans: Every Pod automatically gets assigned one of three Quality of Service classes, purely based on how its containers’ requests and limits are configured - you never set this directly, Kubernetes derives it - and that class is what decides eviction order the moment a node runs short on resources.

Guaranteed: assigned when every container in the Pod has both CPU and memory limits set, and each request exactly equals its limit. This is the safest class - Guaranteed Pods are the last to be evicted under node pressure, making it the right choice for anything genuinely critical, like a production database or a payment service.

resources:
  requests: { cpu: "500m", memory: "512Mi" }
  limits:   { cpu: "500m", memory: "512Mi" }   # exactly equal

Burstable: assigned when at least one container has a resource request set, but requests and limits aren’t equal across the board (limits are set higher than requests, or only some resources have limits at all). This is the middle tier and the most common one for typical application workloads - guaranteed its request, able to burst up to its limit when spare capacity exists, but evicted before Guaranteed Pods when the node comes under pressure.

BestEffort: assigned when a Pod has no resource requests or limits set at all, on any container. It gets whatever’s left over after everything else is satisfied, and it’s the first to be evicted under any resource pressure - fine for genuinely disposable, low-priority work, but a real risk for anything you actually care about staying up.

flowchart TD Q{"Requests and limits\nset on every container?"} Q -->|"requests == limits"| G["Guaranteed\n(evicted last)"] Q -->|"requests set, but\nnot equal to limits"| B["Burstable\n(evicted second)"] Q -->|"nothing set at all"| BE["BestEffort\n(evicted first)"]
Q18
How does Kubernetes handle resource pressure? Explain node pressure, Pod eviction, and the role of resource requests and QoS.
Basic

Ans: When a node starts running dangerously low on a resource - memory, disk space, or inodes - kubelet actively intervenes rather than just letting the situation spiral into the Linux OOM killer picking victims essentially at random.

Detecting pressure: kubelet continuously monitors the node and sets node conditions like MemoryPressure or DiskPressure once usage crosses configured eviction thresholds. These conditions are visible on the Node object and are exactly what you’d check first if you saw a node marked with unusual status.

Evicting Pods: once a node is under pressure, kubelet starts evicting Pods to relieve it, and it doesn’t pick randomly - it evicts in a deliberate order based on QoS class:

  1. BestEffort Pods first - no requests or limits means no guarantees were made, so these go first.
  2. Burstable Pods second - specifically ones using more than their requested amount are prioritized for eviction over those staying within their request.
  3. Guaranteed Pods last - only evicted as an absolute last resort, since their usage should never exceed their (equal) request/limit in the first place under normal circumstances.
flowchart LR PRESSURE["Node under\nresource pressure"] --> BE["Evict BestEffort Pods"] BE -->|still under pressure| BURST["Evict Burstable Pods\n(exceeding requests first)"] BURST -->|still under pressure| GUAR["Evict Guaranteed Pods\n(last resort)"]

Why requests and QoS matter here: a Pod’s resource request isn’t just a scheduling hint, it’s effectively a promise about how much it’s allowed to consume before it becomes an eviction candidate ahead of others - a Burstable Pod happily using only its requested amount is treated better than one aggressively bursting past it, even though both are technically the same QoS class. This is exactly why setting accurate, honest resource requests matters beyond just “will my Pod get scheduled” - it directly determines how safe that Pod is the next time its node comes under real pressure.

Kubernetes Storage

Q19
What is Kubernetes storage? Explain how Kubernetes provides temporary and persistent storage to Pods.
Basic

Ans: Kubernetes storage is the set of mechanisms for giving a Pod access to data that outlives the container’s own filesystem, which by itself disappears completely the moment the container restarts.

Temporary (ephemeral) storage: the simplest option is an emptyDir volume - a directory that starts empty and lives exactly as long as the Pod does. It survives individual container restarts within that Pod (which is often the whole point - sharing scratch space or a cache between containers), but it’s gone the instant the Pod itself is deleted or rescheduled elsewhere.

Persistent storage: for data that needs to survive well beyond any single Pod’s lifetime - a database’s actual data files, for instance - Kubernetes provides the PersistentVolume/PersistentVolumeClaim system. A PersistentVolume represents real, durable storage (a cloud disk, an NFS share); a PersistentVolumeClaim is how a Pod requests some of that storage without needing to know the underlying implementation details; and a StorageClass can provision a matching PV automatically the moment a PVC asks for one.

flowchart TD POD["Pod needs storage"] --> TYPE{"Needs to survive\nbeyond this Pod?"} TYPE -->|"no, just scratch space"| EMPTY["emptyDir\n(ephemeral, Pod-lifetime)"] TYPE -->|"yes, real durable data"| PVC["PersistentVolumeClaim"] PVC --> PV["Bound to a PersistentVolume\n(cloud disk, NFS, etc.)"]

The right choice really comes down to one question: does this data need to exist after the Pod that wrote it is gone? If no, ephemeral storage is simpler and sufficient. If yes, you need the PV/PVC system, backed by real, durable storage outside the Pod’s own lifecycle entirely.

Q20
What is a Kubernetes volume? Explain how volumes differ from container filesystems and how they are mounted into containers.
Basic

Ans: A container’s own filesystem is ephemeral by nature - it’s a writable layer on top of the image it was started from, and it disappears completely the moment that specific container is removed, even if a replacement is started right after. A volume is Kubernetes’ answer to that limitation: a directory, defined at the Pod level, that exists independently of any one container’s lifecycle.

How volumes differ from the container filesystem: the container filesystem is tied to one specific container instance and vanishes with it; a volume is tied to the Pod (or to external durable storage, depending on the volume type) and can outlive individual containers being restarted, and in the case of persistent volumes, can even outlive the Pod itself.

How they’re mounted: a volume is declared once at the Pod level under volumes, and then each container that wants access to it adds its own volumeMounts entry specifying where in that container’s filesystem the volume should appear.

spec:
  containers:
  - name: app
    volumeMounts:
    - name: data
      mountPath: /var/lib/app     # this container sees it here
  - name: sidecar
    volumeMounts:
    - name: data
      mountPath: /data            # a different path, same volume
  volumes:
  - name: data
    emptyDir: {}

Because the mount path is set independently per container, two containers in the same Pod can mount the exact same underlying volume at completely different paths - which is exactly what lets a main app write logs to one directory while a sidecar reads them from a directory it’s chosen for its own purposes, both pointing at the same actual data underneath.

Q21
What are emptyDir, hostPath, and ephemeral volumes? Explain their lifecycle, use cases, and limitations.
Basic

Ans: These are all non-persistent (or semi-persistent) volume types, each solving a different, narrower problem than the full PV/PVC system.

TypeLifecycleUse caseLimitation
emptyDirTied to the Pod; wiped on Pod deletionScratch space, cache, sharing files between containers in one PodData lost if the Pod is deleted or rescheduled
hostPathTied to the node’s own filesystemAccessing node-level files (logs, Docker socket) from a DaemonSetTies the Pod to a specific node’s disk; real security risk if misused
Generic ephemeral volumeTied to the Pod, but backed by real CSI-provisioned storageScratch space that needs real disk performance without outliving the PodStill gone when the Pod is deleted, despite being “real” storage underneath

emptyDir starts completely empty when the Pod is scheduled and lives exactly as long as that Pod does - it survives individual container restarts within the Pod (which is often the actual reason to use one), but not the Pod being deleted or rescheduled to another node.

hostPath mounts a path directly from the node’s own filesystem into the Pod - this is how DaemonSets like log collectors or monitoring agents get access to node-level files that only exist on the host itself. It’s generally discouraged for regular application workloads specifically because it ties a Pod to whatever’s on that one particular node’s disk (breaking if the Pod moves), and because mounting sensitive host paths into an untrusted container is a genuine security risk.

Generic ephemeral volumes are the newer option that bridges the gap - they let you get dynamically-provisioned, CSI-backed storage (with real disk performance characteristics) without the PV/PVC surviving beyond the Pod’s own lifetime, useful for something like a high-performance scratch disk that genuinely doesn’t need to outlive the Pod using it.

Q22
What is a PersistentVolume (PV)? Explain its purpose and lifecycle.
Basic

Ans: A PersistentVolume represents a piece of real, durable storage in the cluster - an AWS EBS volume, an NFS share, a local SSD - provisioned either by an administrator ahead of time (static provisioning) or automatically via a StorageClass the moment a matching claim shows up (dynamic provisioning).

Its purpose: a PV exists as its own independent cluster resource, decoupled from any specific Pod - which is exactly what lets the underlying data survive a Pod being deleted, crashing, or rescheduled to a different node entirely.

Its lifecycle moves through a defined set of phases:

flowchart LR AVAIL["Available\n(provisioned, not yet claimed)"] --> BOUND["Bound\n(matched to a PVC)"] BOUND --> RELEASED["Released\n(PVC deleted, PV not yet reclaimed)"] RELEASED --> RECLAIM["Deleted or Retained\n(per reclaim policy)"]
  1. Available - the PV exists and is ready to be claimed, but no PVC has bound to it yet.
  2. Bound - a PVC has matched and bound to this PV; it’s now in active use by whatever Pod references that PVC.
  3. Released - the PVC that was using it has been deleted, but the PV itself hasn’t been cleaned up yet. At this point, the PV isn’t automatically available for a new claim, even if it technically has free capacity - it needs to go through reclamation first.
  4. Deleted or Retained - what happens next depends entirely on the PV’s reclaim policy. Delete removes both the PV object and its underlying storage automatically; Retain leaves both intact for an administrator to manually inspect, archive, or reassign.

Understanding this lifecycle matters practically, because a PV stuck in Released (common with a Retain policy) won’t automatically become available again - it genuinely needs manual intervention before it can be bound to a new claim.

Q23
What is a PersistentVolumeClaim (PVC)? Explain how applications request persistent storage using PVCs.
Basic

Ans: A PersistentVolumeClaim is how an application actually asks for storage, without needing to know or care about the underlying storage technology backing it.

How it works: a PVC specifies what an application needs - how much storage, which access mode, optionally which StorageClass - and Kubernetes handles the rest. If a suitable PersistentVolume already exists (static provisioning), the PVC binds to it. If none exists but the PVC references a StorageClass capable of dynamic provisioning, Kubernetes creates a brand-new PV automatically to satisfy the claim.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 100Gi
# A Pod just references the PVC by name - no storage details needed
spec:
  containers:
  - name: postgres
    volumeMounts:
    - name: data
      mountPath: /var/lib/postgresql/data
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: postgres-data

Why this decoupling matters: the application developer writing the Pod spec never needs to know whether the storage underneath is AWS EBS, GCE Persistent Disk, or on-prem NFS - they just ask for “100Gi, mounted read-write by one node,” and the PVC/PV/StorageClass machinery handles matching that request to real, working storage. This is exactly what lets the same application manifest run unmodified across completely different underlying infrastructure.

Q24
What is a StorageClass? Explain how it defines storage provisioning behavior and storage characteristics.
Basic

Ans: A StorageClass defines a “profile” of storage - which provisioner creates it, what parameters to pass that provisioner, and what happens to the volume once its claim is deleted - and its main job is enabling dynamic provisioning, so PVs don’t have to be created by hand ahead of time.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com          # which CSI driver provisions this
parameters:
  type: gp3                            # disk type
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Retain                  # what happens when the PVC is deleted
allowVolumeExpansion: true             # can PVCs using this class be resized later
volumeBindingMode: WaitForFirstConsumer # when the volume actually gets created

Provisioning behavior: the provisioner field points at a specific CSI driver (ebs.csi.aws.com for AWS EBS, for instance), and that driver is what actually creates the underlying storage whenever a PVC references this StorageClass and no matching PV already exists.

Storage characteristics: the parameters block is provisioner-specific and controls the actual physical characteristics of what gets created - disk type, IOPS, throughput, encryption, and anything else the specific backend supports. This is how a cluster can offer multiple StorageClasses side by side (fast-ssd, standard-hdd, high-throughput) so applications can pick the storage tier that actually fits their workload, just by naming the right class in their PVC.

A cluster can also mark one StorageClass as the default (storageclass.kubernetes.io/is-default-class: "true"), which is used automatically for any PVC that doesn’t explicitly specify one.

Q25
What are static and dynamic volume provisioning? Explain the difference and when each is used.
Basic

Ans: These are the two ways a PersistentVolume actually comes into existence, and the difference is entirely about who creates it and when.

Static provisioning: an administrator manually creates PersistentVolume objects ahead of time, pointing at storage that already exists somewhere - a pre-created EBS volume, an existing NFS export. A PVC then simply binds to whichever pre-existing PV matches its size and access mode requirements. This is more upfront manual work, but it’s necessary whenever you need to hand a Pod access to storage that was provisioned entirely outside Kubernetes, or that needs very specific placement decisions Kubernetes has no way to make on its own.

Dynamic provisioning: a PVC references a StorageClass, and if no existing PV already satisfies it, Kubernetes automatically calls the StorageClass’s provisioner to create a brand-new PV on demand, sized and configured exactly as the StorageClass specifies.

flowchart TD STATIC["Static: admin pre-creates PV\nfrom existing storage"] --> BIND1["PVC binds to matching PV"] DYNAMIC["Dynamic: PVC references\na StorageClass"] --> CHECK{"Matching PV\nalready exists?"} CHECK -->|no| CREATE["Provisioner creates\na new PV automatically"] CHECK -->|yes| BIND2["PVC binds directly"] CREATE --> BIND2

When each is used: dynamic provisioning is the default, low-friction path in virtually any cloud-backed cluster today - developers just request storage through a PVC and the actual disk gets created behind the scenes without any administrator involvement. Static provisioning still matters for pre-existing storage you need to bring into Kubernetes as-is, for storage backends that don’t have a CSI driver capable of dynamic provisioning, or for cases where an administrator deliberately wants tight, manual control over exactly which storage gets assigned to which claim.

Q26
What is CSI in Kubernetes? Explain why the Container Storage Interface exists and how it enables storage plugins.
Basic

Ans: CSI (Container Storage Interface) is a standardized API that lets storage vendors build Kubernetes storage plugins without their code needing to be merged into Kubernetes’ own codebase at all.

Why it exists: before CSI, every storage integration was compiled directly “in-tree” - baked straight into Kubernetes itself. That meant any new storage backend’s support was tied to Kubernetes’ own release cycle, and even a small fix to an existing storage driver required waiting for (and shipping) an entire new Kubernetes release. That coupling didn’t scale as the number of storage backends people wanted to use with Kubernetes kept growing.

How it enables plugins: CSI defines a standard gRPC interface covering the operations any storage backend needs to support - provisioning a volume, attaching it to a node, mounting it into a Pod, resizing it, and eventually deleting it. Any vendor - AWS, GCP, NetApp, Portworx, anyone - can implement that interface as a completely independent driver, versioned and released entirely on their own schedule, installed into a cluster like any other workload rather than needing to be compiled into Kubernetes core.

The practical result is that Kubernetes today ships with essentially no storage backends built in at all - literally everything, including AWS EBS and GCE Persistent Disk support, runs as an installable CSI driver, decoupled entirely from the Kubernetes release train itself.

Q27
What is a CSI driver? Explain its role in provisioning, attaching, mounting, and managing storage.
Basic

Ans: A CSI driver is the actual, concrete implementation of the CSI spec for one specific storage backend - the AWS EBS CSI driver, the EFS CSI driver, and so on - and it’s what a StorageClass’s provisioner field points at.

Its role across the storage lifecycle:

  1. Provisioning - when a PVC needs a new volume, the driver’s controller component calls the backend’s API (AWS’s EC2 API, for EBS) to actually create the underlying storage resource.
  2. Attaching - once a Pod needing that volume is scheduled to a specific node, the driver attaches the storage to that node (attaching an EBS volume to the right EC2 instance, for example).
  3. Mounting - the driver’s node-level component (typically running as a DaemonSet) mounts the attached storage into the Pod’s filesystem at the path kubelet expects.
  4. Ongoing management - resizing a volume when a PVC is expanded, taking snapshots if the driver supports it, and eventually detaching and deleting the storage once it’s no longer needed (per the reclaim policy).

Most CSI drivers deploy as two cooperating pieces: a controller component (usually a Deployment) handling cluster-wide operations like provisioning and attaching, and a node component (a DaemonSet, since it needs a presence on every node) handling the local mount/unmount work that has to happen right where the Pod actually runs. Because kubelet talks to every driver through the exact same standardized CSI interface, it never needs backend-specific logic of its own - swapping which CSI driver backs a StorageClass is a config change, not a Kubernetes upgrade.

Q28
What are Kubernetes volume access modes? Explain ReadWriteOnce, ReadOnlyMany, ReadWriteMany, and ReadWriteOncePod.
Basic

Ans: An access mode describes how a volume can be mounted, declared by both PVs and PVCs - a PVC only ever binds to a PV that actually supports the access mode(s) it’s requesting.

Access ModeMeaningCommon backing storage
ReadWriteOnce (RWO)Read-write by a single nodeAWS EBS, GCE PD, Azure Disk
ReadOnlyMany (ROX)Read-only by many nodes simultaneouslyNFS, EFS, shared static datasets
ReadWriteMany (RWX)Read-write by many nodes simultaneouslyNFS, AWS EFS
ReadWriteOncePod (RWOP)Read-write by exactly one Pod, cluster-wideAny CSI driver supporting it

ReadWriteOnce is the most widely supported mode and the source of a common point of confusion: it means one node, not one Pod - multiple Pods on the same node can actually mount an RWO volume simultaneously. It’s the right fit for anything that doesn’t need to be shared across nodes, like a single-instance database.

ReadOnlyMany lets many nodes mount the same volume, but strictly read-only - no writer allowed from anywhere. Good for distributing a shared, static dataset (reference data, a shared ML model file) to many Pods without any risk of accidental modification.

ReadWriteMany allows genuine concurrent read-write access from multiple nodes at once - block storage like EBS fundamentally can’t do this, which is exactly why a network filesystem like NFS or AWS EFS exists for workloads that need it.

ReadWriteOncePod is the strictest mode, closing the loophole RWO leaves open (multiple Pods on one node mounting it) by guaranteeing that literally only one specific Pod, anywhere in the entire cluster, can mount it read-write at a time - useful when you need an absolute guarantee against accidental double-mounting, like a single-writer database that would corrupt its data if two instances ever wrote to it concurrently.

Q29
What are PersistentVolume reclaim policies? Explain Retain, Delete, and the lifecycle implications of each.
Basic

Ans: A reclaim policy decides what happens to a PersistentVolume, and the real storage underneath it, once its bound PVC is deleted.

Delete: both the PV object and the actual underlying storage resource (the EBS volume, for instance) are deleted automatically the moment the PVC goes away. This is the default for dynamically provisioned volumes, and it’s convenient - no manual cleanup needed - but genuinely unforgiving if a PVC gets deleted by mistake, since the data goes with it, immediately and irreversibly.

Retain: the PV and its underlying storage are left completely intact after the PVC is deleted - the PV just transitions to Released status, disconnected from any claim, waiting for an administrator to manually decide what happens next (archive the data, manually reassign it to a new claim, or delete it explicitly once it’s confirmed safe to do so).

flowchart TD PVCDEL["PVC deleted"] --> POLICY{"Reclaim\npolicy"} POLICY -->|Delete| GONE["PV + underlying storage\ndeleted automatically"] POLICY -->|Retain| RELEASED["PV becomes Released\ndata intact, needs manual action"]

Lifecycle implications: for anything holding data you genuinely can’t afford to lose - a production database’s storage volume, say - Retain is the much safer default, since it turns an accidental PVC deletion into an inconvenience (data sitting there, needing manual cleanup or reattachment) rather than an outright data-loss incident. Delete makes the most sense for storage that’s genuinely disposable or easily regenerated, where the convenience of automatic cleanup outweighs the risk of losing it. It’s worth explicitly setting reclaimPolicy: Retain on any StorageClass backing something critical, precisely because Delete is the default you’d otherwise get without thinking about it.

Q30
What is PersistentVolume expansion? Explain how Kubernetes allows supported persistent volumes to be resized.
Basic

Ans: Volume expansion lets you grow a PersistentVolumeClaim’s storage size after it’s already been created and bound, without needing to recreate the Pod, migrate data manually, or lose anything already stored.

How it works:

  1. Confirm the PVC’s StorageClass actually supports it - allowVolumeExpansion: true has to be set; without it, expansion is rejected outright.
  2. Patch the PVC with a larger value under resources.requests.storage.
  3. The CSI driver’s controller component resizes the underlying storage resource (expanding the actual EBS volume, for instance).
  4. Depending on the driver, the filesystem inside the volume may need to be resized too - for many modern CSI drivers this happens automatically online, without touching the Pod at all; for some older drivers, a Pod restart is needed for the filesystem resize step to actually complete.
# Confirm the StorageClass allows expansion
kubectl get storageclass fast-ssd -o jsonpath='{.allowVolumeExpansion}'

# Expand the PVC
kubectl patch pvc postgres-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

# Watch the expansion progress
kubectl get pvc postgres-data -w

Important constraints: expansion is strictly one-directional - you can grow a volume, but you can never shrink one back down through this mechanism, since most storage backends simply don’t support shrinking a live volume safely. It’s also worth knowing that the underlying cloud storage resource may start being billed at the new, larger size immediately, even before the filesystem inside it has actually been resized to make full use of that space.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form