Kubernetes ships almost no monitoring. kubectl top does not work on a fresh cluster, there is no metric history, and container logs vanish when a Pod is deleted.
Everything below has to be installed deliberately.
metrics-server collects current CPU and memory usage from each kubelet and serves it through the Metrics API. It is what kubectl top and the Horizontal Pod Autoscaler read.
kubectl top nodes
kubectl top pods --containersNAME CPU(cores) MEMORY(bytes)
payments-api-7d4f-x8k2 143m 310Mi
payments-api-7d4f-m3p9 156m 298Mi
It stores nothing. There is no history, no querying, no alerting, just the last reading. If kubectl top fails or your HPA shows <unknown>, metrics-server is the first thing to check:
kubectl get deployment metrics-server -n kube-systemInstall it, but do not mistake it for observability.
The near-universal choice, and the practical way to install it is the kube-prometheus-stack chart, which bundles Prometheus, Alertmanager, Grafana, node-exporter and kube-state-metrics with dashboards and alert rules already wired together:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespaceAssembling those components individually is a genuine time sink for no benefit.
Three sources feed it, and knowing which produces what saves a lot of confusion:
| Source | Provides |
|---|---|
| kubelet / cAdvisor | Actual container CPU, memory, network, filesystem usage |
| kube-state-metrics | Object state from the API: replicas desired vs ready, Pod phase, restarts |
| node-exporter | Node-level OS metrics (disk, load, filesystem) |
kube-state-metrics is the one people miss. It does not measure resource usage; it exposes what the API server knows. That is where kube_deployment_status_replicas_unavailable, kube_pod_container_status_restarts_total and kube_pod_status_phase come from, and those are the metrics most of your alerts should be built on.
Expose Prometheus metrics from your service, then tell Prometheus to collect them with a ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: payments-api
namespace: payments
labels:
release: monitoring # must match the Prometheus instance's selector
spec:
selector:
matchLabels:
app: payments-api
endpoints:
- port: metrics # the NAME of a Service port, not a number
interval: 30sTwo things break this more often than anything else. The port must be the name of a port on the Service, so the Service must declare named ports. And the ServiceMonitor's own labels must match what the Prometheus resource selects, with kube-prometheus-stack that is usually release: <helm release name>.
# Is it being scraped?
kubectl port-forward -n monitoring svc/monitoring-kube-prometheus-prometheus 9090
# then open localhost:9090/targetsFor applications, the RED signals: Rate, Errors, Duration. For resources, the USE signals: Utilisation, Saturation, Errors.
Cluster-level things worth a dashboard:
| Signal | Why |
|---|---|
| Pod restart rate | The clearest leading indicator of trouble |
| Pods not Ready | Capacity you are paying for and not getting |
Node NotReady | Workloads about to be evicted |
| CPU throttling | Invisible latency from a too-low CPU limit |
| Memory usage vs limit | How close you are to OOMKilled |
| Requests vs allocatable | Why nothing schedules on an apparently idle cluster |
| PVC usage | Disks fill silently and fail loudly |
| Certificate expiry | A recurring, entirely avoidable outage |
A useful query. Pods restarting repeatedly in the last hour:
increase(kube_pod_container_status_restarts_total[1h]) > 3
And CPU throttling, which almost nobody watches and which explains a lot of unexplained latency:
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m]) > 0.25
Alert on symptoms users feel, not on every deviation. An alert nobody acts on trains people to ignore the channel.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: payments-api
namespace: payments
labels:
release: monitoring
spec:
groups:
- name: payments-api
rules:
- alert: PodCrashLooping
expr: increase(kube_pod_container_status_restarts_total{namespace="payments"}[15m]) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.pod }} is crash-looping"
- alert: DeploymentUnavailable
expr: kube_deployment_status_replicas_unavailable{namespace="payments"} > 0
for: 10m
labels:
severity: critical
annotations:
summary: "{{ $labels.deployment }} has unavailable replicas"The for clause matters as much as the expression. Without it, every transient blip pages someone.
A short, honest starting set: crash-looping Pods, a Deployment with unavailable replicas, nodes not ready, disk above 85%, certificates expiring within two weeks, and your own error rate and latency SLOs.
Container logs live on the node and are deleted with the Pod. kubectl logs reads them directly, which is fine for debugging and useless afterwards:
kubectl logs <pod>
kubectl logs <pod> --previous # the run that crashed
kubectl logs -l app=payments-api --tail=100 --prefix
kubectl logs <pod> --since=1hFor anything you need to keep, run a log shipper as a DaemonSet. Loki with Promtail or Alloy is the common pairing with Prometheus and Grafana and is far cheaper to operate than Elasticsearch; the ELK or OpenSearch stack remains the choice where full-text search over large volumes matters.
Log to stdout in JSON. Writing to files inside the container means the collector cannot see them, and structured logs make fields queryable instead of regex-matched.
Events are how Kubernetes explains itself, and they are only retained for one hour by default:
kubectl get events -A --sort-by=.lastTimestamp
kubectl get events --field-selector involvedObject.name=payments-api-7d4f-x8k2That hour is routinely shorter than the time between an incident and someone investigating it. Ship events into your logging system, kube-prometheus-stack does not do this by itself, and the gap is easy to miss until you need an event that has already expired.
Metrics say something is slow; traces say where. OpenTelemetry is the standard instrumentation layer, with Tempo, Jaeger or a commercial backend behind it.
Worth adding once you have several services calling each other. Before that, metrics and logs cover most of what you need.
| Task | Command |
|---|---|
| Current usage | kubectl top pods --containers |
| Node usage | kubectl top nodes |
| Logs | kubectl logs <pod> [-c <container>] |
| Crashed container logs | kubectl logs <pod> --previous |
| Logs by label | kubectl logs -l app=<label> --tail=100 --prefix |
| Recent events | kubectl get events -A --sort-by=.lastTimestamp |
| Reach Prometheus | kubectl port-forward -n monitoring svc/<prom-svc> 9090 |
| Reach Grafana | kubectl port-forward -n monitoring svc/<grafana-svc> 3000 |
| Is metrics-server up | kubectl get deploy metrics-server -n kube-system |
kubectl top and the HPA.kube-prometheus-stack rather than assembling the components yourself.port must be a port name, and the labels must match Prometheus's selector.for.<MCQExercise exercise={{ id: 'k8s-mon-metrics-server', title: 'Missing history', description: 'A team installs metrics-server and asks why they cannot graph last week's memory usage. What is the explanation?', options: [ { id: 'a', text: 'metrics-server holds only current readings in memory. Historical storage and querying require Prometheus or similar' }, { id: 'b', text: 'Historical metrics need to be enabled with a flag on metrics-server' }, { id: 'c', text: 'Retention defaults to 24 hours and must be increased' }, { id: 'd', text: 'kubectl top can show history with the --since flag' }, ], correctId: 'a', explanation: 'metrics-server exists to serve the Metrics API for kubectl top and the HorizontalPodAutoscaler, both of which only ever need the current value. It keeps a short in-memory window and writes nothing durable by design. Any question about trends over time needs a time-series database, which in practice means Prometheus scraping the same underlying kubelet and cAdvisor endpoints.', wrongExplanations: { b: 'There is no such flag. Persistence was deliberately left out to keep it lightweight and to avoid duplicating what Prometheus does.', c: 'Its retention is on the order of minutes, not a configurable day, and it is not a storage system in any case.', d: 'kubectl top has no --since flag, because the API it reads exposes only current values.', }, }} />
<MCQExercise exercise={{ id: 'k8s-mon-servicemonitor', title: 'A ServiceMonitor that scrapes nothing', description: 'A ServiceMonitor is applied, the application exposes /metrics correctly, but no target appears in Prometheus. What are the two things to check first?', options: [ { id: 'a', text: 'That the endpoint port is a port NAME declared on the Service, and that the ServiceMonitor labels match the Prometheus instance's selector' }, { id: 'b', text: 'That the application is annotated with prometheus.io/scrape: "true"' }, { id: 'c', text: 'That the ServiceMonitor is in the same namespace as Prometheus' }, { id: 'd', text: 'That metrics-server is installed and healthy' }, ], correctId: 'a', explanation: 'These two account for nearly every silent ServiceMonitor failure. The endpoints port field refers to a named port on the Service, so an unnamed port or a number matches nothing. And the Prometheus resource selects ServiceMonitors by label, with kube-prometheus-stack that is normally release: <helm-release>, so a ServiceMonitor without it is simply never picked up. Neither produces an error.', wrongExplanations: { b: 'Those annotations belong to the older static scrape-config approach. The Prometheus Operator discovers targets through ServiceMonitor and PodMonitor resources instead.', c: 'The Operator can discover ServiceMonitors across namespaces, governed by namespaceSelector. Co-location is not required.', d: 'metrics-server serves the Metrics API for kubectl top and the HPA. Prometheus scrapes independently and does not use it at all.', }, }} />
metrics-server serves only current CPU and memory readings, keeps no history, and exists to support kubectl top and autoscaling. Prometheus is a time-series database that scrapes many sources on an interval, stores the results, and supports querying, dashboards and alerting. They are complementary rather than alternatives: you generally run metrics-server for the HPA and Prometheus for everything a human looks at.
It exposes the state of Kubernetes objects as Prometheus metrics. How many replicas a Deployment wants versus has available, which phase each Pod is in, container restart counts, job completions. It measures the API server's view rather than actual resource consumption, which is why it complements the kubelet's cAdvisor metrics rather than duplicating them. Most genuinely useful alerts are built on it, so yes. It is included in kube-prometheus-stack.
You cannot with kubectl logs. Container logs live on the node and are removed with the Pod, and --previous only reaches back one container generation. Run a log shipper as a DaemonSet on every node: Promtail or Grafana Alloy feeding Loki is the usual pairing alongside Prometheus, while Fluent Bit into OpenSearch or a commercial backend suits environments needing heavier full-text search. Have applications log to stdout in JSON, since files written inside the container are invisible to the collector.
Events have a one-hour retention by default, controlled by the API server's --event-ttl. That is often shorter than the gap between an incident and someone investigating it, so the scheduling failure or probe error that explains everything has already expired. Ship events into your logging system with a tool such as the Kubernetes Event Exporter. This is not included in kube-prometheus-stack and is easy to overlook until the moment you need an event that is gone.
Symptoms that users experience, not every metric that moves. A short, honest starting set: Pods crash-looping, Deployments with unavailable replicas, nodes not Ready, disk above 85%, certificates expiring within two weeks, and your own error rate and latency against an agreed SLO. Always include a for clause so transient blips do not page anyone. Alerts nobody acts on are worse than no alerts, because they teach people to ignore the channel that also carries the real ones.
2d:["$","$L2e",null,{"tutorial":{"slug":"kubernetes-pods","title":"Kubernetes Pods","seoTitle":"What Is a Kubernetes Pod? Explained With Examples","description":"Learn what a Kubernetes Pod is, what its containers share, why pod IPs change, how to write one, and how to read the statuses you see when debugging.","content":"$2f","difficulty":"Easy","duration":"16 min","keywords":["kubernetes pods","what is a pod","pod ip address","pod lifecycle phases","CrashLoopBackOff","ImagePullBackOff","pod vs container","kubectl describe pod","sidecar container","init container"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":12,"tableOfContents":[{"id":"why-a-wrapper-around-containers-at-all","title":"Why a wrapper around containers at all?","level":2},{"id":"what-containers-in-a-pod-actually-share","title":"What containers in a Pod actually share","level":2},{"id":"pods-are-disposable-and-the-ip-goes-with-them","title":"Pods are disposable, and the IP goes with them","level":2},{"id":"writing-a-pod","title":"Writing a Pod","level":2},{"id":"multi-container-patterns","title":"Multi-container patterns","level":2},{"id":"lifecycle","title":"Lifecycle","level":2},{"id":"debugging-the-three-statuses-you-will-actually-see","title":"Debugging: the three statuses you will actually see","level":2},{"id":"dont-create-bare-pods","title":"Don't create bare Pods","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a Pod and a container?","answer":"A container is a running image with its own filesystem and process. A Pod is the Kubernetes object that wraps one or more containers and gives them a shared network namespace, a shared IP, and a shared lifecycle on a single node. Kubernetes schedules Pods, not containers. You cannot ask it to place a container directly. In practice most Pods hold exactly one container, and the multi-container capability exists for processes that genuinely cannot be separated, such as an application and its log shipper."},{"question":"Why is my Pod stuck in Pending?","answer":"Nothing has been able to schedule it. Run kubectl describe pod <name> and read the Events section, which states the reason explicitly. The frequent causes are resource requests larger than any node can satisfy, no node matching a nodeSelector or affinity rule, a node taint with no matching toleration on the Pod, and a PersistentVolumeClaim that has not bound. A message like 0/3 nodes are available: 3 Insufficient cpu means exactly what it says. Either lower the requests or add capacity."},{"question":"What does CrashLoopBackOff mean and how do I fix it?","answer":"It means the container starts, exits, and Kubernetes keeps restarting it with an increasing delay between attempts, up to five minutes. It is a symptom rather than a diagnosis. Read the logs of the run that failed with kubectl logs <pod> --previous, since plain kubectl logs shows the instance that has only just started. The usual causes are a missing environment variable or config file, a dependency unavailable at startup, an OOMKilled container whose memory limit is too low, and a liveness probe firing before the application has finished booting."},{"question":"Can two containers in the same Pod use the same port?","answer":"No. They share one network namespace and therefore one port space, so the second process to bind fails with "address already in use", exactly as it would on a single machine. Give each container a distinct port. This same shared namespace is what lets containers in a Pod reach each other on localhost with no service discovery."},{"question":"Should I ever create a Pod directly?","answer":"Only for debugging. A bare Pod has no controller behind it, so if its node fails or it is evicted, nothing recreates it. There is no self-healing, no scaling, and no rolling update. For real workloads use a Deployment for stateless services, a StatefulSet where stable identity or storage matters, a DaemonSet for per-node agents, and a Job or CronJob for work that completes. A throwaway debugging Pod via kubectl run --rm -it is the legitimate exception."},{"question":"Why does my Pod keep getting OOMKilled?","answer":"The container exceeded its memory limit and the kernel killed it. Confirm it with kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}', which returns OOMKilled. Either the limit is genuinely too low for the workload, or the application has a memory leak that a higher limit will only postpone. Check actual usage with kubectl top pod before raising the number, and be aware that runtimes with their own heap settings, the JVM especially, need to be told about the container limit or they will size their heap against the node's total memory instead."}]},"allTutorials":[{"slug":"introduction-to-kubernetes","title":"Introduction to Kubernetes","seoTitle":"What Is Kubernetes? A Beginner's Introduction (K8s)","description":"Learn what Kubernetes is and what problem it solves, how it differs from Docker and Docker Compose, the core objects to know, and whether you really need it.","content":"$30","difficulty":"Easy","duration":"18 min","keywords":["what is kubernetes","kubernetes for beginners","kubernetes vs docker","kubernetes vs docker compose","container orchestration","kubernetes explained","do i need kubernetes","kubernetes cluster basics"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":1,"tableOfContents":[{"id":"the-problem-it-solves","title":"The problem it solves","level":2},{"id":"a-concrete-example","title":"A concrete example","level":2},{"id":"kubernetes-vs-docker-compose","title":"Kubernetes vs Docker Compose","level":2},{"id":"the-objects-you-need-on-day-one","title":"The objects you need on day one","level":2},{"id":"how-the-cluster-is-arranged","title":"How the cluster is arranged","level":2},{"id":"do-you-actually-need-kubernetes","title":"Do you actually need Kubernetes?","level":2},{"id":"where-to-go-next","title":"Where to go next","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is Kubernetes in simple terms?","answer":"Kubernetes is a system that runs containers across a group of machines and keeps them running. You give it a description of what should be true. How many copies of an application, how much memory each gets, how they are reached, and it continuously compares that against reality and fixes any difference. If a container crashes it is restarted; if a machine dies its workloads are moved elsewhere; if you change the image it rolls out the new version gradually. You describe the destination rather than the steps."},{"question":"What is the difference between Docker and Kubernetes?","answer":"They do different jobs and are usually used together. Docker builds container images and runs containers on a single machine. Kubernetes takes images, which may well have been built with Docker, and runs them across many machines with scheduling, health checking, load balancing, rolling updates and scaling. Kubernetes removed Docker as its container runtime in version 1.24, but that changed nothing for users. Images follow the OCI standard, so Docker-built images run on Kubernetes exactly as before."},{"question":"Should I learn Docker before Kubernetes?","answer":"Yes. Kubernetes assumes you already understand images, containers, ports, volumes and environment variables, and a great many Kubernetes problems turn out to be container problems wearing a disguise: a wrong entrypoint, a missing environment variable, a process writing to a path that is not persisted. Being comfortable with docker build, docker run and writing a Dockerfile will make the Kubernetes learning curve markedly shorter."},{"question":"Is Kubernetes overkill for a small project?","answer":"Often, yes. If you run one application on one or two servers, deploy occasionally, and have no one with Kubernetes experience, the operational overhead. Upgrades, networking, storage, access control, and keeping up with a fast-moving ecosystem. Will likely cost more than it returns. Docker Compose on a single server, or a managed platform such as ECS, Cloud Run or App Runner, is a perfectly respectable choice. Move to Kubernetes when you hit a specific limitation you can name."},{"question":"What is the difference between Kubernetes and Docker Compose?","answer":"Compose orchestrates containers on one machine; Kubernetes orchestrates them across a cluster. Both restart a crashed container, but only Kubernetes reschedules workloads when an entire machine fails, performs rolling updates without downtime, scales automatically on load, and uses health probes to decide which instances receive traffic. Compose is simpler and better for local development and single-server deployments. The crossover point is the second machine."},{"question":"How long does it take to learn Kubernetes?","answer":"Deploying an application and understanding Pods, Deployments and Services takes a few days of hands-on work. Being genuinely comfortable. Debugging networking, understanding storage and access modes, reasoning about scheduling and resource limits, and operating upgrades. Takes months of real use. The most effective approach is a local cluster with kind or minikube and deliberately breaking things: delete Pods, set a memory limit too low, misconfigure a probe. The failure modes are what you actually need to recognise."}]},{"slug":"kubernetes-architecture","title":"Kubernetes Architecture","seoTitle":"Kubernetes Architecture: Control Plane and Node Components","description":"Understand Kubernetes architecture: API server, etcd, scheduler, controller manager, kubelet and kube-proxy, and what happens when you run kubectl apply.","content":"$31","difficulty":"Medium","duration":"18 min","keywords":["kubernetes architecture","control plane components","kube-apiserver","etcd","kube-scheduler","controller manager","kubelet","kube-proxy","container runtime","kubernetes request flow"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":2,"tableOfContents":[{"id":"control-plane","title":"Control plane","level":2},{"id":"kube-apiserver","title":"kube-apiserver","level":3},{"id":"etcd","title":"etcd","level":3},{"id":"kube-scheduler","title":"kube-scheduler","level":3},{"id":"kube-controller-manager","title":"kube-controller-manager","level":3},{"id":"cloud-controller-manager","title":"cloud-controller-manager","level":3},{"id":"node-components","title":"Node components","level":2},{"id":"kubelet","title":"kubelet","level":3},{"id":"kube-proxy","title":"kube-proxy","level":3},{"id":"container-runtime","title":"Container runtime","level":3},{"id":"what-happens-when-you-run-kubectl-apply","title":"What happens when you run kubectl apply","level":2},{"id":"control-plane-vs-node-side-by-side","title":"Control plane vs node, side by side","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What are the main components of Kubernetes architecture?","answer":"The control plane comprises the API server (the single entry point for every request), etcd (the key-value store holding all cluster state), the scheduler (assigns Pods to nodes), the controller manager (runs the reconciliation loops that provide self-healing), and on cloud clusters the cloud controller manager (provisions load balancers and volumes). Each worker node runs the kubelet (starts and monitors containers), kube-proxy (programs Service networking rules), and a container runtime such as containerd."},{"question":"What happens if the Kubernetes control plane goes down?","answer":"Running Pods keep running and keep serving traffic. Kubelets deliberately continue managing the containers they already have when they cannot reach the API server, and kube-proxy's existing kernel rules keep routing Service traffic. What stops is change: no deployments, no scaling, no rescheduling of failed Pods, and kubectl does not work. It is a control outage rather than a service outage, though it becomes a service outage if something fails during it, since nothing will replace it."},{"question":"Why does etcd need an odd number of members?","answer":"etcd uses Raft consensus, which requires a strict majority of members to agree before committing a write. Three members need two healthy and tolerate one failure; five need three and tolerate two. A four-member cluster still needs three healthy, so it tolerates exactly one failure, the same as three members, while giving you an extra machine that can fail. Even numbers add cost and risk without adding fault tolerance."},{"question":"What is the difference between kubelet and kube-proxy?","answer":"The kubelet manages containers: it watches for Pods assigned to its node, instructs the container runtime to start them, mounts volumes, executes liveness and readiness probes, and reports status to the API server. kube-proxy manages Service networking: it watches Services and EndpointSlices and programs iptables or IPVS rules so traffic sent to a Service's virtual IP is rewritten to a real Pod IP. One runs your workloads, the other makes them reachable."},{"question":"Does Kubernetes still use Docker?","answer":"Not as a runtime. Kubernetes removed the Docker shim in version 1.24 and uses containerd or CRI-O through the Container Runtime Interface. This has no effect on images: Docker produces OCI-compliant images and containerd runs them, so anything built with docker build works unchanged. The only practical difference is on the nodes themselves, where docker ps no longer shows Kubernetes containers, crictl ps does."},{"question":"Why is my Pod stuck in Pending?","answer":"The scheduler filtered out every node and found nowhere to place it. kubectl describe pod <name> reports the exact reason in its events, usually something like 0/5 nodes are available: 2 Insufficient memory, 2 node(s) had untolerated taint. The frequent causes are resource requests larger than any node's free capacity, taints without matching tolerations, node selectors or affinity rules that match nothing, and a PersistentVolumeClaim that cannot bind or is in the wrong Availability Zone."}]},{"slug":"installing-kubernetes","title":"Installing Kubernetes","seoTitle":"How to Install Kubernetes Locally: kind, Minikube, k3s","description":"Compare ways to install Kubernetes: kind, Minikube, Docker Desktop, k3s, kubeadm and managed clusters, and pick the right one for learning or production.","content":"$32","difficulty":"Easy","duration":"14 min","keywords":["install kubernetes","kind vs minikube","local kubernetes cluster","docker desktop kubernetes","k3s","kubeadm","managed kubernetes eks gke aks","kubernetes for learning"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":3,"tableOfContents":[{"id":"the-options","title":"The options","level":2},{"id":"for-learning-kind-or-minikube","title":"For learning: kind or minikube","level":2},{"id":"kind","title":"kind","level":3},{"id":"minikube","title":"minikube","level":3},{"id":"which-one","title":"Which one?","level":3},{"id":"docker-desktop","title":"Docker Desktop","level":2},{"id":"k3s","title":"k3s","level":2},{"id":"kubeadm","title":"kubeadm","level":2},{"id":"managed-clusters","title":"Managed clusters","level":2},{"id":"resources-you-will-actually-need","title":"Resources you will actually need","level":2},{"id":"after-installing","title":"After installing","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the easiest way to install Kubernetes for learning?","answer":"kind or minikube. kind runs each node as a Docker container and starts in well under a minute, making it ideal for throwing clusters away and recreating them. minikube is a little slower but has an add-on system that installs Ingress, metrics-server and a dashboard with one command each. Either is a good choice, and both are far more appropriate for learning than building a cluster with kubeadm."},{"question":"What is the difference between kind and minikube?","answer":"kind creates nodes as Docker containers, which makes it very fast and well suited to CI pipelines, and multi-node clusters are a few lines of config. minikube typically runs in a VM or Docker and provides curated add-ons, so getting Ingress or metrics-server working takes one command rather than a Helm install. kind requires kind load docker-image to make local builds available; minikube offers minikube tunnel to make LoadBalancer Services function. Both are fine. Choose on whether you value speed or bundled add-ons."},{"question":"Should I use Docker Desktop's built-in Kubernetes?","answer":"It is a reasonable low-effort option if you already run Docker Desktop, since enabling it is a checkbox. The limitations are that it is single-node, its Kubernetes version is tied to the Docker Desktop release, and it shares resources with your containers. For anything beyond a quick look. Particularly anything involving scheduling, node affinity or multi-node behaviour. Kind or minikube will serve you better."},{"question":"Can I run Kubernetes in production without a cloud provider?","answer":"Yes. kubeadm builds a conformant cluster on your own machines, and k3s is a certified lightweight distribution that runs well on small servers and edge hardware. Be clear about what you take on: etcd backups, certificate rotation, control plane upgrades, node patching, and the networking and storage integrations a cloud provider supplies. For most teams the managed control plane fee is smaller than the engineering time it replaces."},{"question":"How much memory and CPU does a local cluster need?","answer":"At least 2 CPUs and 4GB of RAM free, and 4 CPUs with 8GB is a much better experience once you add an ingress controller or a monitoring stack. Kubernetes control plane components alone consume a meaningful share of a small allocation. If Pods sit Pending with Insufficient cpu on a freshly created cluster, the cluster is undersized rather than misconfigured. Restart it with more resources."},{"question":"Why are my CoreDNS Pods stuck in Pending after kubeadm init?","answer":"No CNI plugin is installed. kubeadm init deliberately leaves the choice of network plugin to you, and without one Pods cannot be assigned addresses, so CoreDNS cannot start. Apply a CNI such as Calico, Flannel or Cilium and CoreDNS moves to Running within a minute. If you passed --pod-network-cidr to kubeadm init, make sure the plugin's configuration uses the same range."}]},{"slug":"installing-kubernetes-minikube","title":"Installing Kubernetes with Minikube","seoTitle":"How to Install Minikube and Start a Local Cluster","description":"Install Minikube, run minikube start, enable add-ons like ingress, reach services with minikube tunnel, and build images locally without a registry.","content":"$33","difficulty":"Easy","duration":"13 min","keywords":["minikube install","minikube start","minikube addons ingress","minikube tunnel","minikube docker-env","minikube profile","minikube dashboard","local kubernetes"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":4,"tableOfContents":[{"id":"install","title":"Install","level":2},{"id":"start-a-cluster","title":"Start a cluster","level":2},{"id":"add-ons","title":"Add-ons","level":2},{"id":"reaching-your-services","title":"Reaching your services","level":2},{"id":"building-images-without-a-registry","title":"Building images without a registry","level":2},{"id":"profiles","title":"Profiles","level":2},{"id":"housekeeping","title":"Housekeeping","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How much memory and CPU should I give minikube?","answer":"At least 4 CPUs and 8GB if your machine allows it, passed as minikube start --cpus=4 --memory=8192. The defaults of 2 CPUs and 2GB are enough for an empty cluster and run out as soon as you enable an ingress controller or deploy a few workloads, producing Insufficient cpu errors that look like a misconfiguration but are simply a lack of capacity. The setting only applies at creation, so changing it means deleting and recreating the cluster."},{"question":"How do I access a service running in minikube?","answer":"For a NodePort Service, minikube service <name> --url prints a working URL. For a LoadBalancer Service you need minikube tunnel running in a separate terminal, otherwise the external IP stays <pending> indefinitely. With the ingress add-on enabled, minikube ip gives the address to point a hosts-file entry at. For a quick check none of that is necessary, kubectl port-forward service/<name> 8080:80 works immediately."},{"question":"Why is my LoadBalancer Service stuck pending in minikube?","answer":"There is no cloud provider to create a load balancer, so nothing ever assigns the external IP. minikube tunnel fills that role by creating a network route into the cluster and assigning addresses to LoadBalancer Services. It has to keep running in its own terminal, and it usually prompts for a password because it modifies system routes."},{"question":"How do I use a locally built Docker image in minikube?","answer":"Run eval $(minikube docker-env) to point your shell at minikube's own Docker daemon, then build normally. The image lands directly in the cluster with no registry involved. Set imagePullPolicy: IfNotPresent on the container, since Always, which is also the default for an unpinned or latest tag, makes the kubelet attempt a registry pull that fails. eval $(minikube docker-env -u) switches your shell back. Alternatively, minikube image load myapp:dev copies an already-built image in."},{"question":"What is the difference between minikube stop and minikube delete?","answer":"stop shuts the cluster down while preserving its state, so minikube start brings everything back including your deployments. Much faster than recreating. delete removes the cluster and all its data permanently. Use stop to reclaim memory and CPU between work sessions, and delete when you want a clean slate or when troubleshooting the local cluster itself has become slower than rebuilding it."},{"question":"Can I run more than one minikube cluster?","answer":"Yes, using profiles: minikube start -p payments and minikube start -p legacy --kubernetes-version=v1.28.0 create independent clusters, each with its own kubectl context. minikube profile list shows them and minikube profile <name> sets the default for minikube commands. Each profile is a separate VM or container consuming real CPU and memory, so delete profiles you are not actively using."}]},{"slug":"installing-kubernetes-kind","title":"Installing Kubernetes with kind","seoTitle":"kind create cluster: Run Kubernetes in Docker Locally","description":"Install kind and run kind create cluster to get a local Kubernetes cluster in Docker. Covers multi-node configs, pinned versions, local images, ingress and CI.","content":"$34","difficulty":"Easy","duration":"13 min","keywords":["kind kubernetes","kind create cluster","kind config multi node","kind load docker-image","kind extraPortMappings","kind ingress nginx","kubernetes in docker","kind ci github actions"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":5,"tableOfContents":[{"id":"install","title":"Install","level":2},{"id":"a-first-cluster","title":"A first cluster","level":2},{"id":"multi-node-which-is-the-point","title":"Multi-node, which is the point","level":2},{"id":"pinning-the-kubernetes-version","title":"Pinning the Kubernetes version","level":2},{"id":"loading-local-images","title":"Loading local images","level":2},{"id":"ingress","title":"Ingress","level":2},{"id":"mounting-host-directories","title":"Mounting host directories","level":2},{"id":"kind-in-ci","title":"kind in CI","level":2},{"id":"debugging","title":"Debugging","level":2},{"id":"kind-or-minikube","title":"kind or minikube?","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is kind and how is it different from minikube?","answer":"kind runs each Kubernetes node as a Docker container rather than in a VM, which makes cluster creation and deletion very fast and multi-node configurations trivial. That speed is why it is the usual choice for CI. minikube is somewhat slower but ships curated add-ons, so Ingress and metrics-server are one command each rather than a manual install. The practical split is kind for speed, multi-node and pipelines; minikube for a batteries-included local environment."},{"question":"Why does my locally built image give ImagePullBackOff in kind?","answer":"Because kind nodes are Docker containers with their own containerd image store, and your host's Docker images are not visible inside them. Run kind load docker-image myapp:dev --name <cluster> to copy it in. Also set imagePullPolicy: IfNotPresent, since Always, which is the default when the tag is latest or omitted, makes the kubelet try a registry pull that fails. Forgetting kind load is the most common kind problem by a wide margin."},{"question":"How do I create a multi-node cluster with kind?","answer":"Write a config file listing the nodes you want, with one control-plane role and one or more worker roles, then pass it with kind create cluster --config kind-config.yaml. It is worth doing even for simple work: on a single node, pod anti-affinity and topology spread constraints have no observable effect, ReadWriteOnce volume conflicts never occur, and node drains are meaningless. All of which then surprise you in a real cluster."},{"question":"How do I use an Ingress controller with kind?","answer":"Declare extraPortMappings for ports 80 and 443 in the cluster config, and label the control-plane node with ingress-ready=true. These must be set when the cluster is created, because Docker fixes published ports at container creation. Adding them later means recreating the cluster. Then apply the kind-specific ingress-nginx manifest, which schedules the controller onto the labelled node, and localhost will reach your Ingress."},{"question":"Why is my LoadBalancer Service pending in kind?","answer":"There is no cloud provider to fulfil the request, so the external IP is never assigned. Install MetalLB if you genuinely need LoadBalancer Services to work, but for local development an Ingress with extraPortMappings, or simply kubectl port-forward, is considerably less setup for the same result."},{"question":"Can I use kind in CI pipelines?","answer":"Yes, and it is the standard approach for integration testing against a real Kubernetes API. helm/kind-action sets up a cluster in a GitHub Actions job in under a minute, after which you build your image, kind load it, apply your manifests, and run tests. Because it is a genuine API server, it catches admission controller rejections, RBAC problems and invalid manifests that YAML linting cannot detect."}]},{"slug":"installing-kubernetes-kubectl","title":"Installing kubectl","seoTitle":"How to Install kubectl on Linux, macOS and Windows","description":"Install kubectl, stay within the supported version skew, connect to a cluster with kubeconfig, enable shell completion and add useful krew plugins.","content":"$35","difficulty":"Easy","duration":"12 min","keywords":["install kubectl","kubectl version skew","kubeconfig setup","kubectl completion","krew plugin manager","kubectl alias","kubectl client version"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":6,"tableOfContents":[{"id":"install","title":"Install","level":2},{"id":"macos","title":"macOS","level":3},{"id":"linux","title":"Linux","level":3},{"id":"windows","title":"Windows","level":3},{"id":"verify","title":"Verify","level":3},{"id":"version-skew","title":"Version skew","level":2},{"id":"connecting-to-a-cluster","title":"Connecting to a cluster","level":2},{"id":"set-this-up-on-day-one","title":"Set this up on day one","level":2},{"id":"shell-completion","title":"Shell completion","level":3},{"id":"the-alias","title":"The alias","level":3},{"id":"context-in-your-prompt","title":"Context in your prompt","level":3},{"id":"plugins-worth-having","title":"Plugins worth having","level":2},{"id":"commands-to-confirm-it-works","title":"Commands to confirm it works","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I install kubectl?","answer":"Use your platform's package manager: brew install kubectl on macOS, winget install -e --id Kubernetes.kubectl on Windows, and either the official apt or yum repository or the release binary on Linux. It is a single static binary with no dependencies, so downloading it and placing it on your PATH works anywhere. Confirm with kubectl version --client."},{"question":"Does kubectl need to match my cluster version?","answer":"Not exactly. kubectl is supported within one minor version either side of the API server, so a v1.31 client works with clusters running 1.30, 1.31 or 1.32. Beyond that, most things still work until a newer resource field or subcommand is missing, at which point failures look confusing. kubectl version shows both client and server so you can check the gap. If you manage clusters on different versions, keep the client at the newest version you talk to."},{"question":"Why does kubectl version give a connection error?","answer":"Because it reports both the client and the server version by default, so it tries to reach a cluster. With no kubeconfig, or no cluster running, that fails. kubectl version --client reports only the local binary and contacts nothing. This trips up almost everyone immediately after installing, and does not indicate a problem with the installation."},{"question":"How do I connect kubectl to my cluster?","answer":"Local tools handle it for you, kind create cluster and minikube start both write and select a context in /.kube/config. For cloud clusters, run the provider's command: aws eks update-kubeconfig, gcloud container clusters get-credentials, or az aks get-credentials. Verify with kubectl cluster-info, which prints the API server URL when connected. A connection refused means no cluster is reachable; Unauthorized means the credentials are the problem rather than the connection."},{"question":"How do I enable kubectl autocompletion?","answer":"Source the completion script from your shell profile: echo 'source <(kubectl completion zsh)' >> /.kube/client-a.yaml, and kubectl merges them, exposing every context. To produce a single merged file, run KUBECONFIG=a:b kubectl config view --flatten > merged; the --flatten flag inlines certificate data, which a plain config view leaves as file paths that then break when the file moves. Keeping production credentials in a separate file you source deliberately is itself a useful safeguard."},{"question":"How do I avoid running commands against the wrong cluster?","answer":"Show the current context in your shell prompt, kube-ps1, or the Kubernetes segment in starship, powerlevel10k or oh-my-zsh, so it is always visible. Prefer kubectl --context=<name> for one-off commands rather than switching globally. Most importantly, make production read-only by default with a separate admin context used only when you intend to change something, so RBAC refuses destructive commands rather than relying on you remembering which cluster you are on."},{"question":"Why does kubectl ask me to log in again on a cloud cluster?","answer":"Cloud kubeconfigs use an exec credential plugin that shells out to aws, gcloud or az to obtain a short-lived token on each call. When that CLI's own session expires, kubectl surfaces it as an authentication failure even though the kubeconfig itself is fine. Re-authenticating with the cloud CLI, aws sso login, gcloud auth login, restores access without touching your Kubernetes configuration."}]},{"slug":"kubernetes-pods","title":"Kubernetes Pods","seoTitle":"What Is a Kubernetes Pod? Explained With Examples","description":"Learn what a Kubernetes Pod is, what its containers share, why pod IPs change, how to write one, and how to read the statuses you see when debugging.","content":"$3b","difficulty":"Easy","duration":"16 min","keywords":"$2d:props:tutorial:keywords","publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":12,"tableOfContents":[{"id":"why-a-wrapper-around-containers-at-all","title":"Why a wrapper around containers at all?","level":2},{"id":"what-containers-in-a-pod-actually-share","title":"What containers in a Pod actually share","level":2},{"id":"pods-are-disposable-and-the-ip-goes-with-them","title":"Pods are disposable, and the IP goes with them","level":2},{"id":"writing-a-pod","title":"Writing a Pod","level":2},{"id":"multi-container-patterns","title":"Multi-container patterns","level":2},{"id":"lifecycle","title":"Lifecycle","level":2},{"id":"debugging-the-three-statuses-you-will-actually-see","title":"Debugging: the three statuses you will actually see","level":2},{"id":"dont-create-bare-pods","title":"Don't create bare Pods","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a Pod and a container?","answer":"A container is a running image with its own filesystem and process. A Pod is the Kubernetes object that wraps one or more containers and gives them a shared network namespace, a shared IP, and a shared lifecycle on a single node. Kubernetes schedules Pods, not containers. You cannot ask it to place a container directly. In practice most Pods hold exactly one container, and the multi-container capability exists for processes that genuinely cannot be separated, such as an application and its log shipper."},{"question":"Why is my Pod stuck in Pending?","answer":"Nothing has been able to schedule it. Run kubectl describe pod <name> and read the Events section, which states the reason explicitly. The frequent causes are resource requests larger than any node can satisfy, no node matching a nodeSelector or affinity rule, a node taint with no matching toleration on the Pod, and a PersistentVolumeClaim that has not bound. A message like 0/3 nodes are available: 3 Insufficient cpu means exactly what it says. Either lower the requests or add capacity."},{"question":"What does CrashLoopBackOff mean and how do I fix it?","answer":"It means the container starts, exits, and Kubernetes keeps restarting it with an increasing delay between attempts, up to five minutes. It is a symptom rather than a diagnosis. Read the logs of the run that failed with kubectl logs <pod> --previous, since plain kubectl logs shows the instance that has only just started. The usual causes are a missing environment variable or config file, a dependency unavailable at startup, an OOMKilled container whose memory limit is too low, and a liveness probe firing before the application has finished booting."},{"question":"Can two containers in the same Pod use the same port?","answer":"No. They share one network namespace and therefore one port space, so the second process to bind fails with "address already in use", exactly as it would on a single machine. Give each container a distinct port. This same shared namespace is what lets containers in a Pod reach each other on localhost with no service discovery."},{"question":"Should I ever create a Pod directly?","answer":"Only for debugging. A bare Pod has no controller behind it, so if its node fails or it is evicted, nothing recreates it. There is no self-healing, no scaling, and no rolling update. For real workloads use a Deployment for stateless services, a StatefulSet where stable identity or storage matters, a DaemonSet for per-node agents, and a Job or CronJob for work that completes. A throwaway debugging Pod via kubectl run --rm -it is the legitimate exception."},{"question":"Why does my Pod keep getting OOMKilled?","answer":"The container exceeded its memory limit and the kernel killed it. Confirm it with kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}', which returns OOMKilled. Either the limit is genuinely too low for the workload, or the application has a memory leak that a higher limit will only postpone. Check actual usage with kubectl top pod before raising the number, and be aware that runtimes with their own heap settings, the JVM especially, need to be told about the container limit or they will size their heap against the node's total memory instead."}]},{"slug":"kubernetes-pods-manifest","title":"Kubernetes Pod Manifest","seoTitle":"Kubernetes Pod YAML: Manifest Fields With Examples","description":"Write a Kubernetes Pod manifest with confidence. Walks through apiVersion, metadata, the container spec, ports and securityContext with a full example.","content":"$3c","difficulty":"Easy","duration":"14 min","keywords":["kubernetes pod yaml","pod manifest example","containerPort","securityContext","pod spec fields","kubectl explain","imagePullPolicy","pod immutable fields"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":13,"tableOfContents":[{"id":"the-four-top-level-fields","title":"The four top-level fields","level":2},{"id":"metadata","title":"Metadata","level":2},{"id":"the-container-spec","title":"The container spec","level":2},{"id":"ports-is-documentation","title":"ports is documentation","level":3},{"id":"imagepullpolicy","title":"imagePullPolicy","level":3},{"id":"env-and-the-downward-api","title":"env and the Downward API","level":3},{"id":"securitycontext","title":"securityContext","level":2},{"id":"full-example","title":"Full example","level":2},{"id":"pods-are-largely-immutable","title":"Pods are largely immutable","level":2},{"id":"finding-fields-without-searching","title":"Finding fields without searching","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What are the required fields in a Pod manifest?","answer":"apiVersion: v1, kind: Pod, a metadata.name, and a spec.containers list where each container has a name and an image. Everything else is optional, though a manifest with only those is unfit for production. Resource requests, a readiness probe and a securityContext should all be considered part of the minimum in practice. kubectl explain pod.spec documents every available field against your cluster's own API version."},{"question":"What is the difference between labels and annotations?","answer":"Labels are indexed key-value pairs used for selection: Services find Pods by label, Deployments identify the Pods they own by label, and NetworkPolicies choose targets by label. Annotations hold arbitrary metadata that nothing selects on, build information, tool configuration, controller hints, and can be far larger. If something needs to find the object, it must be a label; if it only needs to know something, it belongs in an annotation."},{"question":"Does containerPort actually expose the port?","answer":"No. It is informational. The container listens on whatever port its process binds, and Kubernetes neither enforces nor alters that. Declaring containerPort documents intent and, when named, lets a Service's targetPort or a probe's port reference it by name rather than number, which is more readable and survives a port change. A mismatch between the declaration and reality produces no error, only a Service or probe that cannot connect."},{"question":"Why can't I edit a running Pod?","answer":"Pods are almost entirely immutable once created, because the scheduler has already placed them and reserved resources on a specific node based on their spec. Only spec.containers[].image may be changed. To alter anything else you delete and recreate the Pod, which is precisely why real workloads use a Deployment, where changing the template creates replacement Pods gradually and keeps the previous version available for rollback."},{"question":"What should I put in securityContext?","answer":"At Pod level, runAsNonRoot: true with an explicit runAsUser, an fsGroup so mounted volumes are writable by that user, and seccompProfile: RuntimeDefault. At container level, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, and capabilities: drop: ["ALL"]. runAsNonRoot is the highest-value single setting because it converts an image that silently runs as root into a visible startup failure. A read-only root filesystem usually requires adding an emptyDir at /tmp."},{"question":"How do I find out what fields are available?","answer":"kubectl explain pod.spec and kubectl explain pod.spec.containers.resources document fields directly from the API server you are connected to, so the output always matches your cluster's version rather than whatever a search result describes. Add --recursive to see a whole subtree at once. To get a working starting point, kubectl run name --image=x --dry-run=client -o yaml generates a valid manifest without contacting the cluster."}]},{"slug":"kubernetes-pods-lifecycle","title":"Kubernetes Pod Lifecycle","seoTitle":"Kubernetes Pod Lifecycle: Phases, States, CrashLoopBackOff","description":"Understand Kubernetes pod phases and container states, restartPolicy, termination and eviction, and why CrashLoopBackOff is a symptom rather than the cause.","content":"$3d","difficulty":"Medium","duration":"15 min","keywords":["kubernetes pod lifecycle","pod phases","container states","restartPolicy","CrashLoopBackOff","terminationGracePeriodSeconds","preStop hook","pod eviction"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":14,"tableOfContents":[{"id":"phases","title":"Phases","level":2},{"id":"container-states","title":"Container states","level":2},{"id":"startup-order","title":"Startup order","level":2},{"id":"restartpolicy","title":"restartPolicy","level":2},{"id":"crashloopbackoff-is-a-symptom","title":"CrashLoopBackOff is a symptom","level":2},{"id":"termination","title":"Termination","level":2},{"id":"eviction","title":"Eviction","level":2},{"id":"reading-status-quickly","title":"Reading status quickly","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What are the Kubernetes Pod phases?","answer":"Pending means the Pod is accepted but not yet running. Being scheduled, or pulling images. Running means it is bound to a node with at least one container running. Succeeded means all containers exited 0 and will not restart, and Failed means all terminated with at least one failure. Unknown means the node stopped reporting. Phases only move forward; a failed Pod is replaced rather than recovered. Note that Running says nothing about health. The READY column does that."},{"question":"What does CrashLoopBackOff actually mean?","answer":"The container starts, exits, and the kubelet restarts it with a growing delay: 10 seconds, then 20, then 40, doubling to a five-minute cap, resetting after ten minutes of stable running. It is a symptom, not a diagnosis. Find the cause with kubectl logs <pod> --previous, which shows the run that failed rather than the one just starting, and kubectl describe pod for the events. The usual culprits are missing configuration, an unreachable startup dependency, an OOM kill, or a liveness probe firing before the application is ready."},{"question":"What is the difference between restartPolicy values?","answer":"Always restarts the container on any exit, including a successful one, and is the default used by Deployments, StatefulSets and DaemonSets. OnFailure restarts only on a non-zero exit and suits Jobs. Never does not restart at all. The policy applies to containers within a Pod. A Pod itself is never restarted, only replaced. This is why a task that completes should run as a Job: under Always a successful exit is treated as something to correct."},{"question":"What do exit codes 137 and 143 mean?","answer":"Codes above 128 encode the signal that terminated the process: 137 is 128 plus 9 for SIGKILL, and 143 is 128 plus 15 for SIGTERM. 143 usually indicates a normal shutdown during a deploy or scale-down. 137 means the process was killed outright, most often OOMKilled at its memory limit or terminated after failing a liveness probe. Check lastState.terminated.reason to tell those apart. SIGKILL cannot be caught, so the container never logs anything about it."},{"question":"How do I make my application shut down gracefully?","answer":"Three things together. Handle SIGTERM in the application by refusing new connections and finishing in-flight requests rather than exiting immediately. Add a preStop hook that sleeps a few seconds, so removal from Service endpoints propagates to every node before the signal arrives. And set terminationGracePeriodSeconds above your longest normal request, because anything still running when it expires is SIGKILLed. Skipping the preStop delay is the usual reason a correctly-configured rolling update still drops a few connections."},{"question":"Why is my Pod stuck in Init:0/1?","answer":"An init container is failing or still waiting. Init containers run to completion in order before any application container starts, so a failing one blocks the Pod indefinitely while being retried. Read its logs with kubectl logs <pod> -c <init-container-name>. The container name is required, since the main container has not started. Common causes are an init container waiting on a dependency that never becomes available, or a migration step failing against the database."}]},{"slug":"kubernetes-pods-multicontainer","title":"Multi-container Pods","seoTitle":"Multi-Container Pods: Sidecar and Init Container Patterns","description":"Learn when and how to run multiple containers in one Kubernetes Pod: init containers, native sidecars, shared emptyDir volumes, and when to avoid the pattern.","content":"$3e","difficulty":"Medium","duration":"15 min","keywords":["multi container pod","sidecar container","init container","native sidecar restartPolicy Always","shared volume emptyDir","ambassador pattern","adapter pattern","pod shared network namespace"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":15,"tableOfContents":[{"id":"what-is-shared-and-what-is-not","title":"What is shared and what is not","level":2},{"id":"init-containers","title":"Init containers","level":2},{"id":"sidecars","title":"Sidecars","level":2},{"id":"the-other-two-patterns","title":"The other two patterns","level":2},{"id":"sharing-data-between-containers","title":"Sharing data between containers","level":2},{"id":"when-not-to-use-a-multi-container-pod","title":"When not to use a multi-container Pod","level":2},{"id":"working-with-them","title":"Working with them","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"When should a Pod contain more than one container?","answer":"Only when the containers need a shared network namespace or a shared volume. A log shipper reading files the application writes needs the volume; a service mesh proxy intercepting the application's traffic needs the network namespace. Those are genuine reasons. If two containers merely communicate over the network, they belong in separate Pods with a Service between them, because containers in a Pod are scheduled, scaled and deployed as a single unit."},{"question":"What is the difference between an init container and a sidecar?","answer":"An init container runs to completion before the application containers start, and is used for migrations, waiting on dependencies, or fetching configuration. A sidecar runs for the whole life of the Pod alongside the application. Modern Kubernetes expresses sidecars as init containers with restartPolicy: Always, which makes them start before the app containers and terminate after them. Solving startup ordering for service mesh proxies and, importantly, letting Jobs with sidecars actually complete."},{"question":"Why does my Job with a sidecar never complete?","answer":"Because a Job is only complete when every container in its Pod has terminated, and a sidecar such as a log shipper has no reason to exit. The Pod stays active forever. The fix is to declare the sidecar as an init container with restartPolicy: Always rather than as an ordinary entry in containers; Kubernetes then recognises it as a sidecar and shuts it down once the main containers finish. On older clusters the workaround was for the main container to signal the sidecar to exit."},{"question":"Can containers in a Pod use the same port?","answer":"No. They share a single network namespace, and therefore one IP address and one port space, exactly as two processes on the same host would. The second container to bind the port fails with "address already in use" and typically ends up in CrashLoopBackOff. Give each container a distinct port. This same shared namespace is what lets them reach each other over localhost with no service discovery at all."},{"question":"How do containers in a Pod share files?","answer":"Through a volume mounted into both, usually an emptyDir. It is created when the Pod is scheduled and deleted when the Pod is removed, which matches the shared lifecycle. Mount it at whatever path each container expects; they can be different paths. Always set a sizeLimit, because an emptyDir consumes node disk and a log file growing unchecked can fill the node and cause every Pod on it to be evicted."},{"question":"Should I put my application and database in the same Pod?","answer":"No. Containers in a Pod scale together, so three replicas means three databases with three separate datasets. They also deploy together, so every application release restarts the database. And they are scheduled together, so both compete for one node's resources. Run the database as a StatefulSet with its own storage and put a Service in front of it. The convenience of localhost is not worth losing independent scaling, independent deployment, and the ability to give each workload the resources it actually needs."}]},{"slug":"kubernetes-pods-probes","title":"Kubernetes Probes","seoTitle":"Kubernetes Liveness, Readiness and Startup Probes","description":"Configure Kubernetes liveness, readiness and startup probes. Learn when to use each, the HTTP, TCP and exec methods, timing fields and common failure modes.","content":"$3f","difficulty":"Medium","duration":"16 min","keywords":["kubernetes probes","liveness probe","readiness probe","startup probe","initialDelaySeconds","probe failure CrashLoopBackOff","health check kubernetes","httpGet exec tcpSocket"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":16,"tableOfContents":[{"id":"three-probes-three-different-questions","title":"Three probes, three different questions","level":2},{"id":"readiness-the-one-you-should-always-set","title":"Readiness: the one you should always set","level":2},{"id":"liveness-use-it-sparingly","title":"Liveness: use it sparingly","level":2},{"id":"startup-for-slow-boots","title":"Startup: for slow boots","level":2},{"id":"probe-methods","title":"Probe methods","level":2},{"id":"the-timing-fields","title":"The timing fields","level":2},{"id":"a-complete-example","title":"A complete example","level":2},{"id":"failure-modes-to-recognise","title":"Failure modes to recognise","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a liveness and a readiness probe?","answer":"A liveness probe answers "is this container stuck?" and a failure causes the kubelet to kill and restart it. A readiness probe answers "should this container receive traffic right now?" and a failure removes the Pod from its Service endpoints without restarting anything. Failing readiness is a normal, temporary state. A Pod warming up or briefly overloaded. Failing liveness is a statement that the process is unrecoverable. Use readiness liberally and liveness sparingly."},{"question":"Why is my Pod in CrashLoopBackOff with "Liveness probe failed"?","answer":"The probe is failing before the application is ready to answer it. Check kubectl describe pod for the exact message. A timeout usually means the app has not started or the endpoint is slow, while a 404 or connection refused usually means the wrong path or port. If the application genuinely takes a long time to boot, the fix is a startup probe rather than a larger initialDelaySeconds, because a startup probe protects the boot window without weakening detection afterwards. Test the endpoint from inside the Pod with kubectl exec -it <pod> -- wget -qO- localhost:8080/healthz."},{"question":"Do I need all three probes?","answer":"Readiness, essentially always. It is what makes rolling updates safe and keeps traffic off Pods that cannot serve. Liveness only when a restart would genuinely fix a condition you can detect, such as a deadlock; otherwise it adds restarts to a struggling system without helping. Startup only when the application takes long enough to boot that liveness would kill it first. A common and reasonable configuration is readiness alone, with a startup probe added for slow-starting services."},{"question":"Should my health check verify the database connection?","answer":"Not in a liveness probe. A shared dependency fails for every replica at once, so the entire fleet gets restarted simultaneously and a slow database becomes a complete outage caused by the health check itself. A readiness probe may check that the application is able to serve, including that its connection pool is initialised, since removing Pods from the Service is recoverable and does not restart anything. The safest pattern is a /healthz that checks only the process and a /ready that checks whether this instance can serve a request."},{"question":"What do initialDelaySeconds and failureThreshold actually control?","answer":"initialDelaySeconds is how long the kubelet waits before the first probe, periodSeconds how often it repeats, timeoutSeconds how long it waits for a response, and failureThreshold how many consecutive failures trigger the action. Time to detection is roughly periodSeconds à failureThreshold. The default timeoutSeconds of 1 is worth changing. An endpoint that occasionally takes just over a second under load will fail intermittently, and probes get slower precisely when the system is busiest."},{"question":"Why do my Pods get no traffic even though they are Running?","answer":"They are Running but not Ready, so they have been excluded from the Service's endpoints. kubectl get pods shows this in the READY column as 0/1, and kubectl describe pod gives the probe's failure message. Common causes are a readiness path that requires authentication and returns 401, a wrong port, or an initialDelaySeconds shorter than the application's real startup time. Confirm with kubectl get endpointslices -l kubernetes.io/service-name=<service>. An empty list means no Pod is currently ready."}]},{"slug":"kubernetes-deployments","title":"Kubernetes Deployments","seoTitle":"Kubernetes Deployments: ReplicaSets, Updates, Rollbacks","description":"Learn how a Kubernetes Deployment manages ReplicaSets and pods, how to write one, run rolling updates and rollbacks, and when to choose a StatefulSet instead.","content":"$40","difficulty":"Easy","duration":"16 min","keywords":["kubernetes deployment","replicaset","rolling update","kubectl rollout undo","deployment vs statefulset","maxSurge maxUnavailable","deployment stuck","progressDeadlineSeconds","kubectl rollout status"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":17,"tableOfContents":[{"id":"why-not-manage-pods-yourself","title":"Why not manage pods yourself?","level":2},{"id":"the-chain-deployment-replicaset-pods","title":"The chain: Deployment â ReplicaSet â Pods","level":2},{"id":"writing-a-deployment","title":"Writing a Deployment","level":2},{"id":"rolling-updates","title":"Rolling updates","level":2},{"id":"rollbacks","title":"Rollbacks","level":2},{"id":"when-a-rollout-will-not-finish","title":"When a rollout will not finish","level":2},{"id":"deployment-or-statefulset","title":"Deployment or StatefulSet?","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a Deployment and a ReplicaSet?","answer":"A ReplicaSet keeps a specified number of identical pods running, that is all it does. A Deployment manages ReplicaSets and adds everything around versioning: when you change the pod template it creates a new ReplicaSet, shifts replicas from old to new gradually, and keeps the previous ReplicaSets so you can roll back. You almost never create a ReplicaSet directly; you create a Deployment and it manages them for you."},{"question":"Why is my deployment stuck and not rolling out?","answer":"The rollout is waiting for new pods to become Ready and they never do. Ignore the Deployment and look at the pods: kubectl get pods -l app=<label> then kubectl describe pod <new-pod>. The usual causes are a readiness probe pointed at the wrong path or port, an initialDelaySeconds shorter than the application's real startup time, an image tag that does not exist, a crash at startup visible via kubectl logs <pod> --previous, or, with maxUnavailable: 0, no room in the cluster for the one extra surge pod."},{"question":"How do I roll back a bad deployment?","answer":"kubectl rollout undo deployment/<name> reverts to the previous revision, and --to-revision=N targets a specific one from kubectl rollout history. This works because old ReplicaSets are retained at zero replicas, so a rollback is really just scaling one of them back up. It is fast, but note that revisionHistoryLimit (default 10) bounds how far back you can go, and a rollback does not undo anything outside Kubernetes. A database migration applied by the bad release still needs handling separately."},{"question":"What do maxSurge and maxUnavailable actually do?","answer":"They bound the rollout. maxSurge is how many pods above your replica count may exist temporarily, and maxUnavailable is how many of your desired pods may be down at once. maxSurge: 1, maxUnavailable: 0 never dips below full capacity but needs room for one extra pod. maxSurge: 0, maxUnavailable: 1 uses no extra capacity but runs one pod short during the update. Both accept percentages, which is more useful at larger replica counts."},{"question":"How do I restart all pods in a deployment without changing the image?","answer":"kubectl rollout restart deployment/<name>. It sets a timestamp annotation on the pod template, which counts as a template change and triggers a normal rolling update, so it respects your surge and unavailability settings and causes no downtime. This is the correct way to pick up a changed ConfigMap or Secret mounted as environment variables, since those do not restart pods on their own."},{"question":"When should I use a StatefulSet instead?","answer":"When pod identity matters. StatefulSets give each pod a stable name and DNS entry that survives rescheduling, create a PersistentVolumeClaim per pod, and start and stop pods in order. That is what databases, message brokers and quorum-based systems need. A replica has to come back as the same member with the same data. If any replica can serve any request and keeps nothing on local disk, a Deployment is simpler and is the right choice."}]},{"slug":"kubernetes-deployments-create","title":"Creating Kubernetes Deployments","seoTitle":"How to Create a Kubernetes Deployment (kubectl and YAML)","description":"Create a Kubernetes Deployment with kubectl create deployment or a YAML manifest. Learn matchLabels, kubectl scale, inspecting objects and common errors.","content":"$41","difficulty":"Easy","duration":"14 min","keywords":["create kubernetes deployment","deployment yaml example","kubectl create deployment","deployment selector matchLabels","kubectl scale deployment","replicaset","selector is immutable"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":18,"tableOfContents":[{"id":"the-quickest-version","title":"The quickest version","level":2},{"id":"the-manifest","title":"The manifest","level":2},{"id":"what-it-actually-created","title":"What it actually created","level":2},{"id":"scaling","title":"Scaling","level":2},{"id":"inspecting","title":"Inspecting","level":2},{"id":"the-errors-you-will-hit","title":"The errors you will hit","level":2},{"id":"deleting","title":"Deleting","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I create a Kubernetes Deployment?","answer":"Write a manifest with apiVersion: apps/v1, kind: Deployment, a replica count, a selector, and a pod template, then run kubectl apply -f deployment.yaml. The fastest way to get a correct starting point is kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > deployment.yaml, which generates valid YAML without contacting the cluster. Edit that, commit it, and apply it. Managing real workloads through imperative commands leaves no record of why the cluster looks the way it does."},{"question":"Why does my Deployment fail with "selector does not match template labels"?","answer":"spec.selector.matchLabels and spec.template.metadata.labels must contain the same key-value pairs. The selector is how the Deployment recognises the Pods it owns, so a mismatch would mean it creates Pods it cannot then manage, and the API server rejects it. Make the two blocks identical. Note that the selector is immutable after creation, so if you need a different label scheme later you must delete and recreate the Deployment."},{"question":"What is the difference between a Deployment and a ReplicaSet?","answer":"A ReplicaSet keeps a fixed number of identical Pods running and does nothing else. A Deployment manages ReplicaSets and adds version handling: changing the pod template creates a new ReplicaSet, shifts replicas from the old one to the new gradually, and retains previous ReplicaSets at zero replicas so you can roll back. You create Deployments; the ReplicaSets are an implementation detail you mostly read rather than write."},{"question":"Why are my Deployment's Pods stuck at 0/3 ready?","answer":"The containers are running but failing their readiness probe, so they never become Ready. Run kubectl describe pod <name> and look at the events for the probe's failure message. The usual causes are a wrong path or port, an endpoint behind authentication returning 401, or an initialDelaySeconds shorter than the application's genuine startup time. For slow-starting applications, add a startup probe rather than inflating initialDelaySeconds, which would otherwise delay detection of real failures later."},{"question":"How do I scale a Deployment?","answer":"kubectl scale deployment/<name> --replicas=5 changes it immediately, and editing replicas in the manifest and reapplying does the same declaratively. Remember that imperative scaling is undone by the next kubectl apply if the file still specifies a different number. Where load varies, a HorizontalPodAutoscaler is the better answer, and when you adopt one, remove replicas from the manifest entirely so the autoscaler and your deployment tooling are not writing the same field."},{"question":"What do READY, UP-TO-DATE and AVAILABLE mean?","answer":"READY shows how many Pods are passing readiness checks against how many are desired. UP-TO-DATE counts Pods running the current pod template, which is how you see rollout progress. AVAILABLE counts Pods that have been ready for at least minReadySeconds, so it lags READY slightly when that is configured. During a rollout these diverge, 3/3 ready with only 1 up-to-date means the new version is still being rolled out."}]},{"slug":"kubernetes-deployments-rolling-updates","title":"Kubernetes Rolling Updates","seoTitle":"Kubernetes Rolling Updates: maxSurge and maxUnavailable","description":"Run zero-downtime rolling updates in Kubernetes. Tune maxSurge and maxUnavailable, use readiness probes and graceful shutdown, and fix stalled rollouts.","content":"$42","difficulty":"Medium","duration":"15 min","keywords":["kubernetes rolling update","maxSurge maxUnavailable","kubectl rollout status","zero downtime deployment kubernetes","progressDeadlineSeconds","recreate strategy","terminationGracePeriodSeconds","preStop hook"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":19,"tableOfContents":[{"id":"what-triggers-one","title":"What triggers one","level":2},{"id":"watching-it","title":"Watching it","level":2},{"id":"maxsurge-and-maxunavailable","title":"maxSurge and maxUnavailable","level":2},{"id":"the-readiness-probe-is-what-makes-it-safe","title":"The readiness probe is what makes it safe","level":2},{"id":"graceful-shutdown-is-the-other-half","title":"Graceful shutdown is the other half","level":2},{"id":"recreate-when-you-cannot-run-two-versions","title":"Recreate, when you cannot run two versions","level":2},{"id":"when-a-rollout-stalls","title":"When a rollout stalls","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do rolling updates work in Kubernetes?","answer":"Changing a Deployment's pod template creates a new ReplicaSet. The Deployment controller then scales the new ReplicaSet up and the old one down in steps, bounded by maxSurge and maxUnavailable, waiting for each new Pod to become Ready before removing an old one. The old ReplicaSet is retained at zero replicas so a rollback is just a matter of scaling it back up. Only template changes trigger this. Adjusting the replica count alone does not."},{"question":"What is the difference between maxSurge and maxUnavailable?","answer":"maxSurge is how many Pods may exist above the desired replica count during the update, and maxUnavailable is how many of the desired Pods may be unavailable at once. Both default to 25% and accept integers or percentages. maxSurge: 1, maxUnavailable: 0 never drops below full capacity but requires room for one extra Pod; maxSurge: 0, maxUnavailable: 1 needs no extra capacity but runs one Pod short throughout."},{"question":"How do I achieve genuinely zero-downtime deployments?","answer":"Four things together. Set maxUnavailable: 0 so capacity never dips. Define a readiness probe, without which Kubernetes treats a started process as ready and shifts traffic too early. Add a preStop hook that sleeps a few seconds, so endpoint removal propagates before the container gets SIGTERM. And handle SIGTERM in the application by refusing new connections while finishing in-flight ones, with terminationGracePeriodSeconds set above your longest normal request."},{"question":"Why did my rollout stop with "exceeded its progress deadline"?","answer":"The new Pods failed to become Ready within progressDeadlineSeconds, which defaults to 600. Kubernetes marks the Deployment failed and stops. It does not roll back automatically, so with maxUnavailable: 0 the old Pods are still serving and the application is up. Inspect the new Pods with kubectl describe pod: the usual causes are a failing readiness probe, an image that cannot be pulled, a crash at startup, or no cluster capacity for the surge Pod."},{"question":"When should I use the Recreate strategy?","answer":"Only when the application genuinely cannot tolerate two versions running simultaneously. Something holding an exclusive lock, a singleton background worker, or a release with a schema migration that the old version cannot work against. Recreate terminates every Pod before starting any new ones, so it causes downtime by design. It should be a deliberate decision about the application's constraints, never a way to simplify a deployment."},{"question":"How do I make Pods pick up a changed ConfigMap?","answer":"kubectl rollout restart deployment/<name>. It sets a timestamp annotation on the pod template, which counts as a template change and triggers a normal rolling update honouring your surge and unavailability settings, so there is no downtime. This is necessary because ConfigMap values consumed as environment variables are fixed when the process starts and can never update in place. Values mounted as volumes do refresh automatically, within about a minute, unless mounted with subPath, which never updates."}]},{"slug":"kubernetes-deployments-rollbacks","title":"Kubernetes Rollbacks","seoTitle":"kubectl rollout undo: Roll Back a Kubernetes Deployment","description":"Roll back a bad release with kubectl rollout undo. Learn revision history, change-cause annotations, revisionHistoryLimit and what a rollback cannot fix.","content":"$43","difficulty":"Easy","duration":"13 min","keywords":["kubectl rollout undo","kubernetes rollback deployment","rollout history","revisionHistoryLimit","kubernetes.io/change-cause","deployment revision","rollback database migration"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":20,"tableOfContents":[{"id":"revision-history","title":"Revision history","level":2},{"id":"record-why-not-just-what","title":"Record why, not just what","level":2},{"id":"rolling-back","title":"Rolling back","level":2},{"id":"how-far-back-you-can-go","title":"How far back you can go","level":2},{"id":"what-a-rollback-does-not-fix","title":"What a rollback does not fix","level":2},{"id":"rollback-or-roll-forward","title":"Rollback or roll forward?","level":2},{"id":"preventing-the-need","title":"Preventing the need","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I roll back a Kubernetes deployment?","answer":"kubectl rollout undo deployment/<name> reverts to the previous revision, and --to-revision=N targets a specific one listed by kubectl rollout history. Check what a revision contains first with kubectl rollout history deployment/<name> --revision=N. The rollback runs as a normal rolling update, so it honours maxSurge, maxUnavailable and readiness probes. Watch it complete with kubectl rollout status."},{"question":"Why is the CHANGE-CAUSE column empty?","answer":"Because nothing set it. It reads from the kubernetes.io/change-cause annotation, which Kubernetes does not populate automatically. Add it to the manifest's metadata, or run kubectl annotate deployment/<name> kubernetes.io/change-cause="..." --overwrite as part of your deploy. Having CI fill it from the commit message or release tag costs one line and turns an unusable list of revision numbers into something you can act on during an incident."},{"question":"How many revisions does Kubernetes keep?","answer":"Ten by default, controlled by revisionHistoryLimit. Older ReplicaSets beyond that are deleted and their revisions can no longer be rolled back to. The default is sensible for most teams; setting it to 0 disables rollback entirely, which sometimes appears in manifests intended to reduce clutter and quietly removes the fastest recovery path available. Retained ReplicaSets sit at zero replicas and consume no compute."},{"question":"Does rolling back undo database migrations?","answer":"No. A rollback changes which pod template is running and touches nothing else, no databases, no ConfigMaps, no Secrets, no external state. If the bad release applied a destructive migration, the older code will meet a schema it does not understand and may fail worse than before. The standard mitigation is expand-and-contract: ship schema changes that both the old and new versions tolerate, and only remove the old shape after the new version has proven itself."},{"question":"Should I roll back or roll forward?","answer":"Roll back when the previous version is known good and the failure is clear. It restores a known state quickly and buys time to investigate properly. Roll forward when the new release has already changed persistent state in a way the old version cannot handle, when the previous version has its own known problem, or when the fix is genuinely small and well understood. When genuinely unsure, roll back; debugging under production pressure is worse than the delay."},{"question":"Can I prevent bad deployments from reaching users at all?","answer":"Largely, yes. With a correct readiness probe and maxUnavailable: 0, a version that never becomes Ready never displaces the old Pods. The rollout stalls and fails after progressDeadlineSeconds while the application stays up. That converts a bad release into a failed deploy rather than an incident. Lowering progressDeadlineSeconds from its 600-second default makes the failure visible sooner, and kubectl diff -f before applying catches unintended changes."}]},{"slug":"kubernetes-services","title":"Kubernetes Services","seoTitle":"Kubernetes Services: ClusterIP vs NodePort vs LoadBalancer","description":"Learn how a Kubernetes Service finds pods with selectors, how port, targetPort and nodePort differ, how service DNS works, and which of the four types to pick.","content":"$44","difficulty":"Easy","duration":"16 min","keywords":["kubernetes services","clusterip nodeport loadbalancer","service selector","kubernetes service dns","endpointslice","service has no endpoints","port vs targetPort","headless service"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":21,"tableOfContents":[{"id":"how-a-service-finds-its-pods","title":"How a Service finds its pods","level":2},{"id":"port-targetport-nodeport","title":"port, targetPort, nodePort","level":2},{"id":"dns","title":"DNS","level":2},{"id":"the-four-types","title":"The four types","level":2},{"id":"clusterip-the-default","title":"ClusterIP: the default","level":3},{"id":"nodeport","title":"NodePort","level":3},{"id":"loadbalancer","title":"LoadBalancer","level":3},{"id":"externalname","title":"ExternalName","level":3},{"id":"headless-services","title":"Headless Services","level":2},{"id":"a-service-is-not-a-proxy-server","title":"A Service is not a proxy server","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between ClusterIP, NodePort and LoadBalancer?","answer":"They are layers on top of one another. ClusterIP gives a stable internal-only virtual IP and is the default. NodePort does everything ClusterIP does and additionally opens the same port, between 30000 and 32767, on every node so external traffic can reach it. LoadBalancer does everything NodePort does and additionally asks the cloud provider to create a real load balancer pointing at those node ports. Use ClusterIP for internal services, and prefer an Ingress over many LoadBalancer Services when exposing several applications publicly."},{"question":"Why does my Service have no endpoints?","answer":"Either no pod's labels match the Service selector, or matching pods are not passing their readiness probe. Compare them directly with kubectl get pods --show-labels and kubectl get svc <name> -o jsonpath='{.spec.selector}'. A mismatch as small as app: payments versus app: payments-api is enough. If the labels do match, check pod readiness, because only Ready pods are added to a Service. This is intentional, but it means a misconfigured readiness probe empties the Service without any obvious error."},{"question":"What is the difference between port and targetPort?","answer":"port is the port the Service itself listens on, so it is what clients inside the cluster connect to. targetPort is the port on the pod that traffic is forwarded to. With port: 80, targetPort: 8080, a client connects to my-service:80 and the container receives the request on 8080. They are often the same value, which is why the distinction only becomes visible when they differ. targetPort can also reference a named port from the pod spec, which is more readable and does not break if the number changes."},{"question":"How do I call a Service in another namespace?","answer":"Use the namespace in the DNS name: service-name.namespace or the fully qualified service-name.namespace.svc.cluster.local. The bare short name only resolves within the same namespace, because the cluster's DNS search path appends the caller's own namespace first. A request that works from one namespace and fails from another is almost always this."},{"question":"Why is my LoadBalancer Service stuck with EXTERNAL-IP pending?","answer":"Nothing is fulfilling the request. On a managed cloud cluster the cloud controller creates the load balancer, so check its logs and, on AWS, confirm your subnets carry the required role tags, since the controller discovers placement from them. On a local cluster such as kind or minikube, or on bare metal, there is no cloud provider at all and the field stays pending forever; install something like MetalLB, or use kubectl port-forward for local access."},{"question":"When should I use a headless Service?","answer":"When clients need the individual pod addresses rather than a single balanced endpoint. Setting clusterIP: None makes DNS return every ready pod IP instead of one virtual IP. StatefulSets use this so each replica is individually addressable, which database clients and quorum-based systems require. It is also the foundation for client-side load balancing, which is how you get even distribution for gRPC traffic that would otherwise pin to one pod."}]},{"slug":"kubernetes-services-clusterip","title":"Kubernetes Service: ClusterIP","seoTitle":"Kubernetes ClusterIP Service: Internal Access and DNS","description":"Learn how a Kubernetes ClusterIP Service works: virtual IPs, service DNS names, endpoints, testing with port-forward and headless Services.","content":"$45","difficulty":"Easy","duration":"12 min","keywords":["clusterip service","kubernetes internal service","service dns name","kube-proxy iptables","headless service","service no endpoints","port vs targetPort"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":22,"tableOfContents":[{"id":"creating-one","title":"Creating one","level":2},{"id":"the-virtual-ip-is-not-a-real-interface","title":"The virtual IP is not a real interface","level":2},{"id":"dns","title":"DNS","level":2},{"id":"endpoints-are-the-whole-debugging-story","title":"Endpoints are the whole debugging story","level":2},{"id":"testing-without-exposing-anything","title":"Testing without exposing anything","level":2},{"id":"headless-services","title":"Headless Services","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a ClusterIP Service?","answer":"It is the default Service type, giving a stable virtual IP and DNS name reachable only from inside the cluster, with load balancing across the Pods matching its selector. It is what you should use for anything internal, databases, caches, service-to-service APIs, because it provides a fixed address in front of Pods whose own IPs change constantly, without exposing anything externally."},{"question":"Why can't I ping a ClusterIP?","answer":"Because nothing is listening on it. kube-proxy programs iptables or IPVS rules that rewrite the destination of packets sent to the Service IP and port, and ICMP has no port for those rules to match, so ping never works regardless of whether the Service is healthy. Test with curl against the actual port instead, or kubectl port-forward service/<name> 8080:80 from a laptop."},{"question":"Why does my Service have no endpoints?","answer":"Either the selector matches no Pods, or the matching Pods are not Ready. Compare them directly with kubectl get svc <name> -o jsonpath='{.spec.selector}' and kubectl get pods --show-labels; Kubernetes never validates that a selector matches anything, so a small typo creates a perfectly healthy Service that goes nowhere. If the labels do match, check the READY column, only Ready Pods become endpoints, so a failing readiness probe empties the Service silently."},{"question":"How do I access a ClusterIP Service from my laptop?","answer":"kubectl port-forward service/<name> 8080:80 tunnels a local port through the API server to the Service. This is the right approach for debugging and for reaching internal tools, and it needs no change to the Service itself. Switching a Service to NodePort or LoadBalancer in order to test it exposes an internal service unnecessarily and is easy to forget to undo."},{"question":"What is a headless Service and when do I need one?","answer":"Setting clusterIP: None allocates no virtual IP, and DNS returns every ready Pod IP instead of one address. StatefulSets use this so each replica is individually addressable, which database clients and quorum-based systems need in order to discover cluster topology. It is also how you do client-side load balancing, which is the usual fix for gRPC connections that would otherwise pin all traffic to a single Pod."},{"question":"Can a ClusterIP Service be reached from outside the cluster?","answer":"Not directly, that is the point of the type. To expose something externally you use a NodePort Service, a LoadBalancer Service, or, most commonly for HTTP, an Ingress that routes to your ClusterIP Service by hostname and path. kubectl port-forward provides temporary access for debugging without changing anything, which is usually what you actually want."}]},{"slug":"kubernetes-services-nodeport","title":"Kubernetes Service: NodePort","seoTitle":"Kubernetes NodePort Service: Port Range and Example","description":"Learn how a Kubernetes NodePort Service works: the three ports, the 30000-32767 range, externalTrafficPolicy, why it is weak for production and debugging.","content":"$46","difficulty":"Easy","duration":"12 min","keywords":["nodeport service","kubernetes nodeport range","externalTrafficPolicy","nodeport vs loadbalancer","expose kubernetes service","nodeport source ip","minikube service"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":23,"tableOfContents":[{"id":"it-builds-on-clusterip","title":"It builds on ClusterIP","level":2},{"id":"the-three-ports-again","title":"The three ports, again","level":2},{"id":"why-the-port-range-is-what-it-is","title":"Why the port range is what it is","level":2},{"id":"externaltrafficpolicy-the-setting-that-matters","title":"externalTrafficPolicy: the setting that matters","level":2},{"id":"why-it-is-a-poor-production-choice","title":"Why it is a poor production choice","level":2},{"id":"debugging","title":"Debugging","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a NodePort Service and how does it work?","answer":"It exposes a Service on a static port, between 30000 and 32767, on every node in the cluster. Traffic arriving at any node's IP on that port is forwarded to the Service's Pods, even if that particular node runs none of them. A NodePort Service also includes a ClusterIP, so internal callers are unaffected. NodePort adds an external path rather than replacing the internal one."},{"question":"Why is the NodePort range limited to 30000-32767?","answer":"The range is chosen to avoid colliding with well-known service ports and with ports the node's own operating system may use. Exposing a Service on 80 or 443 directly would require changing the --service-node-port-range flag on the API server, which is not something to do casually. This restriction is a large part of why NodePort is unsuitable as a public entry point. Nobody wants to publish a URL with port 31847 in it."},{"question":"What is the difference between NodePort and LoadBalancer?","answer":"LoadBalancer builds on NodePort. It does everything NodePort does and additionally asks the cloud provider to create a real external load balancer that distributes traffic across the nodes and gives you a stable public address. NodePort leaves you to solve that yourself, which is fine on bare metal where you already run a load balancer and awkward on a cloud provider where LoadBalancer does it in one line."},{"question":"Should I use NodePort in production?","answer":"Usually not. Every node opens the port regardless of whether it runs the workload, the port range is unusable for a public URL, and you still need something in front to spread traffic across nodes and handle nodes coming and going. For public HTTP traffic use an Ingress, which gives you hostname and path routing plus TLS through a single load balancer. NodePort is genuinely useful for local development with minikube or kind, and on bare metal behind a load balancer you manage yourself."},{"question":"Why does my application see the node's IP instead of the client's?","answer":"Because externalTrafficPolicy defaults to Cluster, which allows a node to forward traffic to a Pod on another node, and that second hop requires source NAT, replacing the client address. Setting externalTrafficPolicy: Local keeps traffic on the receiving node and preserves the original source IP. The trade-off is that nodes with no matching Pod drop traffic rather than forwarding it, so whatever sits in front must health-check and only send traffic to nodes that have Pods."},{"question":"My NodePort Service works inside the cluster but not from outside. What is wrong?","answer":"If it works internally, the Service, its selector and its endpoints are all correct, so the problem is network reachability to the node. On a cloud provider the node's security group or firewall almost certainly does not allow inbound traffic on 30000 to 32767, which is the most frequent cause. Also confirm you are using an IP that is actually reachable from where you are testing. A private node IP will not work from outside the VPC."}]},{"slug":"kubernetes-services-loadbalancer","title":"Kubernetes Service: LoadBalancer","seoTitle":"Kubernetes LoadBalancer Service: EXTERNAL-IP Pending Fix","description":"Use a Kubernetes LoadBalancer Service, configure cloud annotations, restrict access, set externalTrafficPolicy, and fix an EXTERNAL-IP stuck on pending.","content":"$47","difficulty":"Medium","duration":"13 min","keywords":["loadbalancer service kubernetes","external-ip pending","service annotations aws nlb","metallb bare metal","loadbalancer vs ingress","externalTrafficPolicy local","loadBalancerSourceRanges"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":24,"tableOfContents":[{"id":"it-layers-on-nodeport-which-layers-on-clusterip","title":"It layers on NodePort, which layers on ClusterIP","level":2},{"id":"the-cost-problem","title":"The cost problem","level":2},{"id":"annotations-do-the-real-configuration","title":"Annotations do the real configuration","level":2},{"id":"restricting-who-can-connect","title":"Restricting who can connect","level":2},{"id":"externaltrafficpolicy","title":"externalTrafficPolicy","level":2},{"id":"external-ip-stuck-at-pending","title":"EXTERNAL-IP stuck at pending","level":2},{"id":"keeping-the-address-stable","title":"Keeping the address stable","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a LoadBalancer Service?","answer":"It is a Service type that asks the cloud provider to create an external load balancer routing traffic into your cluster, then reports its address in the Service's EXTERNAL-IP field. It includes everything NodePort and ClusterIP provide, so internal access continues to work unchanged. It is the simplest way to get a stable public endpoint for a Service, and it provisions real chargeable cloud infrastructure each time you create one."},{"question":"Why is my LoadBalancer Service stuck at pending?","answer":"Nothing is fulfilling the request. On kind, minikube or bare metal there is no cloud controller at all, so it will never resolve. Install MetalLB, run minikube tunnel, or use kubectl port-forward for local access. On a cloud cluster, check the Service events with kubectl describe svc and the cloud controller manager logs; the usual causes are missing subnet role tags on AWS, insufficient IAM permissions, or having hit the account's load balancer quota for that region."},{"question":"What is the difference between LoadBalancer and Ingress?","answer":"A LoadBalancer Service creates one cloud load balancer per Service and routes on port alone. An Ingress uses a single load balancer, fronting an Ingress controller that routes HTTP traffic to many Services by hostname and path and terminates TLS centrally. For more than one or two public HTTP services, Ingress is substantially cheaper and easier to manage. LoadBalancer remains the right choice for non-HTTP protocols and for exposing the Ingress controller itself."},{"question":"How do I make a LoadBalancer internal rather than public?","answer":"Through a provider-specific annotation: service.beta.kubernetes.io/aws-load-balancer-scheme: "internal" on AWS, networking.gke.io/load-balancer-type: "Internal" on GCP, and service.beta.kubernetes.io/azure-load-balancer-internal: "true" on Azure. Omitting it gives you a public address, which is a genuinely common way to expose an internal API to the internet by accident. These annotations are not portable and are ignored without warning on a different provider."},{"question":"Why does my application see node IPs instead of client IPs?","answer":"externalTrafficPolicy defaults to Cluster, which lets any node forward traffic to a Pod on another node and source-NATs the packet in the process. Set externalTrafficPolicy: Local to keep traffic on the receiving node and preserve the client address. On a LoadBalancer Service this is relatively safe, because the cloud load balancer health-checks each node and stops sending traffic to those without a matching Pod. The same setting on a bare NodePort has no such protection."},{"question":"Can I keep the same IP address if the Service is recreated?","answer":"Some providers allow requesting a pre-allocated static address via spec.loadBalancerIP, though support and syntax vary and several clouds have moved to annotations instead. The more durable approach is ExternalDNS, which watches Services and Ingresses and updates your DNS provider automatically whenever an address changes. That way recreation is handled rather than avoided, which also covers cluster rebuilds and failovers."}]},{"slug":"kubernetes-ingress","title":"Kubernetes Ingress","seoTitle":"Kubernetes Ingress Explained: Controller, Rules and TLS","description":"Learn how a Kubernetes Ingress works with an ingress controller, write a working manifest with pathType and TLS, and debug routing problems step by step.","content":"$48","difficulty":"Medium","duration":"15 min","keywords":["kubernetes ingress","ingress controller","nginx ingress","ingressClassName","pathType prefix","ingress tls certificate","cert-manager","ingress 404 default backend","gateway api"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":25,"tableOfContents":[{"id":"an-ingress-on-its-own-does-nothing","title":"An Ingress on its own does nothing","level":2},{"id":"a-working-ingress","title":"A working Ingress","level":2},{"id":"pathtype-matters-more-than-it-looks","title":"pathType matters more than it looks","level":2},{"id":"tls","title":"TLS","level":2},{"id":"annotations-are-controller-specific","title":"Annotations are controller-specific","level":2},{"id":"debugging","title":"Debugging","level":2},{"id":"ingress-and-the-gateway-api","title":"Ingress and the Gateway API","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between an Ingress and a Service?","answer":"A Service provides stable networking to a set of pods and routes on port alone. It has no understanding of HTTP. An Ingress sits in front of Services and routes HTTP traffic based on hostname and URL path, terminates TLS, and lets many applications share one entry point. They work together: the Ingress decides which Service should receive a request, and the Service decides which pod. You still need a Service for every backend an Ingress references."},{"question":"Why is my Ingress returning 404?","answer":"No rule matched the request. Check three things in order. First, the Host header, if you are testing against the Ingress IP directly, no hostname is sent and a rule with host: api.example.com cannot match; use curl -H "Host: api.example.com". Second, the path and its pathType, Exact on /api will not match /api/users. Third, the namespace, because the backend Service must be in the same namespace as the Ingress. kubectl describe ingress and the controller logs usually name the problem outright."},{"question":"Do I need an Ingress controller, and which should I choose?","answer":"Yes. An Ingress resource is inert configuration and Kubernetes ships no controller. ingress-nginx is the most widely used and the safest default, with the largest body of documentation and examples. Traefik is popular where dynamic configuration and a built-in dashboard matter. On AWS, the AWS Load Balancer Controller is often the better fit because it provisions a real ALB instead of running a proxy inside the cluster, which moves TLS termination and scaling to managed infrastructure."},{"question":"How do I add HTTPS to an Ingress?","answer":"Reference a Secret of type kubernetes.io/tls in the spec.tls block, in the same namespace as the Ingress. Rather than creating and rotating that Secret yourself, install cert-manager and add a cert-manager.io/cluster-issuer annotation; it will obtain a certificate from Let's Encrypt, write the Secret, and renew it automatically before expiry. If a certificate never becomes ready, the HTTP-01 challenge usually cannot reach the cluster. Verify DNS points at the Ingress address and no rule is intercepting the challenge path."},{"question":"Why do the annotations from a tutorial have no effect?","answer":"They were almost certainly written for a different Ingress controller. Only hosts, paths and TLS are part of the Ingress specification itself; rewrites, timeouts, body size limits, rate limiting and authentication are all controller-specific annotations. An nginx.ingress.kubernetes.io/ annotation is ignored entirely by Traefik or the AWS Load Balancer Controller, with no error to tell you. Check which controller you are running with kubectl get ingressclass and use its own documentation."},{"question":"Should I use the Gateway API instead of Ingress?","answer":"For a new platform, it is worth serious consideration. The Gateway API separates the cluster operator's concerns from the application team's, and brings header-based matching, traffic splitting and cross-namespace routing into the specification rather than leaving them to vendor annotations. For existing clusters, Ingress remains well supported, has far more ecosystem tooling and examples, and is not being removed. Migrating is a project rather than a quick win, so most teams adopt the Gateway API for new workloads first."}]},{"slug":"kubernetes-configmaps-secrets","title":"ConfigMaps and Secrets","seoTitle":"ConfigMap vs Secret in Kubernetes: When to Use Each","description":"Learn when to use a Kubernetes ConfigMap vs a Secret, how to consume them as env vars or files, why base64 is not encryption, and where real secrets belong.","content":"$49","difficulty":"Easy","duration":"15 min","keywords":["configmap vs secret","kubernetes configuration","externalize config kubernetes","secret base64 not encryption","configmap volume mount update","immutable configmap","envFrom","external secrets operator"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":26,"tableOfContents":[{"id":"why-configuration-does-not-belong-in-the-image","title":"Why configuration does not belong in the image","level":2},{"id":"configmap-or-secret","title":"ConfigMap or Secret?","level":2},{"id":"two-ways-to-consume-them-and-one-important-difference","title":"Two ways to consume them, and one important difference","level":2},{"id":"immutable-configmaps-and-secrets","title":"Immutable ConfigMaps and Secrets","level":2},{"id":"where-real-secret-management-lives","title":"Where real secret management lives","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a ConfigMap and a Secret?","answer":"Functionally they are near-identical key-value objects, both capped at 1 MiB and both consumable as environment variables or mounted files. The differences are that Secrets are a separate RBAC resource so access can be granted independently, their values are base64-encoded and omitted from kubectl describe, they can be encrypted at rest in etcd if the cluster is configured for it, and mounted Secret volumes are backed by tmpfs rather than node disk. Use ConfigMaps for ordinary settings and Secrets for anything sensitive, but do not mistake a Secret for encryption."},{"question":"Why doesn't my pod pick up an updated ConfigMap?","answer":"Because it consumes the value as an environment variable. A process gets its environment once at start-up and it cannot be changed afterwards, so the pod keeps the old value until it is replaced. ConfigMaps mounted as volumes do update in place, typically within a minute. To apply an environment variable change, run kubectl rollout restart deployment/<name>, which triggers a normal rolling update with no downtime. Note that volumes mounted with subPath also never update, which is a separate and easily missed cause of the same symptom."},{"question":"Are Kubernetes Secrets encrypted?","answer":"Not by default in the sense people usually mean. Values are base64-encoded in the API, which is trivially reversible. Kubernetes can encrypt Secrets at rest in etcd, but that must be configured explicitly, on managed clusters it generally means enabling envelope encryption with a cloud KMS key. Even then, anyone with get secrets permission in the namespace reads the plaintext through the API. RBAC is the real control, and for high-value credentials an external manager such as Vault or AWS Secrets Manager, synced in via the External Secrets Operator, is the right answer."},{"question":"How do I store secrets in Git safely?","answer":"Never commit a Secret manifest containing real base64 data. It is not obfuscated and Git history is permanent. The two established approaches are Sealed Secrets, which encrypts a Secret with a cluster-held key so the encrypted file is safe to commit and only the in-cluster controller can decrypt it, and the External Secrets Operator, where Git holds only a reference to a secret stored in AWS Secrets Manager, Parameter Store or Vault. Both keep plaintext out of the repository while staying compatible with GitOps."},{"question":"Is there a size limit on ConfigMaps?","answer":"Yes, 1 MiB, imposed by etcd. It is more than enough for configuration and too little for data. If you are approaching it, the content probably belongs somewhere else: a file fetched at start-up from object storage, a volume, or an init container that materialises it. Very large ConfigMaps also put real pressure on the API server, since every kubelet consuming one watches it for changes unless it is marked immutable."},{"question":"Should I use environment variables or mounted files?","answer":"Mounted files when the value may change while the pod runs, when the application expects a config file on disk, or when the value is large or structured. Environment variables when the value is small, static for the pod's lifetime, and the application reads its configuration from the environment. A practical default is environment variables for simple scalars and volumes for anything file-shaped, while remembering that only volumes refresh without a restart."}]},{"slug":"kubernetes-configmaps","title":"Kubernetes ConfigMaps","seoTitle":"Kubernetes ConfigMap: Create and Use in Pods (Examples)","description":"Create a Kubernetes ConfigMap with kubectl or YAML, use it as env vars with envFrom or as mounted files, and find out why a ConfigMap change did not apply.","content":"$4a","difficulty":"Easy","duration":"20 min","keywords":["kubernetes configmap","kubectl create configmap","envFrom configmap","configMapKeyRef","configmap volume mount","configmap not updating","immutable configmap","configmap from file"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":27,"tableOfContents":[{"id":"creating-a-configmap","title":"Creating a ConfigMap","level":2},{"id":"from-literal-values","title":"From literal values","level":3},{"id":"from-a-file","title":"From a file","level":3},{"id":"from-yaml","title":"From YAML","level":3},{"id":"consuming-as-environment-variables","title":"Consuming as environment variables","level":2},{"id":"every-key-at-once-with-envfrom","title":"Every key at once with /.zshrc for zsh, or the bash equivalent. It completes resource names as well as subcommands, so you stop copying and pasting Pod names. If you also alias k=kubectl, add complete -o default -F start_kubectl k, without that line completion works for kubectl but not through the alias, which is the step most people miss."},{"question":"What kubectl plugins are worth installing?","answer":"Install krew first, then ctx and ns for fast context and namespace switching, tree for viewing resource ownership hierarchies, and neat for stripping managed fields and status out of get -o yaml so the output is readable. Outside krew, stern handles multi-Pod log tailing far better than kubectl logs -l, and k9s gives you a terminal UI that makes browsing a cluster much quicker than repeated get and describe commands."}]},{"slug":"kubectl-basics","title":"kubectl Basics","seoTitle":"kubectl Commands Cheat Sheet: Essential Basics","description":"Learn the kubectl commands you will run daily: get, apply, describe, delete, selectors, output formats, namespaces and tips to work faster with kubeconfig.","content":"$36","difficulty":"Easy","duration":"15 min","keywords":["kubectl basics","kubectl commands","kubectl apply vs create","kubectl cheat sheet","kubeconfig context","kubectl output jsonpath","imperative vs declarative kubernetes","kubectl dry-run"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":7,"tableOfContents":[{"id":"where-it-gets-its-configuration","title":"Where it gets its configuration","level":2},{"id":"imperative-and-declarative","title":"Imperative and declarative","level":2},{"id":"apply-vs-create-vs-replace","title":"apply vs create vs replace","level":2},{"id":"the-commands-you-will-actually-run","title":"The commands you will actually run","level":2},{"id":"selecting-and-filtering","title":"Selecting and filtering","level":2},{"id":"output-formats","title":"Output formats","level":2},{"id":"namespaces","title":"Namespaces","level":2},{"id":"making-it-fast","title":"Making it fast","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between kubectl apply and kubectl create?","answer":"create maps to an HTTP POST and fails if the object already exists, so it is for one-off creation only. apply creates the object if absent and updates it if present, using a three-way merge between the last applied configuration, your file and the live object. That merge is what lets it change one field without discarding values set by controllers, and it is what makes running the same command repeatedly safe. Use apply with manifests in version control; use create mainly with --dry-run=client -o yaml to generate those manifests."},{"question":"How do I see why a Pod is failing?","answer":"Start with kubectl describe pod <name> and read the Events section at the bottom, which usually states the cause directly. Failed scheduling with the reason, an image pull error with the registry's own message, or a failing probe. If the container is crash-looping, kubectl logs <name> --previous shows output from the run that actually failed; plain kubectl logs shows the instance that has only just started and typically has nothing yet. For the termination reason specifically, kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' returns values like OOMKilled."},{"question":"How do I switch between clusters and namespaces?","answer":"kubectl config get-contexts lists available contexts and kubectl config use-context <name> switches cluster. For namespaces, kubectl config set-context --current --namespace=<ns> changes the default so you stop typing -n on every command. Because nothing in the output tells you which cluster you are on, showing the current context in your shell prompt, or using kubectx and kubens, is a worthwhile safeguard against running something destructive in the wrong place."},{"question":"How do I generate YAML without writing it by hand?","answer":"Use an imperative command with --dry-run=client -o yaml, for example kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > deployment.yaml. Nothing is sent to the cluster and you get a valid manifest to edit and commit. The same trick works for Services, ConfigMaps, Secrets and Jobs, and it is far quicker than recalling the schema. kubectl explain <resource>.<field> documents any field against the version of Kubernetes you are actually running."},{"question":"Why does kubectl get pods show nothing when my app is running?","answer":"Almost always the namespace. kubectl operates on one namespace at a time, defaulting to the one in your current context, so a workload in payments is invisible from default. Run kubectl get pods -A to search everywhere. The other possibility is that you are connected to a different cluster than you think, kubectl config current-context settles that immediately."},{"question":"What does kubectl diff do?","answer":"kubectl diff -f manifest.yaml shows what applying that file would change, comparing your manifest against the live objects in the cluster and printing a unified diff. It is the closest equivalent to terraform plan and is worth running before any change to a production namespace. Pair it with kubectl apply --dry-run=server, which validates against the real API server including admission controllers and catches problems that client-side validation cannot see."}]},{"slug":"kubectl-get-describe","title":"kubectl get and describe","seoTitle":"kubectl get and describe: Inspect Kubernetes Resources","description":"Inspect cluster resources with kubectl get and describe. Use -o wide, jsonpath and custom-columns, read events, and know when to choose get over describe.","content":"$37","difficulty":"Easy","duration":"13 min","keywords":["kubectl get","kubectl describe","kubectl get -o wide","kubectl jsonpath","kubectl custom-columns","kubectl field-selector","kubernetes events","kubectl get all"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":8,"tableOfContents":[{"id":"get","title":"get","level":2},{"id":"wider-output","title":"Wider output","level":3},{"id":"watching","title":"Watching","level":3},{"id":"selecting","title":"Selecting","level":3},{"id":"sorting","title":"Sorting","level":3},{"id":"output-formats","title":"Output formats","level":2},{"id":"jsonpath","title":"jsonpath","level":3},{"id":"custom-columns","title":"custom-columns","level":3},{"id":"describe","title":"describe","level":2},{"id":"events-on-their-own","title":"Events on their own","level":2},{"id":"get-or-describe","title":"get or describe?","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between kubectl get and kubectl describe?","answer":"get lists resources with a short status summary and works across many objects at once, which makes it the right tool for finding things and for scripting. describe renders one object in full. Its spec, its defaults, related objects, and crucially the events attached to it. Events are where Kubernetes explains itself, so describe is what you run when something is broken and get is what you run to find out what exists."},{"question":"How do I see why a Pod is failing?","answer":"kubectl describe pod <name> and read the Events section at the bottom. It usually states the cause outright: FailedScheduling with the per-node reasons, an image pull error quoting the registry's own message, Unhealthy with the probe's status code, or a FailedMount for a volume problem. Only if the events do not explain it do you need kubectl logs <pod> --previous, which shows output from the container run that actually crashed."},{"question":"What does READY 0/1 mean?","answer":"Zero of one containers are passing their readiness probe. If the Pod's status is also Running, the process started fine and the probe itself is failing. A wrong path or port, an endpoint behind authentication returning 401, or an initialDelaySeconds shorter than real startup time. A Pod in this state is deliberately excluded from its Service's endpoints, so it receives no traffic, which is exactly what readiness is for."},{"question":"How do I get just one field from a resource?","answer":"Use jsonpath: kubectl get pod <name> -o jsonpath='{.status.podIP}'. It is particularly useful for values buried in status, such as kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' which returns OOMKilled for a container killed on memory. For tabular output across many objects, -o custom-columns lets you build exactly the table you want rather than scrolling through YAML."},{"question":"Why are my events missing?","answer":"They expire. Kubernetes keeps events for about an hour by default, set by the API server's --event-ttl, because they are high volume and treated as transient. Anything older than that is gone, which routinely means the event explaining an overnight failure has vanished before anyone looks. Exporting events into your log store, the Kubernetes Event Exporter is the usual tool, preserves them, and it is not something the common monitoring stacks do for you."},{"question":"Does kubectl get all really show everything?","answer":"No, and the name is misleading. It covers the common workload and networking types. Pods, Services, Deployments, ReplicaSets, StatefulSets, DaemonSets and Jobs, but omits ConfigMaps, Secrets, PersistentVolumeClaims, Ingresses, ServiceAccounts and every custom resource. To see genuinely everything in a namespace you need to enumerate the API resources and query each, which kubectl api-resources supports but is rarely worth doing by hand."}]},{"slug":"kubectl-apply-delete","title":"kubectl apply and delete","seoTitle":"kubectl apply vs create: Apply and Delete Resources","description":"Learn kubectl apply vs create vs replace, preview changes with kubectl diff, use server-side apply, delete resources safely and fix objects stuck deleting.","content":"$38","difficulty":"Easy","duration":"13 min","keywords":["kubectl apply","kubectl delete","apply vs create","server-side apply","kubectl diff","cascade delete kubernetes","kubectl apply prune","stuck terminating pod"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":9,"tableOfContents":[{"id":"apply","title":"apply","level":2},{"id":"apply-vs-create-vs-replace","title":"apply vs create vs replace","level":2},{"id":"preview-before-applying","title":"Preview before applying","level":2},{"id":"server-side-apply","title":"Server-side apply","level":2},{"id":"delete","title":"delete","level":2},{"id":"deletion-cascades","title":"Deletion cascades","level":3},{"id":"deleting-a-namespace","title":"Deleting a namespace","level":3},{"id":"when-something-will-not-delete","title":"When something will not delete","level":2},{"id":"pruning","title":"Pruning","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between kubectl apply and kubectl create?","answer":"create maps to an HTTP POST and fails if the object already exists, so it only handles first creation. apply creates the object when absent and updates it when present, using a three-way merge between the last applied configuration, your file, and the live object. That merge lets it update your fields without discarding values set by controllers, and makes running the same command repeatedly safe. Use apply for managed manifests and create mainly with --dry-run=client -o yaml to generate them."},{"question":"What does kubectl diff do?","answer":"kubectl diff -f manifest.yaml computes what applying that file would change and prints a unified diff against the live objects, without modifying anything. It is the nearest equivalent to terraform plan and worth running before any change to a production namespace. Pair it with kubectl apply --dry-run=server, which sends the request to the API server for full validation including admission controllers but does not persist it."},{"question":"What is server-side apply and should I use it?","answer":"With server-side apply the API server records which manager owns each field, so two controllers writing the same field produce an explicit conflict instead of silently overwriting each other. A message like conflict with "hpa-controller": .spec.replicas is telling you that an autoscaler owns the replica count and your manifest should not declare it. It is the more robust mechanism and is worth adopting, particularly where several controllers touch the same objects. --force-conflicts overrides ownership and should be a considered decision."},{"question":"Does deleting a Deployment delete its Pods?","answer":"Yes. Owner references make deletion cascade from Deployment to ReplicaSets to Pods. --cascade=orphan keeps the children, which is occasionally useful when migrating workloads between controllers but leaves Pods that nothing manages. Note the reverse is not true: deleting a Pod that belongs to a Deployment simply causes the controller to create a replacement, which is exactly how you restart a single Pod."},{"question":"Why is my Pod stuck in Terminating?","answer":"Almost always a finalizer, and for Pods the usual cause is an unreachable node. The API server will not remove the Pod until the kubelet confirms the containers stopped, and a node that is down never will. kubectl delete pod <name> --grace-period=0 --force removes it from the API. Be careful with StatefulSet Pods: if the node is merely partitioned rather than dead, the container is still running with its volume attached, and a replacement Pod writing the same data can corrupt it."},{"question":"What does --prune do and is it safe?","answer":"kubectl apply -f ./manifests/ --prune -l app=myapp deletes objects matching the label selector that are no longer present in the applied manifests, which is what makes apply fully declarative rather than additive. It is genuinely useful and genuinely dangerous, because a wrong or overly broad selector deletes more than intended. Most teams get the same behaviour from ArgoCD or Flux, where pruning is scoped to an application and previewed before it runs."}]},{"slug":"kubectl-exec-logs","title":"kubectl exec, logs, and port-forward","seoTitle":"kubectl logs, exec and port-forward: Debug Your Pods","description":"Debug pods with kubectl logs (including --previous), kubectl exec, port-forward and kubectl cp. Handle distroless images with kubectl debug.","content":"$39","difficulty":"Easy","duration":"14 min","keywords":["kubectl logs","kubectl logs previous","kubectl exec","kubectl port-forward","kubectl debug","ephemeral container","distroless debug","kubectl cp"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":10,"tableOfContents":[{"id":"logs","title":"logs","level":2},{"id":"the-flag-that-matters-most","title":"The flag that matters most","level":3},{"id":"multiple-containers","title":"Multiple containers","level":3},{"id":"across-pods","title":"Across Pods","level":3},{"id":"what-logs-are-and-are-not","title":"What logs are and are not","level":3},{"id":"exec","title":"exec","level":2},{"id":"distroless-images-have-no-shell","title":"Distroless images have no shell","level":2},{"id":"port-forward","title":"port-forward","level":2},{"id":"copying-files","title":"Copying files","level":2},{"id":"a-debugging-order-that-works","title":"A debugging order that works","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does kubectl logs show nothing for a crashing Pod?","answer":"Because it reads the container that is running now, and during a CrashLoopBackOff that instance has only just started or has not started at all. The output you need belongs to the previous, terminated instance: kubectl logs <pod> --previous. Pair it with kubectl describe pod, whose events often name the cause outright, and note that --previous only reaches back one container generation. Earlier runs are gone unless you ship logs off the node."},{"question":"How do I get a shell inside a running Pod?","answer":"kubectl exec -it <pod> -- sh, adding -c <container> when the Pod has more than one. Use sh rather than bash, since most slim images do not include bash. The -- separator is required so that everything after it is passed to the container instead of being interpreted by kubectl. For a Pod with several containers, kubectl will not guess and returns an error until you name one."},{"question":"How do I debug a container with no shell?","answer":"Use kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>. This attaches an ephemeral container to the running Pod that shares its network namespace, and with --target its process namespace, giving you a full toolkit against the live workload without modifying the image or restarting anything. It is the standard answer for distroless and scratch images, and the Pod returns to normal when you exit."},{"question":"What does kubectl port-forward do?","answer":"It opens a tunnel from a local port, through the API server, to a port on a Pod or Service in the cluster, so kubectl port-forward service/payments-api 8080:80 makes the Service reachable at localhost:8080. This is the correct way to reach an internal service or database from a laptop without creating an Ingress or changing the Service type. Forwarding to a Service selects a single Pod and is not load balanced, and the connection drops when that Pod is replaced."},{"question":"Why don't my application's logs appear in kubectl logs?","answer":"Because the application writes to a file inside the container rather than to stdout or stderr. The container runtime only captures those two streams, and that is all kubectl logs can read. The fix is to configure the application to log to stdout, ideally as structured JSON, which also makes logs collectable by a shipper such as Promtail or Fluent Bit. Writing to files inside containers means losing them entirely when the Pod is removed."},{"question":"Can I view logs from all Pods in a Deployment at once?","answer":"Yes, with a label selector: kubectl logs -l app=payments-api --tail=50 --prefix. The --prefix flag labels each line with its Pod name, which is necessary to make sense of interleaved output. Following multiple Pods is capped by --max-log-requests, five by default. For sustained multi-Pod log watching, stern handles it considerably better, with colouring per Pod and automatic pickup of new Pods as they appear."}]},{"slug":"kubectl-contexts-namespaces","title":"kubectl contexts and namespaces","seoTitle":"kubectl config use-context: Switch Contexts and Namespaces","description":"Learn how kubeconfig works, switch clusters with kubectl config use-context, set a default namespace, merge kubeconfig files and avoid the wrong cluster.","content":"$3a","difficulty":"Easy","duration":"13 min","keywords":["kubectl context","kubeconfig","kubectl config use-context","kubectl set namespace","kubectx kubens","KUBECONFIG merge","multiple kubernetes clusters","kubectl current-context"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":11,"tableOfContents":[{"id":"how-kubeconfig-is-structured","title":"How kubeconfig is structured","level":2},{"id":"everyday-commands","title":"Everyday commands","level":2},{"id":"namespaces","title":"Namespaces","level":2},{"id":"several-kubeconfig-files","title":"Several kubeconfig files","level":2},{"id":"not-losing-track-of-where-you-are","title":"Not losing track of where you are","level":2},{"id":"contexts-in-ci","title":"Contexts in CI","level":2},{"id":"checking-what-you-can-actually-do","title":"Checking what you can actually do","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a kubectl context?","answer":"A named combination of a cluster, a user credential and a default namespace, stored in your kubeconfig. Switching context with kubectl config use-context <name> changes all three at once, which is how you move between clusters without editing configuration. kubectl config get-contexts lists them with the current one marked, and kubectl config current-context prints just the active name."},{"question":"How do I switch between Kubernetes clusters?","answer":"kubectl config use-context <name> changes the active context. For a single command elsewhere, kubectl --context=prod get pods avoids changing global state at all, which is safer and is what scripts should do. kubectx makes interactive switching much quicker, with fuzzy matching and kubectx - to jump back to the previous context."},{"question":"How do I change the default namespace?","answer":"kubectl config set-context --current --namespace=payments sets it for the current context, so you stop passing -n on every command. Because the namespace is part of the context, changing it on staging leaves prod untouched. kubens does the same thing interactively if you have kubectx installed."},{"question":"How do I use multiple kubeconfig files?","answer":"Set KUBECONFIG to a colon-separated list, export KUBECONFIG=/.kube/config:envFrom","level":3},{"id":"a-single-key-with-valuefrom","title":"A single key with valueFrom","level":3},{"id":"consuming-as-mounted-files","title":"Consuming as mounted files","level":2},{"id":"why-your-configmap-change-did-not-take-effect","title":"Why your ConfigMap change did not take effect","level":2},{"id":"inspecting","title":"Inspecting","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I create a ConfigMap from a file?","answer":"kubectl create configmap <name> --from-file=path/to/file creates one key named after the file, with the whole file as its value. Pointing --from-file at a directory makes a key per file in it. Use --from-file=wanted-name=path/to/file to control the key name, and --from-env-file=.env to turn a KEY=VALUE file into separate keys. For anything you intend to keep, generate the YAML with --dry-run=client -o yaml and commit that instead of running the imperative command against the cluster."},{"question":"Why is my pod not seeing updated ConfigMap values?","answer":"There are three possibilities. If the value is injected as an environment variable, it can never update in a running process. Restart with kubectl rollout restart deployment/<name>. If the volume uses subPath, it is mounted once and never refreshed; mount the volume normally and use items to set the filename. If it is a normal volume mount, updates do arrive but take up to roughly a minute, and your application still has to reread the file. Many need a signal or a restart to notice."},{"question":"What is the difference between envFrom and valueFrom?","answer":"envFrom with configMapRef imports every key in the ConfigMap as an environment variable, which is concise but hides what the application actually depends on and silently skips keys that are not valid variable names. valueFrom with configMapKeyRef pulls one named key and lets you give it a different variable name. Prefer valueFrom for anything important, because the pod spec then documents the contract between the application and its configuration."},{"question":"Can I store a whole configuration file in a ConfigMap?","answer":"Yes, and it is a common pattern. Use a YAML block scalar so newlines survive, then mount the ConfigMap as a volume so the key appears as a file. Be aware that mounting a volume at a directory replaces that directory's existing contents from the image. Use the items field to project only the keys you want, with the filenames you want. The 1 MiB ConfigMap limit applies, which is ample for configuration but not for data files."},{"question":"Is there a size limit on a ConfigMap?","answer":"1 MiB, imposed by etcd, covering all keys and values together. Large ConfigMaps are also a real load on the API server, because every kubelet consuming one watches it for changes. If you are close to the limit the content probably is not configuration. Fetch it from object storage at start-up, bake it into the image, or use a volume. Marking large ConfigMaps immutable: true removes the watch overhead entirely."},{"question":"Should I use immutable: true?","answer":"Usually yes, paired with a versioned name such as app-config-v4. It makes accidental in-place edits of production configuration impossible, and it lets the kubelet stop watching the object, which noticeably reduces API server load in large clusters. The trade-off is that a change means creating a new ConfigMap and updating the Deployment to reference it, which is arguably the point, since the config change then appears in your rollout history and rolls back like any other change."}]},{"slug":"kubernetes-secrets","title":"Kubernetes Secrets","seoTitle":"Kubernetes Secrets: Create, Use and Secure Them","description":"Create Kubernetes Secrets, mount them as env vars or files, use imagePullSecrets and TLS secrets, and learn why base64 is not encryption.","content":"$4b","difficulty":"Easy","duration":"22 min","keywords":["kubernetes secrets","opaque secret","imagePullSecrets","docker-registry secret","kubernetes.io/tls secret","base64 not encryption","encryption at rest etcd","external secrets operator","stringData"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":28,"tableOfContents":[{"id":"base64-is-not-encryption","title":"Base64 is not encryption","level":2},{"id":"creating-secrets","title":"Creating Secrets","level":2},{"id":"generic-opaque","title":"Generic (Opaque)","level":3},{"id":"registry-credentials","title":"Registry credentials","level":3},{"id":"tls","title":"TLS","level":3},{"id":"consuming-secrets","title":"Consuming Secrets","level":2},{"id":"as-environment-variables","title":"As environment variables","level":3},{"id":"as-mounted-files","title":"As mounted files","level":3},{"id":"production-keep-the-source-of-truth-outside-the-cluster","title":"Production: keep the source of truth outside the cluster","level":2},{"id":"practical-hygiene","title":"Practical hygiene","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Are Kubernetes Secrets encrypted?","answer":"Not by default in the way the name implies. Values are base64-encoded in the API, which is trivially reversible with base64 -d. Kubernetes can encrypt Secrets at rest in etcd through an EncryptionConfiguration, and managed clusters generally expose this as envelope encryption with a cloud KMS key, but it must be enabled, and it protects the datastore on disk rather than API reads. An authorised caller always sees plaintext. RBAC is the control that actually restricts access."},{"question":"How do I decode a Kubernetes Secret?","answer":"kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d. The fact that this is a one-liner is the point: base64 is an encoding for safe transport of binary data in JSON, not a security measure. kubectl describe secret deliberately shows key names and byte counts without values, which is safer when sharing terminal output."},{"question":"What is the difference between data and stringData?","answer":"data expects base64-encoded values, so you must encode them yourself. stringData accepts plaintext and Kubernetes encodes it on write, which makes manifests far more readable and removes a whole class of "I forgot to encode it" errors. stringData is write-only. Reading the object back always shows data with base64 values. If both specify the same key, stringData wins."},{"question":"Why does my pod fail with ImagePullBackOff on a private registry?","answer":"Most often the imagePullSecrets reference is missing from the pod spec, or the Secret exists in a different namespace. Secrets are namespaced and are never shared across them. Confirm the Secret is in the pod's namespace and was created with kubectl create secret docker-registry, which produces the required kubernetes.io/dockerconfigjson type. kubectl describe pod quotes the registry's own error, which distinguishes an authentication failure from a genuinely missing tag. To avoid repeating the reference everywhere, attach the Secret to the namespace's ServiceAccount instead."},{"question":"Should I use environment variables or files for secrets?","answer":"Files, for anything high-value. Environment variables are visible in /proc/<pid>/environ, are frequently captured by crash reporters and APM agents, are inherited by child processes, and never refresh when the Secret changes. Mounted Secret volumes are backed by tmpfs so the value never reaches node disk, they update within about a minute when the Secret changes, and you can restrict permissions with defaultMode: 0400. Environment variables remain reasonable for low-sensitivity values where simplicity matters."},{"question":"How should secrets be managed in production?","answer":"Keep the source of truth outside the cluster and sync it in. The External Secrets Operator reads from AWS Secrets Manager, SSM Parameter Store, Vault or Azure Key Vault and creates native Secrets, so Git holds only a reference. Sealed Secrets takes the other approach, encrypting a Secret with a cluster-held key so the encrypted file is safe to commit. The usual fit for GitOps. Either way, no plaintext credential is committed and rotation happens in one place instead of across every manifest."}]},{"slug":"kubernetes-storage","title":"Kubernetes Storage","seoTitle":"Kubernetes Storage: Volumes, PV, PVC and Access Modes","description":"Compare Kubernetes storage options from emptyDir to PersistentVolumes. Learn PVCs, ReadWriteOnce vs ReadWriteMany, reclaim policies and CSI drivers.","content":"$4c","difficulty":"Easy","duration":"15 min","keywords":["kubernetes storage","persistent volume claim","emptyDir vs persistent volume","hostPath volume","ReadWriteOnce ReadWriteMany","kubernetes volumes explained","csi driver","statefulset storage"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":29,"tableOfContents":[{"id":"everything-a-container-writes-is-temporary-by-default","title":"Everything a container writes is temporary by default","level":2},{"id":"the-storage-options-in-order-of-how-long-data-lasts","title":"The storage options, in order of how long data lasts","level":2},{"id":"emptydir","title":"emptyDir","level":3},{"id":"hostpath-almost-always-the-wrong-answer","title":"hostPath: almost always the wrong answer","level":3},{"id":"persistentvolumes-and-persistentvolumeclaims","title":"PersistentVolumes and PersistentVolumeClaims","level":2},{"id":"access-modes","title":"Access modes","level":2},{"id":"reclaim-policy-what-happens-when-the-claim-goes","title":"Reclaim policy: what happens when the claim goes","level":2},{"id":"csi-where-the-implementations-come-from","title":"CSI: where the implementations come from","level":2},{"id":"debugging-a-pending-pvc","title":"Debugging a Pending PVC","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a PersistentVolume and a PersistentVolumeClaim?","answer":"A PersistentVolume is the actual storage resource in the cluster and is cluster-scoped. A PersistentVolumeClaim is a namespaced request for storage. This much capacity, these access modes, this StorageClass. The split separates the platform team who supply storage from the application team who consume it, so a manifest asking for "20Gi ReadWriteOnce" works unchanged whether the backing store is EBS, Ceph or a local disk. With dynamic provisioning you write only the PVC and the StorageClass creates the PV automatically."},{"question":"Why is my PersistentVolumeClaim stuck in Pending?","answer":"Run kubectl describe pvc <name> and read the events. If it says "waiting for first consumer", nothing is wrong. The StorageClass uses WaitForFirstConsumer and binds once a pod schedules. Otherwise the usual causes are no default StorageClass (kubectl get storageclass should show one marked default), no CSI driver installed so nothing can provision, an access mode the backing storage cannot provide such as ReadWriteMany on block storage, or a zone mismatch between an existing volume and where the pod can be scheduled."},{"question":"Can multiple pods share the same PersistentVolume?","answer":"It depends entirely on the access mode and the backing storage. ReadWriteOnce allows read-write mounting by one node at a time, so pods on that same node can share it but pods elsewhere cannot. ReadWriteMany allows many nodes simultaneously, but block storage, EBS, GCP Persistent Disk, Azure Disk, cannot provide it at all. For genuine shared read-write access you need a file-based system such as EFS, Azure Files, Filestore or NFS. If each replica needs its own storage rather than shared storage, use a StatefulSet with a volumeClaimTemplate."},{"question":"What happens to my data when I delete a PVC?","answer":"That is set by the reclaim policy on the PersistentVolume. With Delete, which is the default for most cloud StorageClasses, the underlying disk is destroyed along with everything on it. With Retain, the PV and data survive and an administrator decides what to do next. Because Delete is the default, removing a namespace can permanently destroy a database volume. For anything valuable, use a StorageClass with reclaimPolicy: Retain and take volume snapshots."},{"question":"Should I ever use hostPath?","answer":"Only for workloads that genuinely need access to the node's own filesystem. Log collectors reading /var/log, monitoring agents reading /proc, some CSI and networking components. For application data it is a poor choice: the data is bound to one node, so a rescheduled pod finds nothing, and it is a significant security risk since a pod able to mount host paths can often escalate to controlling the node. Use a PersistentVolumeClaim instead, or emptyDir if the data is genuinely temporary."},{"question":"What is emptyDir and when should I use it?","answer":"emptyDir is a directory created when a pod is assigned to a node and deleted when the pod is removed. It survives container restarts but not pod replacement, which makes it right for scratch space, caches, and sharing files between containers in the same pod. An application writing logs that a sidecar ships, for instance. Set a sizeLimit, because by default it can consume all node disk and cause evictions across the node. Setting medium: Memory makes it a tmpfs, which is fast but counts against the pod's memory limit."}]},{"slug":"kubernetes-pv-pvc","title":"Persistent Volumes and PersistentVolumeClaims","seoTitle":"PersistentVolume and PVC in Kubernetes: Pending Fixes","description":"Understand Kubernetes PersistentVolumes and PVCs: static vs dynamic provisioning, access modes, reclaim policies, expansion, and why a PVC stays Pending.","content":"$4d","difficulty":"Medium","duration":"18 min","keywords":["persistent volume","persistentvolumeclaim","pvc pending","ReadWriteOnce","reclaim policy retain","volume expansion","pvc stuck terminating","statefulset volumeClaimTemplate"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":30,"tableOfContents":[{"id":"scope-and-ownership","title":"Scope and ownership","level":2},{"id":"static-versus-dynamic-provisioning","title":"Static versus dynamic provisioning","level":2},{"id":"binding-states","title":"Binding states","level":2},{"id":"access-modes","title":"Access modes","level":2},{"id":"reclaim-policies","title":"Reclaim policies","level":2},{"id":"expanding-a-volume","title":"Expanding a volume","level":2},{"id":"recovering-data-from-a-released-pv","title":"Recovering data from a Released PV","level":2},{"id":"a-pvc-stuck-in-terminating","title":"A PVC stuck in Terminating","level":2},{"id":"storage-for-statefulsets","title":"Storage for StatefulSets","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a PV and a PVC?","answer":"A PersistentVolume is the storage itself and is cluster-scoped; a PersistentVolumeClaim is a namespaced request for storage with a given size, access mode and class. Pods reference claims, never volumes directly. The separation lets application teams ask for storage in portable terms while the platform decides what actually backs it, so the same manifest works across clusters with entirely different storage. With dynamic provisioning you write only the PVC and a StorageClass creates the matching PV."},{"question":"Why is my PVC stuck in Pending?","answer":"Check kubectl describe pvc <name> first. "Waiting for first consumer" is normal WaitForFirstConsumer behaviour and resolves once a pod uses the claim. Genuine causes are no default StorageClass, no CSI driver installed so nothing can provision, an access mode the backing storage does not support, most commonly ReadWriteMany on block storage, insufficient capacity in a statically provisioned pool, or a zone mismatch between an existing volume and where the pod can schedule."},{"question":"How do I resize a PersistentVolumeClaim?","answer":"Patch the requested storage upward: kubectl patch pvc <name> -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'. This requires allowVolumeExpansion: true on the StorageClass. Shrinking is never supported by any provisioner. Some drivers complete the filesystem resize online while others set a FileSystemResizePending condition and finish when the pod restarts, so check the PVC's status conditions if the new size does not appear inside the container."},{"question":"Can I recover data after accidentally deleting a PVC?","answer":"Only if the reclaim policy was Retain. In that case the PV moves to Released with the data intact but will not bind to anything, because it still references the deleted claim. Clear it with kubectl patch pv <pv> -p '{"spec":{"claimRef": null}}', then create a PVC with volumeName set to that PV and storageClassName: "" so dynamic provisioning does not hand you a new empty volume instead. With Delete, the underlying storage was destroyed and only a snapshot or backup will help."},{"question":"Why is my PVC stuck Terminating?","answer":"The kubernetes.io/pvc-protection finalizer is holding it because a pod still references the claim. Run kubectl describe pvc <name> and look at the "Used By" field, then delete or reschedule that pod. The finalizer clears automatically once nothing is using the volume. Removing the finalizer by hand does work but risks leaving a volume attached to a node that Kubernetes no longer knows about, so it should be a last resort after confirming nothing is mounted."},{"question":"Do StatefulSet volumes get deleted when I delete the StatefulSet?","answer":"No. PVCs created from a volumeClaimTemplate deliberately outlive the StatefulSet so that deleting a workload cannot destroy a database. Recreating the StatefulSet with the same name reattaches the existing volumes, which is also how scaling down and back up preserves data. The flip side is that finished workloads leave PVCs behind that continue to cost money, so decommissioning something properly means deleting its claims explicitly."}]},{"slug":"kubernetes-storage-classes","title":"Kubernetes StorageClasses","seoTitle":"Kubernetes StorageClass: Dynamic Provisioning Explained","description":"Learn how a Kubernetes StorageClass enables dynamic provisioning: key fields, volumeBindingMode, allowVolumeExpansion, the default class and debugging.","content":"$4e","difficulty":"Medium","duration":"14 min","keywords":["kubernetes storageclass","dynamic provisioning","volumeBindingMode WaitForFirstConsumer","allowVolumeExpansion","default storageclass","gp3 storageclass","csi provisioner","reclaimPolicy"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":31,"tableOfContents":[{"id":"what-one-looks-like","title":"What one looks like","level":2},{"id":"the-fields-that-matter","title":"The fields that matter","level":2},{"id":"provisioner","title":"provisioner","level":3},{"id":"parameters","title":"parameters","level":3},{"id":"reclaimpolicy","title":"reclaimPolicy","level":3},{"id":"volumebindingmode","title":"volumeBindingMode","level":3},{"id":"allowvolumeexpansion","title":"allowVolumeExpansion","level":3},{"id":"the-default-storageclass","title":"The default StorageClass","level":2},{"id":"snapshots","title":"Snapshots","level":2},{"id":"debugging","title":"Debugging","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a StorageClass in Kubernetes?","answer":"It is a cluster-scoped object describing a type of storage that can be created on demand, which CSI driver provisions it, what parameters that driver should use, what happens to the disk when the claim goes away, and when provisioning should happen. A PersistentVolumeClaim names a StorageClass and the provisioner creates a matching PersistentVolume automatically, which removes the need for an administrator to pre-create volumes by hand."},{"question":"What does volumeBindingMode: WaitForFirstConsumer do?","answer":"It defers creating the volume until a Pod using the claim has been scheduled, so the disk is provisioned in the same Availability Zone as the node that will mount it. With Immediate, the volume is created straight away in some arbitrary zone, and if the scheduler later places the Pod elsewhere the Pod is stuck with a volume node affinity conflict. Use WaitForFirstConsumer for any zonal block storage, and do not mistake its "waiting for first consumer" event for an error. It is the mode working as intended."},{"question":"Why is my PVC stuck in Pending with a StorageClass set?","answer":"Check kubectl describe pvc for the exact event. "Waiting for first consumer" is normal and resolves once a Pod uses the claim. "No volume plugin matched" or a missing provisioner means the CSI driver named by the StorageClass is not installed. Confirm with kubectl get csidrivers. Otherwise look at the provisioner's own logs, since quota limits, permissions and unsupported parameter combinations all surface there rather than on the claim."},{"question":"How do I set or change the default StorageClass?","answer":"Add the annotation storageclass.kubernetes.io/is-default-class: "true" to the class you want, and remove it from any other. Having two defaults makes the behaviour unpredictable. Claims that omit storageClassName then use it. Be aware that storageClassName: "" is not the same as leaving the field out: an empty string explicitly disables dynamic provisioning and forces binding to a pre-created PersistentVolume."},{"question":"How do I stop deleting a namespace from destroying my data?","answer":"Create a second StorageClass with reclaimPolicy: Retain and use it for anything stateful. With Delete, which is the default on nearly every cloud StorageClass, removing a PVC destroys the underlying disk, and deleting a namespace removes every PVC in it. Retain keeps the volume and its data, leaving the PV in a Released state that an administrator can recover. Retained volumes do accumulate cost, so they need deliberate cleanup, and real volume snapshots remain the actual backup mechanism."},{"question":"Can I change a StorageClass after volumes have been created?","answer":"Most fields on a StorageClass are immutable, and in any case changes do not apply retroactively to existing PersistentVolumes. They were provisioned with the settings in force at the time. To move a workload onto different storage you create a new StorageClass, create a new PVC using it, and migrate the data, typically from a volume snapshot. This is why enabling allowVolumeExpansion and encryption from the beginning matters: neither can be added to a volume that already exists."}]},{"slug":"kubernetes-namespaces","title":"Kubernetes Namespaces","seoTitle":"Kubernetes Namespaces: Create, Use and Delete Safely","description":"Learn how Kubernetes namespaces work, which resources are namespaced, cross-namespace DNS, how to split a cluster, and fix a namespace stuck Terminating.","content":"$4f","difficulty":"Easy","duration":"14 min","keywords":["kubernetes namespaces","kubectl namespace","namespace vs cluster scoped","cross namespace dns","namespace stuck terminating","resourcequota namespace","namespace rbac","default namespaces"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":32,"tableOfContents":[{"id":"what-is-namespaced-and-what-is-not","title":"What is namespaced and what is not","level":2},{"id":"the-built-in-namespaces","title":"The built-in namespaces","level":2},{"id":"working-with-them","title":"Working with them","level":2},{"id":"cross-namespace-dns","title":"Cross-namespace DNS","level":2},{"id":"what-namespaces-are-actually-good-for","title":"What namespaces are actually good for","level":2},{"id":"how-to-divide-a-cluster","title":"How to divide a cluster","level":2},{"id":"deleting-and-the-terminating-trap","title":"Deleting, and the Terminating trap","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a Kubernetes namespace used for?","answer":"It partitions a cluster into named scopes so resource names only need to be unique within one, and it gives RBAC, resource quotas, limit ranges and network policies something to attach to. That combination is what lets several teams share a cluster: each gets its own space, its own permissions, and a cap on how much it can consume. It is an organisational and policy boundary rather than a runtime one. Workloads still share the same nodes."},{"question":"Are namespaces a security boundary?","answer":"Only partly, and assuming otherwise is a common mistake. Kubernetes networking is flat by default, so a Pod in any namespace can reach a Service in any other. Namespaces do provide an RBAC boundary, so you can stop people managing resources elsewhere, but they do not stop workloads talking to each other. Real isolation needs NetworkPolicy resources enforced by a capable CNI such as Calico or Cilium, plus Pod Security Standards. For strong isolation between untrusted tenants, separate clusters remain the safer answer."},{"question":"How do I call a Service in another namespace?","answer":"Include the namespace in the DNS name: service-name.namespace, or the fully qualified service-name.namespace.svc.cluster.local. The bare short name only resolves within the caller's own namespace because the cluster DNS search path appends that namespace first. This is why a request that works between two services in the same namespace fails the moment one of them moves."},{"question":"Which resources are not namespaced?","answer":"Nodes, PersistentVolumes, StorageClasses, ClusterRoles, ClusterRoleBindings, CustomResourceDefinitions and namespaces themselves are all cluster-scoped. kubectl api-resources --namespaced=false lists them for your cluster. The distinction that surprises people most is that PersistentVolumeClaims are namespaced while the PersistentVolumes they bind are not, and that Secrets are namespaced, so a registry pull secret must exist in the same namespace as the Pod that needs it."},{"question":"Why is my namespace stuck in Terminating?","answer":"Something inside it has a finalizer that is not completing, almost always a custom resource whose operator has been uninstalled so nothing is left to run the cleanup. Check with kubectl get namespace <name> -o json | jq '.status.conditions', which names the remaining resource types. Fix it by deleting those resources or reinstalling the operator. Force-removing the namespace's finalizer is widely suggested and does work, but it orphans whatever the finalizers were protecting, including cloud load balancers and disks that carry on costing money."},{"question":"Should I use one namespace per environment or separate clusters?","answer":"Namespaces per environment are cheap and fine for development and staging, where sharing nodes and a control plane is acceptable. For production, separate clusters are safer: namespaces share nodes, a kernel and a control plane, so a resource-hungry workload or a cluster-wide upgrade affects everything at once. A common arrangement is one non-production cluster split by namespace and a dedicated production cluster, itself split by team or service."}]},{"slug":"kubernetes-resource-management","title":"Kubernetes Resource Management","seoTitle":"Kubernetes Resource Management: Requests, Limits, QoS","description":"Learn how Kubernetes manages CPU and memory: requests vs limits, units, what happens at the limit, QoS classes, namespace controls and how to choose values.","content":"$50","difficulty":"Medium","duration":"15 min","keywords":["kubernetes resource management","cpu memory requests limits","millicores","QoS classes","OOMKilled","cpu throttling","pod eviction","LimitRange ResourceQuota"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":33,"tableOfContents":[{"id":"requests-and-limits-are-two-different-mechanisms","title":"Requests and limits are two different mechanisms","level":2},{"id":"units","title":"Units","level":2},{"id":"what-actually-happens-at-the-limit","title":"What actually happens at the limit","level":2},{"id":"the-cpu-limit-argument","title":"The CPU limit argument","level":2},{"id":"qos-classes","title":"QoS classes","level":2},{"id":"namespace-level-controls","title":"Namespace-level controls","level":2},{"id":"choosing-the-numbers","title":"Choosing the numbers","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between requests and limits?","answer":"A request is what the scheduler reserves for a container when deciding which node can host it; a limit is the hard ceiling the kubelet and the kernel enforce at runtime. The scheduler only ever reads requests and never looks at actual usage, so requests determine placement and limits determine behaviour under pressure. Exceeding a CPU limit throttles the container; exceeding a memory limit kills it."},{"question":"What does OOMKilled mean and how do I fix it?","answer":"The container exceeded its memory limit and the kernel terminated it. Confirm it with kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'. The application gets no warning and so logs nothing, which is why a Pod that restarts periodically with clean logs is almost always this. Either the limit is genuinely too low, check real usage with kubectl top pods before raising it, or there is a leak that a higher limit only postpones. Runtimes with their own heap settings, the JVM especially, must be told about the container limit or they will size against the node's total memory."},{"question":"Should I set CPU limits?","answer":"This is genuinely debated. CPU limits throttle rather than kill, and throttling adds latency that is difficult to diagnose because the application sees time passing with no work done. Multi-threaded runtimes hit the quota faster than expected, since it is consumed across all threads within each 100ms window. Without limits, though, one workload can starve its neighbours. A defensible position is to always set CPU requests, always set memory limits, and either omit CPU limits on latency-sensitive services or set them well above observed peak."},{"question":"What are QoS classes and why do they matter?","answer":"Kubernetes derives a class from your requests and limits. Guaranteed means requests equal limits for every container and both resources; Burstable means requests are set but differ from limits; BestEffort means nothing is set. Under node memory pressure the kubelet evicts BestEffort first, then Burstable, then Guaranteed. A production workload with no requests is therefore first to be killed, as well as being placed by a scheduler that believes it needs nothing."},{"question":"Why won't my Pod schedule when the node looks idle?","answer":"The scheduler works from the sum of requests on each node, not measured usage. If existing Pods request 95% of a node's allocatable CPU, nothing more fits there even if real usage is 20%. Run kubectl describe node <name> and look at the "Allocated resources" section, which shows requests and limits as percentages of capacity. Over-large requests are the most common cause of clusters that cost far more than their actual workload justifies."},{"question":"How do I choose the right values?","answer":"Measure first. kubectl top pods --containers gives a current snapshot, and Prometheus history is better because a single reading misses daily peaks. A reasonable starting point is a memory request at observed steady-state plus about 25%, a memory limit with genuine headroom above that, and a CPU request at observed average rather than peak. Vertical Pod Autoscaler in recommendation mode watches real usage and suggests values without changing anything, which is a low-risk way to sanity-check your numbers."}]},{"slug":"kubernetes-requests-limits","title":"Kubernetes Requests and Limits","seoTitle":"Kubernetes Requests and Limits: CPU, Memory, OOMKilled","description":"Set Kubernetes CPU and memory requests and limits correctly. Learn the units, enforcement, QoS classes, OOMKilled, throttling, LimitRange and ResourceQuota.","content":"$51","difficulty":"Medium","duration":"16 min","keywords":["kubernetes requests and limits","cpu millicores","memory Mi Gi","OOMKilled kubernetes","cpu throttling kubernetes","QoS Guaranteed Burstable","LimitRange","kubectl top"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":34,"tableOfContents":[{"id":"the-yaml","title":"The YAML","level":2},{"id":"units","title":"Units","level":2},{"id":"how-each-is-enforced","title":"How each is enforced","level":2},{"id":"requests-the-scheduler","title":"Requests: the scheduler","level":3},{"id":"cpu-limits-throttling","title":"CPU limits: throttling","level":3},{"id":"memory-limits-the-oom-killer","title":"Memory limits: the OOM killer","level":3},{"id":"qos-classes","title":"QoS classes","level":2},{"id":"namespace-defaults-with-limitrange","title":"Namespace defaults with LimitRange","level":2},{"id":"resourcequota","title":"ResourceQuota","level":2},{"id":"choosing-values","title":"Choosing values","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between requests and limits in Kubernetes?","answer":"A request is what the scheduler reserves when deciding which node can host a container. It is pure accounting and the scheduler never consults actual usage. A limit is the ceiling enforced at runtime by the kernel through cgroups. The consequences differ by resource: exceeding a CPU limit throttles the container, while exceeding a memory limit terminates it. Requests also determine QoS class, which decides eviction order under node pressure."},{"question":"Why does my container keep getting OOMKilled?","answer":"It exceeded its memory limit and the kernel killed it without warning, which is why the application's own logs show nothing. Confirm with kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'. Measure real usage with kubectl top pods --containers before simply raising the limit, if usage grows without bound it is a leak that a larger limit only delays. For JVM, Node or .NET applications, check that the runtime knows about the container limit, since a heap sized against the node's total memory will exceed a small container limit every time."},{"question":"Should I set CPU limits on my containers?","answer":"It is a genuine trade-off rather than a settled best practice. Limits prevent one workload starving its neighbours and are required for the Guaranteed QoS class, but they throttle rather than kill, and throttling adds latency that is very hard to see from inside the application. Multi-threaded runtimes reach the quota quickly because it is consumed across all threads within each 100ms window. A common position is to always set CPU requests and memory limits, and either omit CPU limits on latency-sensitive services or set them comfortably above observed peak."},{"question":"What is the difference between Mi and M?","answer":"Mi is mebibytes, a multiple of 1024², and M is megabytes, a multiple of 1000². So 128Mi is 134,217,728 bytes while 128M is 128,000,000, about 5% apart, which grows to a meaningful gap at gigabyte scale. Kubernetes accepts both, but the binary suffixes Ki, Mi and Gi match how memory is actually reported by tools and by the kernel, so mixing them up can turn a carefully sized limit into one that triggers the OOM killer."},{"question":"How do I know what values to use?","answer":"Measure before guessing. kubectl top pods --containers gives a current snapshot but misses daily peaks, so Prometheus history is better if you have it. Vertical Pod Autoscaler in recommendation mode watches real usage over time and proposes values without modifying anything, which is a safe way to validate your numbers. As a starting point, set memory request to steady-state usage plus around 25%, memory limit with real headroom above that, and CPU request to the observed average rather than peak."},{"question":"What happens if I set no requests or limits at all?","answer":"The Pod becomes BestEffort, which is the worst of both worlds. The scheduler believes it needs nothing and will place it on an already-loaded node, and under memory pressure the kubelet evicts BestEffort Pods first. The container is also uncapped, so a memory leak can consume the node and take down everything else running on it. A LimitRange in the namespace with defaultRequest and default values prevents this happening by accident."}]},{"slug":"kubernetes-hpa","title":"Horizontal Pod Autoscaler","seoTitle":"Kubernetes HPA: Autoscale Pods on CPU and Memory","description":"Set up the Kubernetes Horizontal Pod Autoscaler: the scaling formula, required resource requests, autoscaling/v2 YAML, behavior and fixing unknown targets.","content":"$52","difficulty":"Medium","duration":"16 min","keywords":["kubernetes hpa","horizontal pod autoscaler","autoscaling/v2","metrics-server","hpa unknown target","hpa scaling behavior","custom metrics autoscaling","keda"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":35,"tableOfContents":[{"id":"the-formula","title":"The formula","level":2},{"id":"it-requires-resource-requests","title":"It requires resource requests","level":2},{"id":"a-working-hpa","title":"A working HPA","level":2},{"id":"controlling-how-it-scales","title":"Controlling how it scales","level":2},{"id":"beyond-cpu","title":"Beyond CPU","level":2},{"id":"nodes-have-to-exist","title":"Nodes have to exist","level":2},{"id":"diagnosing","title":"Diagnosing","level":2},{"id":"what-it-will-not-do","title":"What it will not do","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does my HPA show <unknown> for the target metric?","answer":"Two causes only. Either metrics-server is not installed, in which case kubectl top pods also fails, or the target Pods have no resource request for the metric being measured. A Utilization target is a percentage of the request, so without a request there is nothing to compute against. Check with kubectl get deployment <name> -o jsonpath='{.spec.template.spec.containers[0].resources}' and add requests if they are missing."},{"question":"How does the HPA decide how many replicas to run?","answer":"It applies desiredReplicas = ceil(currentReplicas à (currentMetric / targetMetric)), recalculated every 15 seconds by default. Three replicas averaging 90% CPU against a 50% target gives six. Because the calculation is proportional it corrects large overshoots in one step rather than incrementing. It also explains why an HPA cannot scale up from zero: multiplying zero replicas by any ratio gives zero, which is why scale-to-zero requires KEDA."},{"question":"Can the HPA scale on something other than CPU?","answer":"Yes, with autoscaling/v2. Memory works but is often a poor signal, since many runtimes retain memory rather than releasing it so the value never falls enough to scale back down. Custom metrics from your application, requests per second, for example, are available through Prometheus Adapter, and external metrics such as SQS queue depth suit worker pools. When several metrics are configured, the HPA computes a target for each and uses the largest, so any one signal under pressure triggers a scale up."},{"question":"Why does my HPA keep scaling up and down repeatedly?","answer":"Usually the scale-down stabilization window is too short, so a brief dip removes capacity that is immediately needed again. The default of 300 seconds makes the HPA take the highest recommendation from the last five minutes, which damps exactly this. The other common cause is a conflict: if the Deployment manifest still specifies replicas and something reapplies it, the HPA and your deployment tooling will fight over the same field on every apply."},{"question":"Do I need Cluster Autoscaler as well as the HPA?","answer":"Almost certainly. The HPA adds Pods; it does not add machines. If the cluster has no spare capacity the new Pods simply sit in Pending and no extra traffic is served. Cluster Autoscaler adjusts node group sizes in response, and Karpenter does the same job by provisioning right-sized instances directly and generally reacts faster. Set maxReplicas to something your cluster and budget can genuinely absorb, treating it as a safety limit rather than an aspiration."},{"question":"Can I use HPA and Vertical Pod Autoscaler together?","answer":"Not on the same metric. VPA adjusts CPU and memory requests while the HPA scales replicas based on utilisation of those requests, so both react to each other and the result oscillates. The workable split is VPA managing memory and the HPA scaling on CPU or a custom metric, which keeps them out of each other's way. VPA in recommendation-only mode is always safe to run alongside an HPA, since it reports suggested values without changing anything."}]},{"slug":"kubernetes-helm","title":"Helm for Kubernetes","seoTitle":"What Is Helm? The Kubernetes Package Manager Explained","description":"Learn what Helm is for Kubernetes: charts, releases and repositories, how helm upgrade works, where Helm falls short, and how it compares with Kustomize.","content":"$53","difficulty":"Medium","duration":"15 min","keywords":["helm kubernetes","helm chart","helm release","helm vs kustomize","helm upgrade install","helm rollback","helm repository","helm 3 tiller"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":36,"tableOfContents":[{"id":"three-terms","title":"Three terms","level":2},{"id":"the-case-for-helm","title":"The case for Helm","level":2},{"id":"helm-3-removed-the-thing-people-disliked","title":"Helm 3 removed the thing people disliked","level":2},{"id":"the-workflow","title":"The workflow","level":2},{"id":"what-helm-actually-does-at-upgrade-time","title":"What Helm actually does at upgrade time","level":2},{"id":"where-helm-is-weak","title":"Where Helm is weak","level":2},{"id":"helm-or-kustomize","title":"Helm or Kustomize?","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is Helm and do I actually need it?","answer":"Helm is a package manager for Kubernetes that bundles manifests into versioned, parameterised charts and tracks each installation as a release with upgrade and rollback history. You almost certainly need it for consuming third-party software, because Prometheus, ingress-nginx, cert-manager and most of the ecosystem ship as charts and installing them by hand means managing dozens of interdependent manifests. Whether you package your own applications with it is a genuine choice. Many teams use Helm for external software and Kustomize for their own services, keeping their manifests as readable YAML."},{"question":"What is the difference between a chart and a release?","answer":"A chart is the package: templates, default values and metadata, versioned and distributable. A release is one installed instance of that chart in a cluster, with its own name, its own values and its own revision history. The same chart can be installed many times under different names, which is how you run several instances of the same application side by side. helm list shows releases, not charts."},{"question":"What is the difference between Helm and Kustomize?","answer":"Helm uses Go templating with variables substituted at render time, and adds packaging, distribution and release tracking with rollback. Kustomize patches plain YAML through overlays, so base manifests remain valid Kubernetes YAML you can apply directly, and it is built into kubectl via apply -k. Helm is stronger for distributing software to other people and for managing installed releases; Kustomize is easier to read and reason about for your own manifests. Using both, Helm for third-party charts, Kustomize for internal services, is common and works well."},{"question":"Why doesn't helm upgrade update my CRDs?","answer":"By design. CRDs placed in a chart's crds/ directory are installed once on first install and never upgraded or deleted afterwards, because an incorrect CRD change can invalidate or destroy every custom resource of that type in the cluster. Helm takes the view that this is too destructive to automate. When a chart upgrade includes CRD changes, apply them yourself with kubectl apply -f before running the upgrade. The chart's release notes normally say so, and skipping it produces validation errors that look unrelated."},{"question":"How do I roll back a Helm release?","answer":"helm history <release> lists the revisions, then helm rollback <release> <revision> reverts to one. Helm stores each revision's manifest as a Secret in the release namespace, so rollback simply reapplies an earlier state. Two caveats: it only reverts Kubernetes objects, so a database migration run by the bad release still needs handling separately, and data lost through a deleted PersistentVolumeClaim is not coming back. Upgrading with --atomic avoids most rollbacks by reverting automatically on failure."},{"question":"Is Helm safe to use in production? What happened to Tiller?","answer":"Helm 3 is safe in a way Helm 2 was not. Helm 2 ran a cluster-side component called Tiller that typically held broad cluster-admin permissions and was a well-documented privilege escalation risk. Helm 3 removed it entirely and runs client-side, authenticating with your own kubeconfig, so it can only do what you are already permitted to do. Release state lives in Secrets in the release's namespace rather than in a privileged service. Any guide that mentions helm init or Tiller is describing Helm 2 and is out of date."}]},{"slug":"kubernetes-helm-install","title":"Installing and Using Helm","seoTitle":"How to Install Helm and Deploy Your First Chart","description":"Install Helm, add a repository, read chart values, then run helm install, helm upgrade, helm list and helm uninstall to manage a release on Kubernetes.","content":"$54","difficulty":"Easy","duration":"18 min","keywords":["install helm","helm repo add","helm install chart","helm upgrade install","helm list status","helm uninstall","helm version","helm namespace"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":37,"tableOfContents":[{"id":"install","title":"Install","level":2},{"id":"macos","title":"macOS","level":3},{"id":"linux","title":"Linux","level":3},{"id":"windows","title":"Windows","level":3},{"id":"verify","title":"Verify","level":3},{"id":"repositories","title":"Repositories","level":2},{"id":"read-the-values-before-you-install","title":"Read the values before you install","level":2},{"id":"install-a-chart","title":"Install a chart","level":2},{"id":"inspect-what-is-installed","title":"Inspect what is installed","level":2},{"id":"upgrade-and-roll-back","title":"Upgrade and roll back","level":2},{"id":"uninstall","title":"Uninstall","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I install Helm?","answer":"Use your platform's package manager: brew install helm on macOS, choco install kubernetes-helm or winget install Helm.Helm on Windows, and either the official install script or the apt repository on Linux. Confirm with helm version that you have v3.x. There is no cluster-side installation step. Helm 3 is a client that uses your existing kubeconfig, so if kubectl works, Helm works."},{"question":"Do I still need to run helm init or install Tiller?","answer":"No. helm init and Tiller belong to Helm 2, which reached end of life. Tiller was a cluster-side service that usually held broad permissions and was a recognised security risk. Helm 3 removed it: the client talks directly to the Kubernetes API using your credentials, and release state is stored as Secrets in each release's namespace. Any tutorial mentioning helm init is out of date and should be treated with suspicion generally."},{"question":"What is the difference between helm install and helm upgrade --install?","answer":"helm install fails if a release with that name already exists. helm upgrade --install installs it when absent and upgrades it when present, making the command idempotent. That is what you want in CI or any script that may run more than once, since it removes the need to check state first. Adding --atomic makes it safer still by rolling back automatically if the release does not become ready."},{"question":"Why can't Helm find my release?","answer":"helm list only shows releases in the current namespace, because Helm 3 stores release state as Secrets in the namespace where the release lives. Run helm list -A to search every namespace, or helm list -n <namespace> to target one. If it is still missing, confirm you are on the expected cluster with kubectl config current-context. Pointing at the wrong context is the other common cause."},{"question":"Does helm uninstall delete everything?","answer":"Not quite, and the exceptions are deliberate. PersistentVolumeClaims are usually left in place so that uninstalling an application does not destroy its data, which means the underlying cloud volumes keep incurring cost until you delete the claims yourself. CRDs installed from a chart's crds/ directory are also left alone, because deleting a CRD would delete every custom resource of that type across the cluster. After uninstalling anything stateful, check kubectl get pvc in that namespace."},{"question":"How do I see what a chart will actually create before installing it?","answer":"helm template <release-name> <chart> -f values.yaml renders the chart locally and prints the full set of manifests without contacting the cluster. This is the best way to review a third-party chart before trusting it, and to debug your own templates. helm install --dry-run --debug does something similar but validates against the live API server, which additionally catches schema problems and missing CRDs. Pair either with helm show values so you know which settings are available to change."}]},{"slug":"kubernetes-helm-charts","title":"Understanding Helm Charts","seoTitle":"Helm Chart Structure: Chart.yaml, Templates and Helpers","description":"Learn how a Helm chart is built: directory structure, Chart.yaml, templates, built-in objects, _helpers.tpl, debugging with helm template and dependencies.","content":"$55","difficulty":"Medium","duration":"18 min","keywords":["helm chart structure","Chart.yaml","helm templates","helm built-in objects","_helpers.tpl","helm template debug","helm lint","helm dependencies","helm create"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":38,"tableOfContents":[{"id":"structure","title":"Structure","level":2},{"id":"chartyaml","title":"Chart.yaml","level":2},{"id":"built-in-objects","title":"Built-in objects","level":2},{"id":"template-syntax-you-will-actually-use","title":"Template syntax you will actually use","level":2},{"id":"helpers","title":"Helpers","level":2},{"id":"debugging","title":"Debugging","level":2},{"id":"dependencies","title":"Dependencies","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between version and appVersion in Chart.yaml?","answer":"version is the chart's own semantic version and identifies the package in a repository index; appVersion describes the software the chart deploys and is usually the default image tag. They move independently. Fixing a template bug bumps version while appVersion stays put, and deploying a new application release bumps appVersion and also requires a version bump because the chart changed. Forgetting to bump version is why a published fix never reaches users."},{"question":"Why does my Helm template produce invalid YAML?","answer":"Almost always whitespace. Go template actions leave blank lines and stray indentation unless you trim them with {{- and -}}, and a conditional that emits nothing still leaves the line it occupied. The second frequent cause is confusing indent with nindent: nindent prepends a newline before indenting, indent does not, so using the wrong one shifts a whole block. Run helm template and read the rendered output. The broken indentation is visible immediately."},{"question":"How do I debug a Helm chart without installing it?","answer":"helm template <release-name> ./chart -f values.yaml renders everything locally with no cluster contact, and -s templates/deployment.yaml narrows it to one file. Add helm lint for structural problems in Chart.yaml and missing required fields. When you need schema validation against real API versions and CRDs, helm install --dry-run --debug renders against the live API server without persisting anything, which catches errors local rendering cannot."},{"question":"What is _helpers.tpl for?","answer":"It holds named templates, reusable snippets defined with define and invoked with include, and the leading underscore tells Helm not to treat the file as a manifest to render. The standard use is generating consistent names and label sets in one place so every resource in the chart agrees. Note the trunc 63 you will see in generated helpers: Kubernetes object names are limited to 63 characters, and a long release name combined with a long chart name will otherwise exceed it."},{"question":"How do chart dependencies work?","answer":"Declare them in Chart.yaml under dependencies with a name, version range and repository, then run helm dependency update to fetch them into charts/ and generate Chart.lock. Values reach a subchart by nesting under its name in the parent's values.yaml, and global values are visible to every subchart. Adding a condition such as postgresql.enabled lets users disable a dependency entirely. Useful when some will run the bundled database and others point at a managed one. Commit Chart.lock for reproducible builds."},{"question":"Should I use helm create as a starting point?","answer":"It is a reasonable scaffold and worth running once to see the conventions, but the generated chart includes a ServiceAccount, Ingress, HorizontalPodAutoscaler and a large values.yaml that most services do not need. Carrying unused templates makes a chart harder to read and review. Generate it, delete what your application genuinely does not use, and keep _helpers.tpl. The naming and label helpers it produces follow the recommended Kubernetes label conventions and are worth preserving."}]},{"slug":"kubernetes-helm-values","title":"Helm Values, Upgrades, and Rollbacks","seoTitle":"Helm Values: --set, values.yaml, Upgrade and Rollback","description":"Override Helm values with values.yaml files and --set, understand precedence, upgrade safely with --atomic, use helm rollback and fix a stuck release.","content":"$56","difficulty":"Medium","duration":"16 min","keywords":["helm values file","helm set","helm values precedence","helm upgrade atomic","helm rollback","helm reuse-values","helm get values","helm history"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":39,"tableOfContents":[{"id":"start-by-reading-the-defaults","title":"Start by reading the defaults","level":2},{"id":"overriding","title":"Overriding","level":2},{"id":"precedence","title":"Precedence","level":2},{"id":"upgrading","title":"Upgrading","level":2},{"id":"--reuse-values-and---reset-values","title":"--reuse-values and --reset-values","level":3},{"id":"preview-first","title":"Preview first","level":3},{"id":"rolling-back","title":"Rolling back","level":2},{"id":"when-a-release-is-stuck","title":"When a release is stuck","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the order of precedence for Helm values?","answer":"From lowest to highest: subchart values.yaml, then the parent chart's values.yaml, then each -f file in the order given with later files winning, then --set and --set-string. Merging is deep for maps, so a file setting only one key leaves the rest of the defaults intact. Arrays are the exception. A higher-precedence list replaces the lower one entirely rather than merging. helm get values <release> --all shows the final merged result."},{"question":"Why did my helm upgrade lose the values I set at install time?","answer":"Because helm upgrade computes values from the chart defaults plus what you pass on that specific command; anything supplied previously and not repeated is dropped. Passing only --set image.tag=x therefore reverts every other override. --reuse-values carries the previous set forward, but the robust habit is to keep a values file in Git and pass it in full on every upgrade, so the command is deterministic and does not depend on release state."},{"question":"What does --atomic do and should I always use it?","answer":"--atomic makes an upgrade all-or-nothing: Helm waits for the resources to become ready and, if they do not, automatically rolls the release back to the previous revision. Without it a failed upgrade leaves the release partially applied with some resources new and some old, which is a bad state to reason about during an incident. Use it on every upgrade, and pair it with --timeout because --atomic implies --wait and will otherwise wait a long time on a stuck rollout."},{"question":"Why does my image tag break when I use --set?","answer":"--set infers types from the value, so 1.10 is parsed as a float and becomes 1.1, and 01 becomes 1. The resulting image reference does not exist and the pull fails with an error that gives no hint about the cause. Use --set-string image.tag=1.10 to force string handling. The same inference affects values like true, false and anything that looks numeric, so --set-string is the safer choice for any value that must stay textual."},{"question":"How do I roll back a Helm release, and what are the limits?","answer":"helm history <release> lists revisions and helm rollback <release> <revision> reverts to one, creating a new revision rather than erasing history. The limits are important: it reverts Kubernetes objects only, so a database migration applied by the bad release remains applied, and data removed with a PersistentVolumeClaim is not restored by recreating the claim. History is also capped by --history-max, which defaults to 10, so very old revisions are not available indefinitely."},{"question":"How should I structure values files for multiple environments?","answer":"Keep a shared base file with everything common, then a small per-environment overlay holding only what differs, and pass both: -f base.yaml -f production.yaml. Later files override earlier ones per key, so the overlay stays short and the shared settings have exactly one definition. This avoids the usual failure mode of three or four near-identical full values files that quietly drift apart. Keep secrets out of these files entirely. Use --set-file, or an external secret manager synced into the cluster."}]},{"slug":"kubernetes-monitoring","title":"Kubernetes Monitoring","seoTitle":"Kubernetes Monitoring: Prometheus, metrics-server, top","description":"Monitor Kubernetes with metrics-server, kubectl top, Prometheus and kube-state-metrics. Learn what to watch, which alerts matter, and how logs fit in.","content":"$57","difficulty":"Medium","duration":"16 min","keywords":["kubernetes monitoring","prometheus kubernetes","metrics-server","kube-state-metrics","kubectl top","kube-prometheus-stack","kubernetes alerting","container logs"],"publishedDate":"2026-07-13","updatedDate":"2026-09-12","order":40,"tableOfContents":[{"id":"metrics-server-is-not-a-monitoring-system","title":"metrics-server is not a monitoring system","level":2},{"id":"prometheus-is-the-actual-answer","title":"Prometheus is the actual answer","level":2},{"id":"scraping-your-own-applications","title":"Scraping your own applications","level":2},{"id":"what-to-actually-watch","title":"What to actually watch","level":2},{"id":"alerts-worth-having","title":"Alerts worth having","level":2},{"id":"logs","title":"Logs","level":2},{"id":"events-expire","title":"Events expire","level":2},{"id":"tracing","title":"Tracing","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does kubectl top not work on my cluster?","answer":"metrics-server is not installed, or is not healthy. It is a separate component that collects current CPU and memory from each kubelet and serves the Metrics API that kubectl top reads, and most distributions do not ship it by default. Check with kubectl get deployment metrics-server -n kube-system. The same component backs the HorizontalPodAutoscaler, so an HPA showing <unknown> targets usually has the same root cause. The other possibility there being Pods with no resource requests."},{"question":"What is the difference between metrics-server and Prometheus?","answer":"metrics-server serves only current CPU and memory readings, keeps no history, and exists to support kubectl top and autoscaling. Prometheus is a time-series database that scrapes many sources on an interval, stores the results, and supports querying, dashboards and alerting. They are complementary rather than alternatives: you generally run metrics-server for the HPA and Prometheus for everything a human looks at."},{"question":"What is kube-state-metrics and do I need it?","answer":"It exposes the state of Kubernetes objects as Prometheus metrics. How many replicas a Deployment wants versus has available, which phase each Pod is in, container restart counts, job completions. It measures the API server's view rather than actual resource consumption, which is why it complements the kubelet's cAdvisor metrics rather than duplicating them. Most genuinely useful alerts are built on it, so yes. It is included in kube-prometheus-stack."},{"question":"How do I keep logs after a Pod is deleted?","answer":"You cannot with kubectl logs. Container logs live on the node and are removed with the Pod, and --previous only reaches back one container generation. Run a log shipper as a DaemonSet on every node: Promtail or Grafana Alloy feeding Loki is the usual pairing alongside Prometheus, while Fluent Bit into OpenSearch or a commercial backend suits environments needing heavier full-text search. Have applications log to stdout in JSON, since files written inside the container are invisible to the collector."},{"question":"Why do my Kubernetes events disappear?","answer":"Events have a one-hour retention by default, controlled by the API server's --event-ttl. That is often shorter than the gap between an incident and someone investigating it, so the scheduling failure or probe error that explains everything has already expired. Ship events into your logging system with a tool such as the Kubernetes Event Exporter. This is not included in kube-prometheus-stack and is easy to overlook until the moment you need an event that is gone."},{"question":"What should I alert on?","answer":"Symptoms that users experience, not every metric that moves. A short, honest starting set: Pods crash-looping, Deployments with unavailable replicas, nodes not Ready, disk above 85%, certificates expiring within two weeks, and your own error rate and latency against an agreed SLO. Always include a for clause so transient blips do not page anyone. Alerts nobody acts on are worse than no alerts, because they teach people to ignore the channel that also carries the real ones."}]}],"category":"kubernetes","errorPages":[{"slug":"kubernetes-node-notready","title":"Kubernetes node NotReady","description":"Fix a NotReady node: run kubectl describe node, then check disk and memory pressure, the kubelet, container runtime and CNI. Includes journalctl commands."},{"slug":"pvc-pending-no-persistent-volumes","title":"PersistentVolumeClaim stuck in Pending","description":"Fix a PVC stuck in Pending: run kubectl describe pvc, then add the CSI driver, set a valid StorageClass, fix zone or access mode. Covers WaitForFirstConsumer."},{"slug":"kubectl-connection-to-server-refused","title":"The connection to the server localhost:8080 was refused","description":"Fix "connection to the server localhost:8080 was refused": check KUBECONFIG, then kubectl config get-contexts and use-context. Covers no kubeconfig and sudo."},{"slug":"pod-has-untolerated-taint","title":"0/3 nodes are available: node(s) had untolerated taint","description":"Fix "0/3 nodes are available: node(s) had untolerated taint": run kubectl describe node, then add a toleration or remove the taint. Covers control-plane nodes."}],"children":"$L58"}]
58:["$","div",null,{"className":"max-w-none","children":"$L59"}]
59:[["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"A Pod is the smallest thing Kubernetes will schedule. You can't ask Kubernetes to run a container. You ask it to run a Pod, and the Pod contains one or more containers that are guaranteed to land on the same node and share an network identity."}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Almost every Pod in production has exactly one application container. The ability to hold several exists for the cases where two processes are genuinely inseparable, not as a general packaging mechanism."}],"\n",["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"why-a-wrapper-around-containers-at-all","children":"Why a wrapper around containers at all?"}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Because some things must run together, and "together" has to mean something precise. A log shipper that reads files the application writes needs the same filesystem. A proxy that intercepts the application's traffic needs the same ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/linux/linux-networking","children":"network interface"}],". Scheduling them independently would mean they might land on different machines, and the arrangement would fall apart."]}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"A Pod makes co-location a guarantee rather than a hope."}],"\n",["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"what-containers-in-a-pod-actually-share","children":"What containers in a Pod actually share"}],"\n",["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Shared"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Not shared"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Network namespace (one IP, one port space)"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Filesystem (unless you mount a shared volume)"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"localhost"}]," between containers"]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Process namespace (by default)"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Mounted volumes you declare"}],"$L5a"]}],"$L5b"]}]]}]}]}],"\n","$L5c","\n","$L5d","\n","$L5e","\n","$L5f","\n","$L60","\n","$L61","\n","$L62","\n","$L63","\n","$L64","\n","$L65","\n","$L66","\n","$L67","\n","$L68","\n","$L69","\n","$L6a","\n","$L6b","\n","$L6c","\n","$L6d","\n","$L6e","\n","$L6f","\n","$L70","\n","$L71","\n","$L72","\n","$L73","\n","$L74","\n","$L75","\n","$L76","\n","$L77","\n","$L78","\n","$L79","\n","$L7a","\n","$L7b","\n","$L7c","\n","$L7d","\n","$L7e","\n","$L7f","\n","$L80","\n","$L81","\n","$L82","\n","$L83","\n","$L84","\n","$L85","\n","$L86","\n","$L87","\n","$L88","\n","$L89","\n","$L8a","\n","$L8b","\n","$L8c","\n","$L8d","\n","$L8e","\n","$L8f","\n","$L90","\n","$L91","\n","$L92","\n","$L93","\n","$L94","\n","$L95","\n","$L96","\n","$L97","\n","$L98","\n","$L99","\n","$L9a","\n","$L9b","\n","$L9c","\n","$L9d","\n","$L9e","\n","$L9f","\n","$La0"]
a1:I[90938,["8500","static/chunks/8500-98e13bcce54aa7a0.js","3839","static/chunks/3839-ab8b319d7c7091ea.js","5899","static/chunks/5899-9fb98f224ca30c06.js","7985","static/chunks/app/tutorials/kubernetes/%5Bslug%5D/page-0f38ff08fb7fb1e2.js"],"default"]
a5:I[18308,["8500","static/chunks/8500-98e13bcce54aa7a0.js","3839","static/chunks/3839-ab8b319d7c7091ea.js","5899","static/chunks/5899-9fb98f224ca30c06.js","7985","static/chunks/app/tutorials/kubernetes/%5Bslug%5D/page-0f38ff08fb7fb1e2.js"],"default"]
5a:["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"CPU and memory limits (set per container)"}]
5b:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Lifecycle (scheduled and deleted as a unit)"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Container restarts: individual containers restart alone"}]]}]
5c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["The shared network namespace has one consequence worth internalising: ",["$","strong",null,{"children":"two containers in the same Pod cannot listen on the same port."}]," They share a port space, so a second container binding ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":":8080"}]," fails exactly as it would on a single host."]}]
5d:["$","$La1",null,{"type":"pod"}]
5e:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Behind the scenes a small "pause" container holds the network namespace open so application containers can restart without the Pod losing its IP."}]
5f:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"pods-are-disposable-and-the-ip-goes-with-them","children":"Pods are disposable, and the IP goes with them"}]
60:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Every Pod gets its own cluster-routable IP. That IP belongs to the Pod, not to your application, and it is gone the moment the Pod is replaced. During a deploy, a node drain, an eviction, or a crash past the restart limit."}]
61:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"kubectl get pods -o wide\n"}]}]
62:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"NAME READY STATUS RESTARTS AGE IP NODE\npayments-api-7d4f-x8k2 1/1 Running 0 4m 10.244.1.17 node-1\npayments-api-7d4f-m3p9 1/1 Running 0 4m 10.244.2.31 node-2\n"}]}]
63:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Nothing should ever hardcode ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"10.244.1.17"}],". That is what a Service is for. It provides a stable name and virtual IP in front of whatever Pods currently exist."]}]
64:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"writing-a-pod","children":"Writing a Pod"}]
65:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-yaml text-sm","children":"apiVersion: v1\nkind: Pod\nmetadata:\n name: payments-api\n labels:\n app: payments-api\nspec:\n containers:\n - name: api\n image: ghcr.io/acme/payments-api:1.4.2\n ports:\n - containerPort: 8080\n resources:\n requests:\n cpu: 100m\n memory: 128Mi\n limits:\n memory: 512Mi\n readinessProbe:\n httpGet:\n path: /healthz\n port: 8080\n initialDelaySeconds: 5\n env:\n - name: LOG_LEVEL\n value: info\n"}]}]
66:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Three details in that manifest are worth defending in review:"}]
67:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":"Pin the image tag."}]," ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":":1.4.2"}],", never ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":":latest"}],". A mutable tag means two Pods from the same manifest can run different code, and a restart can silently change your application version."]}]
68:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":["Always set a ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"requests"}]," value."]}]," Without it the scheduler assumes the Pod needs nothing and will happily pack it onto a node with no room, where it competes for CPU and gets evicted first under memory pressure."]}]
69:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":"Set a memory limit but think twice about a CPU limit."}]," Exceeding a memory limit gets the container killed (",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"OOMKilled"}],"), which is what you want. An untethered memory leak takes down the node otherwise. CPU limits work differently: they throttle rather than kill, and an aggressive CPU limit adds latency to an application that would otherwise be fine."]}]
6a:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"multi-container-patterns","children":"Multi-container patterns"}]
6b:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Three patterns justify a second container:"}]
6c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":"Init containers"}]," run to completion, in order, before any application container starts. Use them for migrations, waiting on a dependency, or fetching configuration."]}]
6d:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-yaml text-sm","children":"spec:\n initContainers:\n - name: run-migrations\n image: ghcr.io/acme/payments-api:1.4.2\n command: ["./migrate", "up"]\n containers:\n - name: api\n image: ghcr.io/acme/payments-api:1.4.2\n"}]}]
6e:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["If an init container fails, the Pod retries it and the application never starts, which is usually correct. A Pod stuck in ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Init:0/1"}]," means the init container is failing or waiting; ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs <pod> -c run-migrations"}]," tells you which."]}]
6f:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":"Sidecars"}]," run alongside the application for its whole life: a log shipper, a metrics exporter, a service mesh proxy. Modern Kubernetes models these as init containers with ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"restartPolicy: Always"}],", which fixes a long-standing problem where a Job's sidecar kept running after the main container finished, so the Job never completed."]}]
70:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-yaml text-sm","children":"spec:\n initContainers:\n - name: log-shipper\n image: fluent/fluent-bit:3.1\n restartPolicy: Always # makes this a native sidecar\n"}]}]
71:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":"Adapters"}]," reshape output. For example, translating an application's custom metrics format into something Prometheus can scrape."]}]
72:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"If you're reaching for a second container and it isn't one of these, you probably want a second Pod."}]
73:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"lifecycle","children":"Lifecycle"}]
74:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["A Pod's ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"phase"}]," is a coarse summary:"]}]
75:["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Phase"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Meaning"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Pending"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Accepted, not yet running (being scheduled, or pulling images)"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Running"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Bound to a node, at least one container running"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Succeeded"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"All containers exited 0 and will not restart"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Failed"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"All containers terminated, at least one in failure"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Unknown"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"The node stopped reporting"}]]}]]}]]}]}]}]
76:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"restartPolicy"}]," controls what happens when a container exits: ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Always"}]," (the default, and what Deployments use), ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"OnFailure"}],", or ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Never"}],". It applies to containers within the Pod. A Pod is never "restarted", it is replaced."]}]
77:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"debugging-the-three-statuses-you-will-actually-see","children":"Debugging: the three statuses you will actually see"}]
78:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl describe pod"}]," is the right first command for all of them, because the Events section at the bottom usually states the cause outright."]}]
79:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"kubectl describe pod payments-api-7d4f-x8k2\n"}]}]
7a:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Pending"}]}],": nothing will schedule it. Read the events:"]}]
7b:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"Warning FailedScheduling 0/3 nodes are available: 3 Insufficient cpu.\n"}]}]
7c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Common causes: requests larger than any node can satisfy, no node matching a ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"nodeSelector"}]," or affinity rule, a taint with no matching toleration, or an unbound ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pv-pvc","children":"PersistentVolumeClaim"}],"."]}]
7d:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"ImagePullBackOff"}]}]," / ",["$","strong",null,{"children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"ErrImagePull"}]}],": the kubelet cannot fetch the image. Almost always a typo in the image name, a tag that does not exist, or a private registry with no ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"imagePullSecret"}],". The events name the registry error verbatim."]}]
7e:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"CrashLoopBackOff"}]}],": the container starts, exits, and Kubernetes restarts it with growing backoff (10s, 20s, 40s, up to five minutes). This is a symptom, never a cause. The cause is in the logs of the run that ",["$","em",null,{"children":"failed"}],":"]}]
7f:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"# Logs from the previous, crashed container, not the one starting now\nkubectl logs payments-api-7d4f-x8k2 --previous\n"}]}]
80:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["That ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--previous"}]," flag is the single most useful thing to know about debugging Pods. Without it you get the logs of a container that has only just started and has nothing to say yet."]}]
81:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Typical causes, in rough order of frequency: a missing ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/linux/linux-environment-variables","children":"environment variable"}]," or config file, a failing dependency at startup, an ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"OOMKilled"}]," container whose memory limit is too low, or a ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pods-probes","children":"liveness probe"}]," that starts before the application finishes booting and kills it repeatedly."]}]
82:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"# Was it the memory limit?\nkubectl get pod payments-api-7d4f-x8k2 -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'\n"}]}]
83:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"dont-create-bare-pods","children":"Don't create bare Pods"}]
84:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"A Pod you create directly is not managed by anything. If the node fails, the Pod is gone and nothing recreates it. There is no self-healing, no scaling, no rolling update."}]
85:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Use a ",["$","strong",null,{"children":"Deployment"}]," for stateless applications, a StatefulSet for anything needing stable identity or storage, a DaemonSet for one-per-node agents, and a Job or CronJob for work that finishes. Each of these creates Pods for you."]}]
86:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Bare Pods are for debugging:"}]
87:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"kubectl run tmp-shell --rm -it --image=nicolaka/netshoot -- bash\n"}]}]
88:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"explore-further-in-this-section","children":"Explore further in this section"}]
89:["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Sub-topic"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"What you will learn"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pods-manifest","children":"Pod Manifest"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["Write and read complete Pod ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tools/yaml-validator","children":"YAML"}]]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pods-lifecycle","children":"Pod Lifecycle"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Phases, container states, and restart behaviour"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pods-multicontainer","children":"Multi-container Pods"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Sidecars, init containers, and shared volumes"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/kubernetes/kubernetes-pods-probes","children":"Probes"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Liveness, readiness, and startup probes"}]]}]]}]]}]}]}]
8a:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"quick-reference","children":"Quick reference"}]
8b:["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Task"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Command"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"List with IPs and nodes"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl get pods -o wide"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Full detail and events"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl describe pod <name>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Logs"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs <name>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Logs from a crashed run"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs <name> --previous"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Logs from one container"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs <name> -c <container>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Shell into a container"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl exec -it <name> -- sh"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Watch status changes"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"$La2"}]]}],"$La3","$La4"]}]]}]}]}]
8c:["$","hr",null,{}]
8d:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"key-takeaways","children":"Key takeaways"}]
8e:["$","ul",null,{"className":"space-y-2 mb-4 ml-4 sm:ml-6 list-disc marker:text-teal-500","children":["\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"A Pod is the scheduling unit"}],", and containers inside one share a network namespace, an IP, and any volumes you declare, so they cannot bind the same port."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"Pod IPs are disposable."}]," Never hardcode one; put a Service in front."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"Always set resource requests"}],", or the scheduler treats the Pod as free and places it badly."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"Memory limits kill, CPU limits throttle."}]," Set memory limits; be cautious with CPU limits."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"CrashLoopBackOff"}]," is a symptom."]}]," ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs --previous"}]," shows the run that actually failed."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"Never run bare Pods in production"}],": a Deployment, StatefulSet, DaemonSet or Job gives you self-healing."]}],"\n"]}]
8f:["$","hr",null,{}]
90:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"check-your-understanding","children":"Check your understanding"}]
91:["$","$La5",null,{"exercise":{"id":"k8s-pods-port-conflict","title":"Two containers, one port","description":"A Pod defines two containers, both configured to listen on port 8080. What happens?","options":[{"id":"a","text":"The second container fails to bind, because containers in a Pod share one network namespace and port space"},{"id":"b","text":"Both start normally, because each container gets its own IP address"},{"id":"c","text":"Kubernetes automatically remaps the second container to port 8081"},{"id":"d","text":"The Pod is rejected by the API server at creation time"}],"correctId":"a","explanation":"Containers in a Pod share a single network namespace, so they share one IP and one set of ports. Exactly as two processes on the same host would. The second bind fails with "address already in use", and the container almost certainly ends up in CrashLoopBackOff.","wrongExplanations":{"b":"The IP belongs to the Pod, not to each container. A single address is shared by everything inside it, which is also why they can reach each other over localhost.","c":"Kubernetes never rewrites what port your process binds to. containerPort is documentation for humans and tooling; it does not change runtime behaviour.","d":"The API server does not validate port collisions between containers. The manifest is accepted and the failure happens at runtime, which is why it shows up as a crash rather than a rejection."}}}]
92:["$","$La5",null,{"exercise":{"id":"k8s-pods-crashloop-logs","title":"Debugging CrashLoopBackOff","description":"A Pod is in CrashLoopBackOff. You run kubectl logs and get no output at all. What is the most useful next command?","options":[{"id":"a","text":"kubectl logs <pod> --previous, to read the container run that actually crashed"},{"id":"b","text":"kubectl delete pod <pod>, to force a clean restart"},{"id":"c","text":"kubectl scale deployment <name> --replicas=0, then scale back up"},{"id":"d","text":"kubectl get pods -o wide, to check which node it landed on"}],"correctId":"a","explanation":"Plain kubectl logs shows the container running now, which during a backoff has only just started and has produced nothing yet. The --previous flag reads the terminated instance, which is where the actual error is. Pairing it with kubectl describe pod gives you the events and the termination reason too.","wrongExplanations":{"b":"Deleting the Pod destroys the crashed container and its logs, then reproduces the identical failure. You lose the evidence and learn nothing.","c":"Scaling to zero and back has the same effect as deleting, with the added downside of taking the application fully offline while you do it.","d":"Node placement is worth knowing for scheduling problems, but a Pod in CrashLoopBackOff has already been scheduled successfully. The failure is inside the container."}}}]
93:["$","hr",null,{}]
94:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"frequently-asked-questions","children":"Frequently Asked Questions"}]
95:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"What is the difference between a Pod and a container?"}]}]
96:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"A container is a running image with its own filesystem and process. A Pod is the Kubernetes object that wraps one or more containers and gives them a shared network namespace, a shared IP, and a shared lifecycle on a single node. Kubernetes schedules Pods, not containers. You cannot ask it to place a container directly. In practice most Pods hold exactly one container, and the multi-container capability exists for processes that genuinely cannot be separated, such as an application and its log shipper."}]
97:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"Why is my Pod stuck in Pending?"}]}]
98:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Nothing has been able to schedule it. Run ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl describe pod <name>"}]," and read the Events section, which states the reason explicitly. The frequent causes are resource requests larger than any node can satisfy, no node matching a ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"nodeSelector"}]," or affinity rule, a node taint with no matching toleration on the Pod, and a PersistentVolumeClaim that has not bound. A message like ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"0/3 nodes are available: 3 Insufficient cpu"}]," means exactly what it says. Either lower the requests or add capacity."]}]
99:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":["What does ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/troubleshooting/crashloopbackoff","children":"CrashLoopBackOff"}]," mean and how do I fix it?"]}]}]
9a:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["It means the container starts, exits, and Kubernetes keeps restarting it with an increasing delay between attempts, up to five minutes. It is a symptom rather than a diagnosis. Read the logs of the run that failed with ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs <pod> --previous"}],", since plain ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl logs"}]," shows the instance that has only just started. The usual causes are a missing environment variable or config file, a dependency unavailable at startup, an ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"OOMKilled"}]," container whose memory limit is too low, and a liveness probe firing before the application has finished booting."]}]
9b:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"Can two containers in the same Pod use the same port?"}]}]
9c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["No. They share one network namespace and therefore one port space, so the second process to bind fails with "address already in use", exactly as it would on a single machine. Give each container a distinct port. This same shared namespace is what lets containers in a Pod reach each other on ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"localhost"}]," with no service discovery."]}]
9d:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"Should I ever create a Pod directly?"}]}]
9e:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Only for debugging. A bare Pod has no controller behind it, so if its node fails or it is evicted, nothing recreates it. There is no self-healing, no scaling, and no rolling update. For real workloads use a Deployment for stateless services, a StatefulSet where stable identity or storage matters, a DaemonSet for per-node agents, and a Job or CronJob for work that completes. A throwaway debugging Pod via ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl run --rm -it"}]," is the legitimate exception."]}]
9f:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":["Why does my Pod keep getting ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/troubleshooting/oomkilled","children":"OOMKilled"}],"?"]}]}]
a0:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["The container exceeded its memory limit and the kernel killed it. Confirm it with ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'"}],", which returns ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"OOMKilled"}],". Either the limit is genuinely too low for the workload, or the application has a memory leak that a higher limit will only postpone. Check actual usage with ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl top pod"}]," before raising the number, and be aware that runtimes with their own heap settings, the JVM especially, need to be told about the container limit or they will size their heap against the node's total memory instead."]}]
a2:["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl get pods -w"}]
a3:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Delete immediately"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl delete pod <name> --grace-period=0 --force"}]}]]}]
a4:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Full YAML as stored"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"kubectl get pod <name> -o yaml"}]}]]}]
28:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
26:null
2a:[["$","title","0",{"children":"What Is a Kubernetes Pod? Explained With Examples"}],["$","meta","1",{"name":"description","content":"Learn what a Kubernetes Pod is, what its containers share, why pod IPs change, how to write one, and how to read the statuses you see when debugging."}],["$","link","2",{"rel":"author","href":"https://devopslesson.com"}],["$","meta","3",{"name":"author","content":"DevOpsLesson Team"}],["$","meta","4",{"name":"keywords","content":"kubernetes pods, what is a pod, pod ip address, pod lifecycle phases, CrashLoopBackOff, ImagePullBackOff, pod vs container, kubectl describe pod, sidecar container, init container, kubernetes tutorial, learn kubernetes, kubernetes for beginners, k8s tutorial, kubernetes pods tutorial, kubernetes deployments tutorial, kubernetes services tutorial, kubectl tutorial, kubernetes architecture, kubernetes ingress tutorial, kubernetes configmaps secrets, kubernetes persistent volumes, kubernetes namespaces, kubernetes resource limits, kubernetes hpa autoscaling, helm kubernetes tutorial, kubernetes monitoring prometheus, minikube tutorial, kind kubernetes tutorial, container orchestration tutorial, kubernetes beginner guide 2026, kubernetes rolling updates, kubernetes rbac, kubernetes storage classes"}],["$","meta","5",{"name":"creator","content":"DevOpsLesson"}],["$","meta","6",{"name":"publisher","content":"DevOpsLesson"}],["$","meta","7",{"name":"robots","content":"index, follow"}],["$","meta","8",{"name":"googlebot","content":"index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1"}],["$","link","9",{"rel":"canonical","href":"https://devopslesson.com/tutorials/kubernetes/kubernetes-pods"}],["$","meta","10",{"name":"google-site-verification","content":"v1hGowZeYvsOScm3hhGtqYv7ZG_yiFG-YbjDK"}],["$","meta","11",{"property":"og:title","content":"Kubernetes Pods"}],["$","meta","12",{"property":"og:description","content":"Learn what a Kubernetes Pod is, what its containers share, why pod IPs change, how to write one, and how to read the statuses you see when debugging."}],["$","meta","13",{"property":"og:url","content":"https://devopslesson.com/tutorials/kubernetes/kubernetes-pods"}],["$","meta","14",{"property":"og:site_name","content":"DevOpsLesson"}],["$","meta","15",{"property":"og:image","content":"https://devopslesson.com/api/og?title=Kubernetes+Pods&eyebrow=Kubernetes+Tutorial"}],["$","meta","16",{"property":"og:image:width","content":"1200"}],["$","meta","17",{"property":"og:image:height","content":"630"}],["$","meta","18",{"property":"og:image:alt","content":"Kubernetes Pods"}],["$","meta","19",{"property":"og:type","content":"article"}],["$","meta","20",{"property":"article:published_time","content":"2026-07-13"}],["$","meta","21",{"property":"article:modified_time","content":"2026-09-12"}],["$","meta","22",{"property":"article:author","content":"Vishvesh Patel"}],["$","meta","23",{"property":"article:tag","content":"kubernetes pods"}],["$","meta","24",{"property":"article:tag","content":"what is a pod"}],["$","meta","25",{"property":"article:tag","content":"pod ip address"}],["$","meta","26",{"property":"article:tag","content":"pod lifecycle phases"}],["$","meta","27",{"property":"article:tag","content":"CrashLoopBackOff"}],["$","meta","28",{"property":"article:tag","content":"ImagePullBackOff"}],["$","meta","29",{"property":"article:tag","content":"pod vs container"}],["$","meta","30",{"property":"article:tag","content":"kubectl describe pod"}],["$","meta","31",{"property":"article:tag","content":"sidecar container"}],["$","meta","32",{"property":"article:tag","content":"init container"}],["$","meta","33",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","34",{"name":"twitter:title","content":"Kubernetes Pods"}],["$","meta","35",{"name":"twitter:description","content":"Learn what a Kubernetes Pod is, what its containers share, why pod IPs change, how to write one, and how to read the statuses you see when debugging."}],"$La6","$La7","$La8","$La9","$Laa","$Lab"]
ac:I[86869,[],"IconMark"]
a6:["$","meta","36",{"name":"twitter:image","content":"https://devopslesson.com/api/og?title=Kubernetes+Pods&eyebrow=Kubernetes+Tutorial"}]
a7:["$","link","37",{"rel":"shortcut icon","href":"/favicon.svg"}]
a8:["$","link","38",{"rel":"icon","href":"/icon","type":"image/png","sizes":"32x32"}]
a9:["$","link","39",{"rel":"icon","href":"/favicon.svg","type":"image/svg+xml"}]
aa:["$","link","40",{"rel":"apple-touch-icon","href":"/icon","sizes":"180x180","type":"image/png"}]
ab:["$","$Lac","41",{}
Learn what Kubernetes is and what problem it solves, how it differs from Docker and Docker Compose, the core objects to know, and whether you really need it.
Understand Kubernetes architecture: API server, etcd, scheduler, controller manager, kubelet and kube-proxy, and what happens when you run kubectl apply.
Compare ways to install Kubernetes: kind, Minikube, Docker Desktop, k3s, kubeadm and managed clusters, and pick the right one for learning or production.