01 - Kubernetes Core Concept - Pod
Understand core Kubernetes building blocks Pod with real-world examples, YAML manifests, and hands-on troubleshooting scenarios.
Kubernetes Core Concepts — Pod
Before you can deploy real applications on Kubernetes, you need a solid understanding of the three most fundamental building blocks: Pods, ReplicaSets, and Deployments. These three objects are layered on top of each other — Deployments manage ReplicaSets, and ReplicaSets manage Pods. Understanding each layer individually will make you a much more effective Kubernetes engineer and will directly help you debug problems faster.
This guide covers what each resource is, why it exists, where it is used in real environments, a full YAML manifest breakdown, common real-world troubleshooting scenarios, and the exact kubectl commands used to resolve them.
1.1 What Is a Pod?
A Pod is the smallest and most basic deployable unit in Kubernetes. It is important to understand that Kubernetes does not run containers directly — it runs Pods, and each Pod contains one or more containers that share the same network namespace and storage.
Think of a Pod as a logical host. Just like how a virtual machine can run multiple processes, a Pod can run multiple containers that:
- Share the same IP address and port space
- Can communicate with each other via
localhost - Can share mounted volumes (storage)
- Are always scheduled together on the same node
In practice, most Pods run a single container. Multi-container Pods are used for specific patterns like sidecar logging, service mesh proxies (like Envoy in Istio), or init containers that do setup work before the main container starts.
┌─────────────────────────────────────────────┐
│ POD │
│ IP: 10.244.1.15 │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Main App │ │ Sidecar Logger │ │
│ │ Container │ │ Container │ │
│ │ :8080 │ │ (reads logs) │ │
│ └──────────────┘ └──────────────────┘ │
│ │
│ Shared Volume: /var/log/app │
└─────────────────────────────────────────────┘
1.2 Why Is a Pod Used?
Kubernetes introduces the Pod abstraction for several important reasons:
| Reason | Explanation |
|---|---|
| Co-location | Tightly coupled containers (e.g., app + log shipper) need to run on the same node and share resources |
| Shared networking | Containers within a Pod communicate over localhost — no service discovery needed between them |
| Atomic scheduling | Kubernetes schedules an entire Pod, not individual containers — ensures all containers land on the same node |
| Lifecycle management | All containers in a Pod start, stop, and restart together |
| Resource grouping | CPU and memory limits can be set per container within the Pod |
1.3 Where Are Pods Used?
Pods appear in virtually every Kubernetes workload:
- Web application containers — your Node.js, Python Flask, or Java Spring Boot app
- Database instances — a PostgreSQL or MySQL container (usually with a PersistentVolume)
- Batch jobs — one-off data processing containers (via Job or CronJob resources)
- System components — kube-dns, kube-proxy, metrics-server all run as Pods in the
kube-systemnamespace - Sidecar patterns — Envoy proxy (Istio), Fluentd log shipper, Vault agent running alongside the main app
1.4 Pod YAML Manifest — Full Breakdown
apiVersion: v1 # Core API group for Pods
kind: Pod # Resource type
metadata:
name: nginx-pod # Unique name within the namespace
namespace: default # Namespace this pod belongs to
labels:
app: nginx # Label for selectors and grouping
env: production
version: "1.25"
annotations:
description: "Frontend nginx pod for TechWithDB"
spec:
containers:
- name: nginx # Container name (must be unique within the pod)
image: nginx:1.25 # Docker image with tag (always pin the tag!)
ports:
- containerPort: 80 # Port the container listens on (informational)
resources:
requests: # Minimum resources guaranteed to the container
memory: "64Mi"
cpu: "100m" # 100 millicores = 0.1 CPU core
limits: # Maximum resources the container can use
memory: "128Mi"
cpu: "250m"
env: # Environment variables
- name: NGINX_HOST
value: "techwithdb.com"
- name: LOG_LEVEL
value: "info"
livenessProbe: # Kubernetes restarts container if this fails
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10 # Wait 10s before first check
periodSeconds: 15 # Check every 15s
readinessProbe: # Pod removed from service endpoints if this fails
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
volumes:
- name: config-volume # Named volume — referenced by volumeMounts above
configMap:
name: nginx-config # A ConfigMap that holds nginx config files
restartPolicy: Always # Always | OnFailure | Never
nodeSelector:
disk: ssd # Only schedule on nodes with this label
1.5 Common Pod Phases and Status
When you run kubectl get pods, the STATUS column can show various states. Here is what each means:
| Status | Meaning |
|---|---|
Pending | Pod accepted by cluster but not yet scheduled or image not yet pulled |
Running | Pod bound to a node and at least one container is running |
Succeeded | All containers exited with status 0 (common for Jobs) |
Failed | All containers have stopped and at least one exited with non-zero status |
CrashLoopBackOff | Container is crashing repeatedly — Kubernetes keeps restarting it with increasing delay |
ImagePullBackOff | Kubernetes cannot pull the container image |
OOMKilled | Container exceeded its memory limit and was killed by the OS |
Terminating | Pod is being deleted |
ContainerCreating | Container is being created (image pulling or volume mounting in progress) |
Init:0/1 | Init container has not completed yet |
1.6 Real-World Troubleshooting - Pod
Scenario 1: Pod is stuck in CrashLoopBackOff
Situation: You deploy an application pod and notice it shows CrashLoopBackOff. This is one of the most common issues you will face.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 CrashLoopBackOff 5 4m
Step 1 — Check pod events to understand why it crashed:
kubectl describe pod myapp-pod
Look at the Events section at the bottom of the output:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m default-scheduler Successfully assigned default/myapp-pod to node1
Normal Pulled 5m kubelet Successfully pulled image "myapp:latest"
Normal Created 5m kubelet Created container myapp
Normal Started 5m kubelet Started container myapp
Warning BackOff 3m (x5 over 4m) kubelet Back-off restarting failed container
Step 2 — Check the container logs:
# Current logs
kubectl logs myapp-pod
# Logs from the PREVIOUS crashed instance (most useful for CrashLoopBackOff)
kubectl logs myapp-pod --previous
Example log output revealing the issue:
Error: Cannot connect to database at db-service:5432
Connection refused: ECONNREFUSED
Application exiting with code 1
Root Cause: The application exits immediately if it cannot connect to the database on startup. The database service does not exist yet.
Step 3 — Fix the issue:
Option A — Create the missing database service first Option B — Add a startup retry mechanism in the application Option C — Add an init container to wait for the DB to be ready:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 3; done']
Scenario 2: Pod stuck in Pending state
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-pod 0/1 Pending 0 8m
Step 1 — Describe the pod:
kubectl describe pod nginx-pod
Events section shows:
Warning FailedScheduling 8m default-scheduler
0/1 nodes are available: 1 Insufficient memory. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
Root Cause: The node does not have enough memory to satisfy the pod’s resource request.
Fix options:
# Option 1: Check current node resource usage
kubectl describe node minikube | grep -A 10 "Allocated resources"
# Option 2: Reduce the memory request in the pod manifest
# Change requests.memory from "512Mi" to "128Mi"
# Option 3: Check if there are resource quotas applied to the namespace
kubectl get resourcequota -n default
kubectl describe resourcequota -n default
Scenario 3: ImagePullBackOff
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
bad-pod 0/1 ImagePullBackOff 0 2m
kubectl describe pod bad-pod
Events:
Warning Failed 90s kubelet Failed to pull image "myapp:v2.5-typo":
rpc error: code = Unknown desc = Error response from daemon:
manifest for myapp:v2.5-typo not found: manifest unknown
Root Cause: The image tag v2.5-typo does not exist in the registry.
Fix:
# Edit the pod to fix the image tag
kubectl edit pod bad-pod
# Change image: myapp:v2.5-typo to image: myapp:v2.5
# Or delete and recreate with corrected YAML
kubectl delete pod bad-pod
kubectl apply -f corrected-pod.yaml
Note: You cannot update the image of a running pod directly in most cases — pods are largely immutable. This is exactly why Deployments (covered below) are the preferred way to manage pods.
Scenario 4: OOMKilled — Out of Memory
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 OOMKilled 3 10m
kubectl describe pod myapp-pod | grep -A 5 "Last State"
Output:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Mon, 20 Jan 2026 10:00:00 +0530
Finished: Mon, 20 Jan 2026 10:00:45 +0530
Root Cause: The container exceeded its memory limit (limits.memory) and was killed by the Linux OOM (Out Of Memory) killer.
Fix:
# Increase the memory limit in your manifest
resources:
requests:
memory: "256Mi"
limits:
memory: "512Mi" # was 128Mi — application needs more memory
kubectl apply -f pod.yaml