Kubernetes ConfigMap Update Behavior
Understand why ConfigMap changes reach volumes but not environment variables, and how to roll a Deployment deliberately.
Container orchestration: architecture, pods, deployments, services and ingress, config and secrets, probes, autoscaling, storage and troubleshooting.
Official reference: Kubernetes docs
Understand why ConfigMap changes reach volumes but not environment variables, and how to roll a Deployment deliberately.
Share scratch space between containers in a pod with emptyDir, choose the backing medium, and bound its size.
Route traffic with GatewayClass, Gateway and HTTPRoute, and understand how the Gateway API improves on Ingress.
Scale replicas from metrics with the HPA, set sensible targets, and stabilize against flapping.
Run setup and dependency waits in init containers before the app starts, and keep startup logic out of the app itself.
Enforce pod hardening with the Pod Security Admission levels, and migrate workloads off the removed PodSecurityPolicy.
Spread replicas across zones and nodes with topology spread constraints, and balance availability against scheduling failure.
Cap a namespace's total resources with a quota and set per-container defaults with a LimitRange.
Choose ClusterIP, NodePort, LoadBalancer or a headless Service, and know what each exposes.
Fix Kubernetes CrashLoopBackOff: read the exit code, check kubectl logs --previous, fix command, config and probes, and understand backoff timing.
Fix CreateContainerConfigError: read the kubectl describe event, repair missing ConfigMap or Secret names and keys, and use optional refs sparingly.
Fix Kubernetes ImagePullBackOff and ErrImagePull: read the pull error, then fix tag typos, imagePullSecrets, CPU architecture or Docker Hub rate limits.
Fix Kubernetes OOMKilled (exit code 137): read lastState.terminated.reason, tell node eviction from the container limit, and size memory limits from data.
Fix a Kubernetes Pod stuck in Pending: read the '0/3 nodes are available' event, then fix requests, affinity, taints, PVCs or quotas.
Pick the metric your autoscaler follows: CPU versus queue backlog, the HPA replica formula, and capping replicas at what the database can take.
ConfigMap or Secret? Why base64 isn't encryption, what actually protects a Secret, and how env vars and mounted files behave on update.
What happens when a CronJob run outlasts its schedule: Allow, Forbid and Replace, startingDeadlineSeconds, and why the job must be idempotent.
How a Deployment rolls out a new image: ReplicaSets, maxSurge and maxUnavailable arithmetic, readiness gating, progress deadlines and rollback.
Why an Ingress does nothing without a controller, how ingressClassName picks one, how host and path rules match, and how to read 404 versus 503.
How Kubernetes Jobs retry failed Pods: backoffLimit, restartPolicy, deadlines, podFailurePolicy for exit codes, and making each run safe to repeat.
What a namespace scopes (names, RBAC, quotas, policies) and what it leaves open: network traffic, nodes, cluster-scoped objects and DNS.
How NetworkPolicy selects Pods, why ingress and egress are separate, the AND/OR selector trap, and why default-deny egress breaks DNS.
When to use nodeSelector, node affinity, pod anti-affinity or taints and tolerations, and why a toleration alone never pins a Pod to a node.
How PVCs bind to PersistentVolumes, why WaitForFirstConsumer matters, what access modes really mean, and how reclaim policy loses data.
How PodDisruptionBudgets gate evictions during drains and upgrades, minAvailable vs maxUnavailable maths, and budgets that block drains.
What the containers in a Pod share, how phases differ from container states, and why Pods get replaced with new names and IPs.
What each Kubernetes probe does when it fails, why liveness must not check your database, and how to size a startupProbe for a slow boot.
Build a least-privilege Role for a CI deployer, verify it with kubectl auth can-i, and spot grants like pods/exec that quietly escalate.
How CPU and memory requests drive scheduling, what limits enforce (throttling vs OOMKill), how QoS classes form, and how to choose numbers.
Harden a Pod with runAsNonRoot, dropped capabilities, seccomp and a read-only root filesystem, and decode the errors each setting can produce.
How a Service selector becomes EndpointSlices, why a Service with no endpoints refuses connections, and how to debug label and port mismatches.
What a StatefulSet guarantees: ordinal names, per-Pod DNS via a headless Service, a claim per replica, ordered rollouts, and what it doesn't.
The exact order of a Pod shutdown, why endpoint removal races SIGTERM and causes 502s, and how preStop, grace periods and app code fix it.
A repeatable order for debugging Kubernetes: Pod status, events, previous logs, endpoints, then the node, with the command and clue at each step.
Kubernetes is an open-source container orchestration platform, originally designed at Google and now hosted by the CNCF. You describe the desired state of your application declaratively, usually in YAML: which images, how many replicas, how they're exposed, how much CPU and memory they need. Kubernetes then works continuously to make the actual state match.
That solves the problems you hit once you have more than a handful of containers on more than one machine:
The core idea is the reconciliation loop: controllers watch the desired state stored in the API server and keep correcting any drift.
Likely follow-up: What does "declarative" mean compared with imperative commands? · When would Kubernetes be overkill?
A cluster has a control plane that makes decisions and worker nodes that run the workloads.
Control plane:
Each node runs:
Likely follow-up: What happens to running pods if the whole control plane goes down? · Why was dockershim removed?
A Pod is the smallest deployable unit in Kubernetes: one or more containers that are always scheduled together on the same node and share a context. Containers in a pod share a network namespace (one IP address and port space, so they reach each other on localhost) and can share volumes.
Kubernetes schedules pods rather than containers because some processes are tightly coupled and must live and die together, like an app and its log shipper or proxy. The pod gives them a common lifecycle, IP and storage without merging them into one image.
Pods are ephemeral. If a pod dies or its node fails, it isn't resurrected: a controller creates a new pod with a new name and a new IP. That's why you rarely create bare pods. You use a Deployment, StatefulSet, DaemonSet or Job to manage them, and put a Service in front for a stable address.
Likely follow-up: How do two containers in the same pod communicate? · What are the pod phases?
They form a chain of controllers:
replicas.kubectl rollout undo.You rarely create a ReplicaSet yourself because it has no update strategy: editing its template doesn't touch pods that already exist. Old ReplicaSets are kept at zero replicas as revision history, 10 by default (revisionHistoryLimit), and that's what a rollback scales back up.
The selector must match the template's labels, and in apps/v1 a Deployment's selector is immutable after creation.
apiVersion: apps/v1
kind: Deployment
metadata: { name: web }
spec:
replicas: 3
selector:
matchLabels: { app: web } # must match the template labels
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: nginx:1.27Likely follow-up: What happens if you manually create a pod whose labels match a ReplicaSet selector?
ClusterIP, NodePort, LoadBalancer and ExternalName types differ?easyPods come and go with changing IPs, so a Service gives a set of pods, chosen by a label selector, a stable virtual IP and DNS name, and spreads traffic across the ready ones.
nodeIP:nodePort reaches the Service.db.example.com.A headless Service (clusterIP: None) is a variation with no virtual IP: DNS returns the pod IPs directly.
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP # or NodePort / LoadBalancer
selector:
app: web
ports:
- port: 80 # port clients connect to
targetPort: 8080 # port the container listens onLikely follow-up: Why would you put an Ingress in front instead of one LoadBalancer per service? · What is targetPort versus port?
maxSurge and maxUnavailable control, and how do you roll back?midWhen the pod template changes, the Deployment creates a new ReplicaSet and shifts pods over step by step: scale the new one up, wait for new pods to become available, scale the old one down, repeat.
maxSurge: how many pods may exist above the desired count during the rollout.maxUnavailable: how many pods may be unavailable below the desired count.Both take a number or a percentage, both default to 25%, and they can't both be 0. maxSurge: 1, maxUnavailable: 0 keeps full capacity but needs room for an extra pod; a higher maxUnavailable is faster and needs no spare capacity.
Readiness probes are what make this safe: a new pod that never becomes ready stalls the rollout instead of taking traffic. After progressDeadlineSeconds (600 by default) the Deployment reports ProgressDeadlineExceeded, but it does not roll back on its own.
To roll back, check kubectl rollout history deployment/web, then run kubectl rollout undo deployment/web, optionally with --to-revision=N.
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # at most 5 pods during the rollout
maxUnavailable: 0 # never fewer than 4 availablekubectl rollout undo, optionally --to-revisionLikely follow-up: What is the Recreate strategy and when would you use it? · How would you pause a rollout halfway?
They answer different questions, and failures have different consequences:
failureThreshold consecutive failures (3 by default) the kubelet restarts the container.failureThreshold × periodSeconds, the container is restarted.Each probe can be httpGet (any status from 200 to 399 passes), tcpSocket, exec or grpc.
A classic mistake is a liveness check that tests the database. If the database blips, every pod restarts at once. Liveness should only check the process itself.
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 10 # allows up to 300s to start
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5Likely follow-up: Why is initialDelaySeconds often replaced by a startup probe? · Do you need a readiness probe to drain traffic on shutdown?
Both are namespaced key-value objects that keep configuration out of the image.
tmpfs (memory) rather than on disk.A pod can consume either one as:
valueFrom or every key with envFromThe practical difference: env vars are read once at container start, so changes need a restart. Mounted volumes are eventually updated by the kubelet (except with subPath), so an app that re-reads its files can pick up changes. Both objects are limited to 1 MiB and can be marked immutable: true.
# in the container spec
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: db-creds, key: password }
envFrom:
- configMapRef: { name: app-config }
volumeMounts:
- { name: config, mountPath: /etc/app }
# in the pod spec
volumes:
- name: config
configMap: { name: app-config }immutable: true availableLikely follow-up: How would you roll pods automatically when a ConfigMap changes?
Not by default. Secret values are only base64-encoded, and the API server stores them unencrypted in etcd. Anyone with access to etcd, an etcd backup, or get/list/watch on Secrets can read them. And anyone who can create a pod in a namespace can mount any Secret in it, so "create pods" effectively implies reading Secrets.
Encryption at rest isn't on by default because it needs keys that someone has to generate, store and rotate, which is an operator decision. Many managed services configure it for you.
To harden:
EncryptionConfiguration passed to the API server via --encryption-provider-config, ideally the KMS v2 provider so the key lives in an external KMS. Then rewrite existing Secrets so they're stored encrypted.list and watch reveal values too.list/watch expose values tooLikely follow-up: After enabling encryption at rest, why are old Secrets still in plaintext? · How does the KMS provider use envelope encryption?
Use a StatefulSet when each replica has its own identity and data: databases, Kafka, ZooKeeper, Elasticsearch. It adds three guarantees a Deployment doesn't give:
db-0, db-1, db-2, and a replacement keeps the same name.serviceName, each pod gets its own DNS record, like db-0.db-headless.default.svc.cluster.local, so peers can find each other.volumeClaimTemplates create one PVC per pod, and a rescheduled pod reattaches to the same volume.With the default OrderedReady policy, pods are created in order from 0 up, each waiting for the previous one to be Running and Ready, and removed in reverse. Rolling updates go from the highest ordinal down, and partition allows staged updates.
Gotchas: by default, deleting or scaling down a StatefulSet does not delete its PVCs, and you have to create the headless Service yourself.
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: db }
spec:
serviceName: db-headless # headless Service you create yourself
replicas: 3
selector: { matchLabels: { app: db } }
template:
metadata: { labels: { app: db } }
spec: { containers: [{ name: db, image: postgres:17 }] }
volumeClaimTemplates: # one PVC per pod: data-db-0, data-db-1...
- metadata: { name: data }
spec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 10Gi } } }Likely follow-up: How would you do a canary update of a StatefulSet? · What does podManagementPolicy: Parallel change?
They're set per container, for CPU and memory:
CPU is measured in cores (250m is a quarter core) and memory in bytes (Mi, Gi). If you set only a limit, the request defaults to the limit. Together, requests and limits decide the pod's QoS class.
In practice, always set requests, because without them scheduling and autoscaling are guesswork. Set a memory limit to contain leaks. Many teams skip CPU limits for latency-sensitive services to avoid throttling. LimitRange can inject defaults and ResourceQuota can cap totals per namespace.
resources:
requests:
cpu: 250m # a quarter of a CPU core
memory: 256Mi
limits:
cpu: "1"
memory: 512MiLikely follow-up: Should you set CPU limits at all? · What do LimitRange and ResourceQuota do?
CrashLoopBackOff. What does that mean, and how do you troubleshoot it?midCrashLoopBackOff isn't a phase or a root cause. It means a container keeps exiting, and the kubelet is waiting before restarting it again. The delay grows exponentially (10s, 20s, 40s…) up to five minutes, and resets after the container runs cleanly for 10 minutes.
My steps:
kubectl describe pod for the Last State: the reason and exit code, plus events. Exit code 1 is usually an app error; 137 means SIGKILL, often OOMKilled (the reason says so); 127 or 126 point at a bad command or entrypoint.kubectl logs --previous to read the logs of the instance that crashed, since the current one may have just started.sleep, or use kubectl debug to inspect it.kubectl get pod web-7d9c -o wide
kubectl describe pod web-7d9c # Last State, Reason, Exit Code, Events
kubectl logs web-7d9c --previous # output of the crashed instance
kubectl logs web-7d9c -c migrate # a specific container
kubectl events --for pod/web-7d9cdescribe shows exit code and reason; logs --previousLikely follow-up: How do you debug a container that crashes before you can exec into it?
An Ingress is a set of layer-7 HTTP(S) routing rules: send shop.example.com/api to the api Service and everything else to web, and terminate TLS with a given certificate Secret. pathType can be Exact, Prefix or ImplementationSpecific.
The Ingress object alone does nothing. An ingress controller (Traefik, HAProxy, an NGINX-based controller, or a cloud one like the AWS Load Balancer Controller) watches Ingress objects and configures a real proxy or load balancer to match. ingressClassName picks which controller handles it. Usually the controller itself sits behind one LoadBalancer Service, so dozens of services share a single external IP instead of paying for one load balancer each.
Worth knowing: the Ingress API is frozen. It's GA and isn't going away, but it won't get new features, and the Kubernetes project now recommends Gateway API. The popular community ingress-nginx controller was retired in March 2026.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: shop }
spec:
ingressClassName: nginx
tls:
- { hosts: [shop.example.com], secretName: shop-tls }
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend: { service: { name: api, port: { number: 80 } } }ingressClassName selects the controllerLikely follow-up: How is Gateway API different from Ingress? · Where would you terminate TLS?
Namespaces divide one cluster into named scopes. Names must be unique within a namespace, not across the cluster, so dev and prod can each have a web Deployment. They're also the unit that most policies attach to:
A new cluster starts with default, kube-system, kube-public and kube-node-lease. Some resources aren't namespaced at all: Nodes, PersistentVolumes, StorageClasses, ClusterRoles and Namespaces themselves (kubectl api-resources --namespaced=false lists them). Services in other namespaces are reached by DNS as name.namespace.
On their own they're a soft boundary. Pods in different namespaces share nodes and kernels, and can talk to each other over the network unless NetworkPolicies say otherwise. For real multi-tenancy you add RBAC, NetworkPolicies, quotas and Pod Security Standards, or use separate clusters.
service.namespaceLikely follow-up: How would you stop one team from using all the cluster resources?
Both are key-value metadata on objects, but they serve different purposes.
Labels identify and group objects, like app=web, tier=frontend, env=prod. They're short (values up to 63 characters) and they're what the system selects on. A Service finds its pods by label, a ReplicaSet counts pods by label, a NetworkPolicy targets pods by label, and kubectl get pods -l app=web filters by label.
Selectors come in two styles: equality-based (app=web, env!=prod) and set-based (env in (prod,staging), !canary). In manifests that's matchLabels and matchExpressions with In, NotIn, Exists and DoesNotExist.
Annotations hold non-identifying data that you can't select on and that can be much larger: build info, a git SHA, contact details, or tool configuration such as ingress controller settings or Prometheus scrape hints. kubectl apply stores its last-applied configuration in one.
A classic bug is a Service selector that doesn't match the pod labels: the Service exists but has no endpoints.
A DaemonSet runs one copy of a pod on every node, or on every node matching a selector. When a node joins the cluster it gets the pod automatically, and when a node is removed its pod is garbage-collected. You don't set a replica count.
Typical uses are node-level agents:
You limit it to some nodes with nodeSelector or node affinity, and add tolerations if it must run on tainted nodes such as the control plane. The controller also adds some tolerations automatically, which is why DaemonSet pods can run on cordoned nodes, and why kubectl drain needs --ignore-daemonsets.
Updates use RollingUpdate by default, one node at a time unless you raise maxUnavailable, or OnDelete, where pods only update when you delete them.
Likely follow-up: How is a DaemonSet different from a Deployment with pod anti-affinity?
The HPA is a control loop in the controller manager, running every 15 seconds by default. It reads metrics, computes a replica count and updates the target's scale subresource:
desired = ceil(current × currentMetric / targetMetric)
So 4 pods averaging 90% CPU against a 70% target gives ceil(4 × 90/70) = 6. It skips changes within a 10% tolerance, clamps the result to minReplicas/maxReplicas, and uses a 5-minute scale-down stabilization window by default so it doesn't flap.
Key details:
behavior field tunes scale-up and scale-down rates.replicas in a Deployment the HPA manages, or every kubectl apply fights it.apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: web }
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: web }
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 }ceil(current × currentMetric / target)Likely follow-up: How would you scale on queue length instead of CPU? · Can HPA and VPA target the same Deployment?
They separate what storage an app needs from how it's provided:
reclaimPolicy and volumeBindingMode.With dynamic provisioning, a PVC that names a class (or falls back to the default class) makes the provisioner create a PV on demand, so no admin pre-creates volumes. WaitForFirstConsumer delays that until a pod is scheduled, so the disk is created in the pod's zone; the default is Immediate.
Access modes are ReadWriteOnce (one node), ReadOnlyMany, ReadWriteMany and ReadWriteOncePod (one pod). Dynamically provisioned PVs default to the Delete reclaim policy; use Retain for data you can't lose.
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: data }
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources: { requests: { storage: 20Gi } }
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: fast-ssd }
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: RetainWaitForFirstConsumer avoids zone mismatchesLikely follow-up: What does ReadWriteOnce actually guarantee? · How do you resize a PVC?
A ClusterIP is virtual: in the default mode no network interface owns it. Three pieces make it work:
ClusterIP:port, those rules on the client's node DNAT the packet to one backend pod IP, picked at random (or by sessionAffinity: ClientIP). Connection tracking keeps the rest of that connection on the same pod, and the pod network carries it to whichever node the pod is on.So kube-proxy isn't in the data path; the kernel does the forwarding. On Linux the default mode is iptables, nftables is the newer replacement and is expected to become the default, and IPVS mode is deprecated. Some CNIs, such as Cilium, replace kube-proxy with eBPF.
A consequence: balancing is per connection, not per request, so long-lived HTTP/2 or gRPC connections can pile onto a few pods.
Likely follow-up: Why might gRPC traffic be unevenly balanced across pods? · What happens to traffic when a pod fails its readiness probe?
A typical path, with an ingress controller or gateway in front:
shop.example.com to the external load balancer the cloud created for the controller's LoadBalancer Service.externalTrafficPolicy: Local only nodes running a controller pod receive traffic, which also preserves the client IP.containerPort. Only ready pods are listed as endpoints, which is why readiness probes matter for zero-downtime deploys.The response returns along the same connection path.
Likely follow-up: Where can you lose the client source IP along this path? · What changes with a service mesh?
You don't set the QoS class; Kubernetes derives it from requests and limits and records it in status.qosClass:
It matters when a node runs short of memory. The kubelet sets each container's oom_score_adj from its class (-997 for Guaranteed, 1000 for BestEffort, in between for Burstable), so the kernel OOM killer picks BestEffort processes first.
For node-pressure eviction, the kubelet ranks pods by whether their usage exceeds their requests, then by priority, then by how far over requests they are. It doesn't use the QoS class directly, but the result is roughly BestEffort first and Guaranteed last.
So critical workloads should have honest memory requests, ideally Guaranteed. Guaranteed pods with whole-number CPU requests can also get exclusive cores under the static CPU manager policy.
Likely follow-up: Can a pod with only CPU requests be Guaranteed?
OOMKilled, and another is slow even though its average CPU usage looks low. What is going on in each case?midThey're the two ways limits bite, because memory is incompressible and CPU is compressible.
OOMKilled: when a container goes over its memory limit, the kernel OOM killer kills a process in its cgroup. The status shows Reason: OOMKilled and exit code 137, and the container restarts, often into CrashLoopBackOff. A pod can also be killed or evicted under its limit if the whole node runs out of memory. Fix it by sizing the limit from real usage (kubectl top, working-set metrics, VPA recommendations), fixing leaks, and making runtimes container-aware, such as the JVM's -XX:MaxRAMPercentage.
CPU throttling: a CPU limit is enforced as a CFS quota: CPU time per scheduling period, usually 100 ms. A multi-threaded app can burn its whole quota early in a period and then stall until the next one. Average usage looks low while tail latency spikes. The throttling counters in cAdvisor metrics show it. Fix it by raising or removing the CPU limit while keeping an accurate request, or by using fewer threads.
Likely follow-up: How would you confirm throttling with metrics? · Why might a JVM app get OOMKilled with a heap well below the limit?
Pending for ten minutes. How do you find out why?midPending means the pod was accepted but isn't running yet. Usually it hasn't been scheduled; sometimes it's scheduled and still pulling images. kubectl describe pod tells you which: a FailedScheduling event from the scheduler explains why each group of nodes was rejected.
Common causes:
If the pod already has a node assigned, look at container states instead: it's probably pulling a large image or waiting on a volume mount. And if a ResourceQuota is the problem, the pod is never created at all, so check the ReplicaSet's events.
kubectl describe pod api-5f7b # read the Events at the bottom
# e.g. FailedScheduling: 0/6 nodes are available: 3 Insufficient cpu,
# 3 node(s) had untolerated taint {dedicated: gpu}
kubectl describe node worker-1 # Allocated resources, Taints
kubectl get pvc -n shop # an unbound claim blocks schedulingkubectl describe pod and the FailedScheduling eventLikely follow-up: How does the cluster autoscaler react to Pending pods?
ImagePullBackOff mean, and what are the usual causes?easyThe kubelet couldn't pull the container image. You first see ErrImagePull, then ImagePullBackOff while it keeps retrying with an increasing delay, capped at five minutes. The container never starts, so there are no logs; the answer is in kubectl describe pod events, which show the registry's actual error.
Usual causes:
docker-registry Secret and reference it in imagePullSecrets on the pod or its ServiceAccount, or grant the nodes registry access through cloud IAM.To avoid surprises, pin images by tag or digest instead of :latest, and remember that :latest with no explicit imagePullPolicy means Always.
kubectl describe pod events for the registry errorimagePullSecrets:latestRBAC decides whether a subject may perform a verb on a resource. It's purely additive: there are no deny rules, so anything not granted by some binding is refused.
view, edit and admin roles are usually handed out.Kubernetes has no User objects; users and groups come from authentication, such as certificates or OIDC. A binding's roleRef can't be changed; you recreate the binding. Test with kubectl auth can-i list pods -n dev --as=system:serviceaccount:dev:ci-bot, and avoid wildcards, cluster-admin, and broad access to Secrets or pods/exec.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: pod-reader, namespace: dev }
rules:
- apiGroups: [""] # "" is the core API group
resources: [pods, pods/log]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: ci-reads-pods, namespace: dev }
subjects: [{ kind: ServiceAccount, name: ci-bot, namespace: dev }]
roleRef: { kind: Role, name: pod-reader, apiGroup: rbac.authorization.k8s.io }kubectl auth can-i --asLikely follow-up: Why is permission to create pods close to permission to read Secrets? · What do the escalate and bind verbs protect against?
A ServiceAccount is an identity for workloads, as opposed to human users. It's namespaced, every namespace gets one called default, and a pod uses default unless you set serviceAccountName.
By default the kubelet mounts a short-lived, automatically rotated token into the pod at /var/run/secrets/kubernetes.io/serviceaccount/, together with the cluster CA and the namespace name. The token comes from the TokenRequest API and is bound to the pod, so it stops working once the pod is deleted. Since v1.24, Kubernetes no longer auto-creates long-lived token Secrets for each ServiceAccount.
Client libraries use this "in-cluster config": they find the API server through the KUBERNETES_SERVICE_HOST variable and send the token as a bearer token. The API server authenticates it as system:serviceaccount:<namespace>:<name> and RBAC authorizes it.
Good practice: one ServiceAccount per app with minimal RBAC, no permissions on default, and automountServiceAccountToken: false for pods that never call the API. Cloud workload identity features map ServiceAccounts to cloud IAM roles.
default if unspecifiedsystem:serviceaccount:ns:name, then RBACLikely follow-up: How would a pod get AWS or GCP credentials without static keys?
A Job runs pods to completion, unlike a Deployment, which keeps pods running forever. It's done when enough pods succeed:
completions and parallelism control how many successes you need and how many pods run at once. completionMode: Indexed gives each pod an index for sharded work.backoffLimit sets how many failures are retried before the Job is marked failed (default 6). activeDeadlineSeconds caps total runtime.ttlSecondsAfterFinished cleans up finished Jobs automatically.restartPolicy must be OnFailure or Never.A CronJob creates Jobs on a cron schedule. concurrencyPolicy decides what happens if the previous run is still going: Allow (the default), Forbid (skip the new run) or Replace (cancel the old one). You can also set startingDeadlineSeconds for late starts, history limits (3 successful and 1 failed Job kept by default), suspend and timeZone.
A CronJob creates a Job roughly once per scheduled time, but occasionally two or none, so jobs should be idempotent.
apiVersion: batch/v1
kind: CronJob
metadata: { name: nightly-report }
spec:
schedule: "0 2 * * *" # 02:00 every day
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid # skip a run if the last one is still going
jobTemplate:
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers: [{ name: report, image: example/report:1.4 }]completions, parallelism, backoffLimit (default 6)concurrencyPolicy: Allow, Forbid, ReplaceLikely follow-up: How would you run a database migration before a deployment?
A taint on a node repels pods; a toleration on a pod lets it ignore a matching taint. That's the opposite of affinity: affinity attracts, taints repel.
Taint effects:
NoSchedule: pods without a matching toleration won't be scheduled there; running pods stay.PreferNoSchedule: a soft version; the scheduler tries to avoid the node.NoExecute: also evicts running pods that don't tolerate it. tolerationSeconds lets a pod stay for a limited time.Tolerations match with operator: Equal (key, value and effect) or Exists (any value).
The key point for dedicated nodes: a toleration only allows a pod onto a node, it doesn't send it there. So taint the GPU nodes to keep everything else off, and give the GPU workloads both the toleration and a nodeSelector or node affinity for those nodes.
Kubernetes uses taints itself: the node controller adds not-ready and unreachable NoExecute taints, and pods get default 300-second tolerations for them, which is why pods on a dead node are evicted after about five minutes.
# kubectl taint nodes gpu-1 dedicated=gpu:NoSchedule
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
nodeSelector:
pool: gpu # a toleration permits the node; this attracts the podtolerationSeconds delays itLikely follow-up: Why do DaemonSet pods still run on cordoned nodes?
nodeSelector, node affinity and pod anti-affinity. How would you keep replicas of a service off the same node?midAll three constrain where the scheduler places a pod:
In, NotIn, Exists, DoesNotExist, Gt, Lt) and two strengths: required… (hard) and preferred… (weighted, soft). "IgnoredDuringExecution" means pods already running aren't evicted if node labels change later.topologyKey, such as kubernetes.io/hostname for a node or topology.kubernetes.io/zone for a zone. Affinity co-locates, say a cache next to its app; anti-affinity spreads.To keep replicas apart, add pod anti-affinity on the app's own label with topologyKey: kubernetes.io/hostname. Prefer the preferred form: with the required form, replicas beyond the number of nodes stay Pending. Inter-pod affinity is also expensive in large clusters, and topologySpreadConstraints is often the better tool for even spreading.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- { key: topology.kubernetes.io/zone, operator: In, values: [eu-west-1a, eu-west-1b] }
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector: { matchLabels: { app: web } }
topologyKey: kubernetes.io/hostnametopologyKey defines node, zone or region domainsLikely follow-up: How do topologySpreadConstraints differ from anti-affinity?
Helm is the package manager for Kubernetes. It saves you from hand-editing near-identical YAML for every environment.
Chart.yaml with metadata, and a values.yaml with defaults. Charts can depend on other charts and are shared through repositories or OCI registries.-f values-prod.yaml or --set image.tag=1.4, so one chart serves dev, staging and prod.helm rollback returns to an earlier one.Everyday commands are helm upgrade --install (idempotent, good for CI), helm rollback, helm list, and helm template to render manifests locally for review.
Since Helm 3 there's no server-side Tiller: the CLI talks to the API server with your own credentials and stores release state in Secrets in the release's namespace. The main alternative is Kustomize, which uses template-free overlays and is built into kubectl as kubectl apply -k.
Likely follow-up: When would you pick Kustomize over Helm? · How do you manage secrets in Helm values?
Init containers, listed under spec.initContainers, run before the app containers start. They run one at a time, in order, and each must exit successfully before the next one starts. If one fails, the kubelet retries it according to the pod's restartPolicy; with Never, the pod fails. While they run, kubectl get pods shows a status like Init:0/2.
Typical uses:
emptyDir volume.The advantage is separation: setup tools like git or curl stay out of the app image, and the init container can have different permissions. Regular init containers don't support liveness, readiness or startup probes. An init container with restartPolicy: Always is a different thing: a native sidecar.
restartPolicy: Always makes it a sidecarLikely follow-up: Why is running migrations in an init container risky with many replicas?
Only when containers are tightly coupled: they must run on the same node, start and stop together, and scale together. The common patterns:
localhost.Containers in a pod communicate cheaply because they share context:
localhost and can't both bind the same port.emptyDir, for exchanging files.shareProcessNamespace: true), so they can see and signal each other's processes.The classic anti-pattern is putting an app and its database in one pod. They scale differently, a restart takes both down, and the database can't survive the pod. Those belong in separate workloads connected by a Service.
Sidecars used to be just extra entries in containers, which caused three problems:
Native sidecars, stable since v1.33, are init containers with restartPolicy: Always:
Service-mesh proxies and log or metrics agents are the main users.
spec:
initContainers:
- name: log-shipper
image: example/log-shipper:1.0
restartPolicy: Always # this makes it a sidecar
volumeMounts: [{ name: logs, mountPath: /var/log/app }]
containers:
- name: app
image: example/app:2.0
volumeMounts: [{ name: logs, mountPath: /var/log/app }]
volumes:
- { name: logs, emptyDir: {} }restartPolicy: AlwaysLikely follow-up: How did people stop sidecars in Jobs before this feature existed?
Through cluster DNS, usually CoreDNS running in kube-system behind a Service called kube-dns. It watches Services and serves records for them:
my-svc.my-ns.svc.cluster.local resolves to the Service's ClusterIP. cluster.local is the default cluster domain, but it's configurable._http._tcp.my-svc.my-ns.svc.cluster.local.db-0.db.my-ns.svc.cluster.local.The kubelet writes each pod's /etc/resolv.conf to point at the DNS Service and adds search domains, starting with my-ns.svc.cluster.local. That's why a pod can just call http://my-svc in its own namespace, and my-svc.other-ns for another namespace.
One gotcha: the default ndots:5 makes short external names like api.example.com try every search domain first, which multiplies DNS queries. A trailing dot or a tuned dnsConfig avoids that.
There's also an older mechanism: environment variables such as MY_SVC_SERVICE_HOST, but only for Services that existed when the pod started.
service.namespace.svc.cluster.local resolves to the ClusterIPndots:5 can multiply lookups for external namesLikely follow-up: How would you debug a pod that cannot resolve a Service name?
A headless Service sets clusterIP: None. It gets no virtual IP, kube-proxy ignores it, and nothing load-balances it. Instead, a DNS lookup of the Service name returns the IPs of all its ready pods directly.
You need one when clients must see or address individual pods:
serviceName gives each pod a stable name like db-0.db.shop.svc.cluster.local, so clients can reach the primary specifically, and cluster members like Kafka brokers or etcd peers can find each other.publishNotReadyAddresses: true publishes pods before they're ready, which helps members find each other while a cluster is bootstrapping.
The trade-off is that clients take on the work: they must re-resolve DNS as pods come and go, and not cache old IPs for too long.
clusterIP: None: no virtual IP, no kube-proxyBy default the pod network is flat: every pod can reach every other pod, across namespaces. A NetworkPolicy is a layer 3/4 firewall for pods, selected by labels.
podSelector, namespaceSelector (both in one entry means AND) or ipBlock, plus ports.The usual pattern is a default-deny policy per namespace (podSelector: {} with no allow rules), then narrow allow policies like the one shown. If you also deny egress, remember to allow DNS to kube-dns on port 53, or everything breaks.
For layer-7 rules, explicit denies or cluster-wide policies you need CNI-specific resources, such as Cilium's or Calico's own policy types.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: api-allow-frontend, namespace: shop }
spec:
podSelector: { matchLabels: { app: api } }
policyTypes: [Ingress]
ingress:
- from:
- podSelector: { matchLabels: { app: frontend } }
ports:
- { protocol: TCP, port: 8080 }Likely follow-up: What is the difference between namespaceSelector and podSelector in the same from entry versus separate entries?
maxSurge and maxUnavailable. It needs no extra infrastructure, but both versions serve traffic during the rollout, so changes must be backward compatible, and a rollback is just another rollout. (Recreate is the blunt alternative: stop everything, then start the new version, with downtime.)version: blue to version: green, or repointing a route. Rollback is an instant switch back. The cost is double capacity during the switch.backendRefs weights, a service mesh, or ingress controller features.Tools like Argo Rollouts and Flagger automate blue-green and canary, including metric analysis and automatic rollback.
Likely follow-up: How do database schema changes affect these strategies?
etcd stores all cluster state: every object, including Secrets. Running containers keep going without it for a while, but nothing can be created, changed or rescheduled. Losing it without a backup means rebuilding the cluster.
Backup: take a snapshot from a live member with etcdctl snapshot save, using the etcd client TLS certificates, or snapshot the etcd data volume. Check it with etcdutl snapshot status. Run it on a schedule, store copies off the cluster and encrypted (a snapshot holds your Secrets, in plaintext unless encryption at rest is on), and rehearse restores.
Restore: stop all API servers, restore the snapshot into a new data directory on each etcd member with etcdutl snapshot restore, point etcd at that directory (on kubeadm, in the etcd static pod manifest), start etcd, then the API servers. Restarting the scheduler, controller manager and kubelets is recommended so nothing acts on stale data.
On managed services like EKS, GKE or AKS you can't reach etcd. You back up at the resource level instead, with Velero or GitOps, plus volume snapshots.
# kubeadm certificate paths shown
ETCDCTL_API=3 etcdctl --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 \
snapshot save /backup/etcd-snapshot.db
etcdutl --write-out=table snapshot status /backup/etcd-snapshot.db
# restore into a NEW data dir, with all API servers stopped
etcdutl --data-dir /var/lib/etcd-restored snapshot restore /backup/etcd-snapshot.dbetcdctl snapshot save with TLS certificatesetcdutl into a new data dir, API servers stoppedLikely follow-up: Why should an etcd cluster have an odd number of members?
The scheduler watches for pods with no spec.nodeName and handles each in two phases:
It picks the highest-scoring node, breaking ties randomly, and binds the pod by setting its node through the API server. The kubelet on that node sees the assignment and starts the containers.
If no node passes filtering, the pod stays Pending with a FailedScheduling event. A higher-priority pod, set through a PriorityClass, can trigger preemption, evicting lower-priority pods to make room.
The scheduler is built from plugins, supports profiles and can run alongside other schedulers (schedulerName). It only places pods once; it never moves running pods. The separate descheduler project does that.
Likely follow-up: How does pod priority and preemption work?
A PDB limits how many pods of an application can be taken down at once by voluntary disruptions: node drains, cluster upgrades, autoscaler scale-downs, anything that goes through the Eviction API. You set minAvailable or maxUnavailable, as a number or a percentage, and a selector.
If an eviction would break the budget, the API server refuses it with 429 Too Many Requests, and kubectl drain keeps retrying until replacement pods are healthy somewhere else. That's how a three-replica service survives a rolling node upgrade.
What it doesn't do:
maxUnavailable in the workload controls those.Pitfalls: minAvailable equal to the replica count, or maxUnavailable: 0, blocks drains forever, and so does minAvailable: 1 on a single-replica app. Crash-looping pods can also block drains; unhealthyPodEvictionPolicy: AlwaysAllow lets them be evicted.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: { name: web-pdb }
spec:
minAvailable: 2 # or maxUnavailable: 1
selector:
matchLabels: { app: web }minAvailable or maxUnavailable, plus a selectorLikely follow-up: How would you drain a node that runs a single-replica app with a PDB?
They scale different things:
Off (recommendations only), Initial (set at pod creation), Recreate (evict pods to apply) and InPlaceOrRecreate, which uses in-place pod resize where it can.They chain naturally: load rises, the HPA adds pods, some can't be scheduled, the cluster autoscaler adds nodes. The VPA keeps requests honest so both decide well, since scheduling works on requests, not usage.
The main rule: don't let the HPA and VPA act on the same CPU or memory metric for one workload, or they fight. Use VPA in Off mode, or scale the HPA on custom metrics.
Likely follow-up: How does Karpenter differ from the cluster autoscaler?
A CustomResourceDefinition extends the Kubernetes API with a new resource type, like PostgresCluster or Certificate. It declares an OpenAPI v3 schema for validation, versions and optionally a status subresource. After that, the API server serves the new type like a built-in one: kubectl get, RBAC and watches all work. But a CRD on its own just stores data.
An operator is CRDs plus a custom controller that encodes the operational knowledge for one piece of software. Its reconcile loop:
spec with what actually exists.status.Good operators are level-triggered and idempotent. They set owner references so garbage collection removes child objects, and use finalizers to clean up external resources before deletion. Examples include cert-manager, the Prometheus Operator, CloudNativePG and Strimzi. They're usually built with Kubebuilder or the Operator SDK.
The trade-offs: another privileged component to run and trust, and CRD version upgrades to manage.
Likely follow-up: Why should reconcile logic be level-triggered instead of edge-triggered?
kubectl commands do you use day to day to inspect, change and debug workloads?easyGrouped by what I'm doing:
kubectl get pods -n shop -o wide, filtered with -l app=web, across namespaces with -A, or live with -w. kubectl describe pod <name> is the first stop because of its events. kubectl get deploy web -o yaml shows the full object.kubectl logs <pod> -c <container> -f, --previous after a crash, kubectl exec -it <pod> -- sh, kubectl port-forward svc/web 8080:80, and kubectl debug for images without a shell.kubectl apply -f, with kubectl diff -f first; kubectl rollout status, history, undo and restart; kubectl scale deploy/web --replicas=5.kubectl top pods and kubectl top nodes (needs metrics-server), kubectl events, kubectl cordon and kubectl drain, kubectl auth can-i.kubectl explain deployment.spec.strategy, kubectl api-resources.kubectl create deployment web --image=nginx --dry-run=client -o yaml to generate a starting manifest.And always check kubectl config current-context before changing anything, so you don't apply to production by accident.
get, describe (events) and -o yaml to inspectlogs --previous, exec, port-forward, debugapply, diff and rollout subcommands to changetop, events, drain, auth can-i for operationsMy rule of thumb: Compose for local development; ECS with Fargate for smaller teams all-in on AWS who want minimal operations; Kubernetes when you need portability, hybrid or multi-cloud, many teams and services, or its ecosystem.
Likely follow-up: What would a migration from Compose to Kubernetes involve?
I think in layers:
RuntimeDefault seccomp profile. Avoid privileged, hostNetwork, hostPID and hostPath. Enforce it with Pod Security Admission at restricted.cluster-admin for CI pipelines.securityContext: # per container
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: [ALL] }
seccompProfile: { type: RuntimeDefault }restrictedLikely follow-up: How would you give a container a writable temp directory with a read-only root filesystem?
The Pod Security Standards define three profiles:
hostPath volumes, host ports and dangerous capabilities.allowPrivilegeEscalation: false, drop ALL capabilities (only NET_BIND_SERVICE may be added back), use a RuntimeDefault or Localhost seccomp profile, and use only a limited set of volume types.Pod Security Admission is the built-in admission controller that replaced PodSecurityPolicy, which was removed in v1.25. You apply it per namespace with labels such as pod-security.kubernetes.io/enforce: restricted. There are three modes: enforce rejects violating pods, audit records them in the audit log, and warn returns a warning to the user.
A safe rollout is to enforce baseline with warn and audit set to restricted, fix the violations, then enforce restricted. Enforcement applies to pods, so a Deployment can be accepted while its pods are rejected; look at the ReplicaSet's events. For custom rules, use ValidatingAdmissionPolicy, Kyverno or Gatekeeper.
pod-security.kubernetes.io/* labelsLikely follow-up: Why might a Deployment be created successfully but have no pods?
Liveness probe failed, or never become Ready. How do you debug probe failures?midStart with kubectl describe pod: the events say exactly how the probe failed, and the message points to the cause.
127.0.0.1 while the kubelet probes the pod IP.timeoutSeconds defaults to just 1 second. A slow health endpoint, a GC pause or CPU throttling makes it fail. Make the endpoint cheap, or raise the timeout.startupProbe whose failureThreshold × periodSeconds covers the worst-case startup.Then reproduce the probe yourself: kubectl exec into the pod and curl the endpoint, or port-forward to it. kubectl get endpointslices -l kubernetes.io/service-name=web shows which endpoints are ready.
timeoutSeconds is only 1 secondexec or port-forwardLikely follow-up: Should a readiness probe check the database?
Gateway API is the successor to Ingress, developed by SIG Network. It's an add-on: you install its CRDs, and implementations such as Envoy Gateway, Istio, Cilium or cloud load balancers provide the controller.
What it improves:
parentRefs.allowedRoutes controls who can attach, and ReferenceGrant is required to reference backends in another namespace.GatewayClass, Gateway, HTTPRoute and GRPCRoute are stable. With the Ingress API frozen, it's the recommended path, and the ingress2gateway tool helps migrate.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: shop, namespace: shop }
spec:
parentRefs: [{ name: public-gw, namespace: infra }]
hostnames: [shop.example.com]
rules:
- matches: [{ path: { type: PathPrefix, value: /api } }]
backendRefs:
- { name: api-v1, port: 80, weight: 90 }
- { name: api-v2, port: 80, weight: 10 } # 10% canaryLikely follow-up: Who would own the Gateway versus the HTTPRoute in your organization?
A service mesh is an infrastructure layer that handles service-to-service traffic without changing application code. Its data plane is a set of proxies that intercept traffic: traditionally a sidecar in every pod (Envoy in Istio, Linkerd's own proxy), or per-node proxies as in Istio's ambient mode. Its control plane configures the proxies and issues workload certificates.
What you get:
checkout may call payments".The costs are real: another complex system to run and upgrade, extra latency and CPU and memory per hop, and one more layer to debug.
It's worth it with many services, a zero-trust or mTLS requirement, or advanced traffic management needs. For a handful of services, Gateway API, NetworkPolicies and good client libraries are usually enough.
Likely follow-up: How does a sidecar-less mesh differ from the sidecar model?
When a pod is deleted, by a rollout, a scale-down or a drain:
preStop hook and then sends SIGTERM to each container's PID 1.The catch is that race. SIGTERM can arrive before every node and proxy has stopped routing to the pod, so an app that exits instantly drops requests.
Zero-downtime checklist:
terminationGracePeriodSeconds longer than the preStop plus drain time, since preStop counts against it.maxUnavailable: 0, and PDBs for node drains.spec:
terminationGracePeriodSeconds: 45
containers:
- name: web
image: example/web:3.2
lifecycle:
preStop:
sleep: { seconds: 10 } # let endpoint removal propagate first
readinessProbe:
httpGet: { path: /ready, port: 8080 }Likely follow-up: Why does CMD npm start sometimes break graceful shutdown?
kubectl apply -f deployment.yaml, until the containers are serving traffic.hardNo controller calls another directly; they coordinate through watches on the API server.
Likely follow-up: Where would a mutating webhook failure show up? · What is the difference between client-side and server-side apply?
externalTrafficPolicy: Local do on a LoadBalancer or NodePort Service, and what are the trade-offs versus Cluster?hardIt controls where a node may send traffic that arrives from outside the cluster.
Cluster (the default): any node can forward to any ready pod, including pods on other nodes. Load spreads evenly and every node can accept traffic, but the extra hop is source-NATed so replies come back the same way. The pod sees a node's IP, not the client's.Local: a node forwards only to pods on that same node. There's no second hop and no SNAT, so the client source IP is preserved (useful for allowlists, rate limiting and logs) and latency drops. Nodes with no local pod drop the traffic, so for LoadBalancer Services Kubernetes allocates a healthCheckNodePort that the cloud load balancer probes to find which nodes have endpoints.The trade-offs of Local: load can become imbalanced, because the load balancer spreads per node, not per pod, so a node with one pod gets as much traffic as a node with three. Pod moves also depend on the load balancer's health checks catching up. Spreading pods evenly helps.
healthCheckNodePort tells the LB which nodes have podsI'd combine controls at build time and at admission time:
image@sha256:…, or at least block :latest and untagged images.AlwaysPullImages admission plugin forces a registry check with the pod's own credentials, so one tenant can't reuse another's cached private image.Admission policies are the enforcement point; everything else feeds them trustworthy data.
:latestLikely follow-up: What happens to already-running pods when a new policy is added?
kube-node-lease. After a grace period (tens of seconds by default) the node controller sets the node's Ready condition to Unknown, marks its pods not ready so they drop out of Service endpoints, and taints the node node.kubernetes.io/unreachable.db-0 pods could corrupt data. You confirm the node is really gone, then delete the Node, force-delete the pod, or add the node.kubernetes.io/out-of-service taint.Shorter tolerationSeconds on critical stateless apps speeds up failover.
Likely follow-up: Why is force-deleting a StatefulSet pod dangerous?
kubectl debug do?midkubectl exec needs a shell and tools inside the image, which distroless images deliberately leave out. kubectl debug brings your own tools, in three ways:
--target, it shares the target container's process namespace, so you can see its processes, inspect /proc/<pid>/root and use tools like netstat or nslookup in the pod's network namespace. Ephemeral containers are stable since v1.25. They can't have ports, probes or resources, they're never restarted, and they can't be removed from the pod afterwards.--copy-to): creates a copy of the pod, optionally with a debug container, a changed command or different images. That's useful for a container that crashes on startup, because you can start it with a different command.node/<name>): runs a pod in the node's host namespaces with the host filesystem at /host, for checking the kubelet or container runtime without SSH.--profile selects preset security settings for the debug container, such as general, baseline, restricted or sysadmin.
# ephemeral container attached to a running pod, sharing the app's process namespace
kubectl debug -it api-6c9f --image=busybox:1.36 --target=api
# copy of the pod plus a debug container (the original keeps serving)
kubectl debug api-6c9f -it --image=busybox:1.36 --copy-to=api-debug --share-processes
# pod in the node's host namespaces, host filesystem at /host
kubectl debug node/worker-1 -it --image=busybox:1.36--target shares the app container’s process namespace--copy-to debugs a modified copy of the podnode/<name> debugs the node without SSHRun several control-plane nodes, typically three, spread across failure zones. Each component handles redundancy differently:
kube-system, and a standby takes over when the leader stops renewing.You can run etcd stacked on the control-plane nodes (fewer machines, but failures are coupled) or as a separate external cluster (more machines, better isolation).
If etcd loses quorum, the API stops accepting changes. Existing containers keep running on their nodes, but nothing is rescheduled or reconciled until quorum is restored. Managed services run all of this for you.
Likely follow-up: What are the trade-offs between stacked and external etcd?
emptyDir, hostPath and PersistentVolumeClaim volumes differ, and when would you use each?easyA container's own filesystem is lost when the container restarts. Volumes are declared at the pod level and mounted into containers, and they differ mainly in how long the data lives:
medium: Memory makes it a tmpfs that counts against the container's memory limit, and sizeLimit caps it./var/log.topologySpreadConstraints.hardA topology spread constraint tells the scheduler how unevenly matching pods may be distributed across domains, defined by a node label in topologyKey: zones, nodes or racks.
maxSkew: the maximum allowed difference between the number of matching pods in a domain and the lowest count in any eligible domain. With maxSkew: 1 and three zones, six replicas land 2/2/2, and seven land 3/2/2.whenUnsatisfiable: DoNotSchedule makes it a hard rule, so the pod stays Pending; ScheduleAnyway makes it a scoring preference.labelSelector: which pods count, usually the app's own labels. matchLabelKeys can restrict counting to pods of the same revision during rollouts.Compared with pod anti-affinity, which is essentially all-or-nothing ("not on a node that already has one"), spread constraints scale with the replica count. A common combination is a hard rule across zones and a soft one across nodes, as shown.
Caveat: they're only evaluated at scheduling time. Scale-downs or node failures can leave things skewed, and the descheduler project can rebalance. Without explicit constraints, the scheduler still applies soft built-in defaults for nodes and zones.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # hard rule
labelSelector: { matchLabels: { app: web } }
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway # soft preference
labelSelector: { matchLabels: { app: web } }topologyKey defines domains such as zones or nodesmaxSkew bounds the imbalance between domainsLikely follow-up: What happens to spreading when a Deployment scales down?
No questions match that filter.
Prefer multiple choice? All 20 Kubernetes MCQs with answers →