Pods are the unit Kubernetes actually schedules. A pod can be one app container, or it can be a small shared runtime with sidecars and init containers. The important boundary is the same either way: containers inside a pod share networking, storage, and fate.
What is a Pod?
A Pod is the smallest deployable unit in Kubernetes. It represents a single instance of a running process in your cluster and can contain one or more containers that share storage, network, and specifications for how to run.
Pod vs Container
Container World (Docker):
┌─────────────────────┐
│ Container 1 │
│ (nginx) │
└─────────────────────┘
┌─────────────────────┐
│ Container 2 │
│ (node-app) │
└─────────────────────┘
Kubernetes World (Pods):
┌─────────────────────────────────┐
│ Pod 1 │
│ ┌──────────┐ ┌──────────┐ │
│ │Container │ │Container │ │
│ │(nginx) │ │(sidecar) │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────┘
Key Pod Characteristics
- Single IP Address: All containers in a pod share the same IP address
- Shared Storage: Containers can share volumes and data
- Same Network Namespace: Containers can communicate via localhost
- Co-scheduled: All containers are scheduled together on the same node
- Shared Fate: If the pod dies, all containers die together
Pod Architecture and Components
Pod Structure
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
labels:
app: myapp
tier: frontend
spec:
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
value: "postgres://localhost:5432/mydb"
- name: sidecar
image: logging-agent:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log
volumes:
- name: shared-logs
emptyDir: {}
Pod Lifecycle Phases
# Check pod status
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app-pod 2/2 Running 0 5m
Pod Phases:
- Pending: Pod is created but not yet scheduled
- Running: Pod is scheduled and containers are running
- Succeeded: All containers completed successfully
- Failed: All containers terminated, at least one failed
- Unknown: Pod state cannot be determined
Creating and Managing Pods
Basic Pod Creation
# Create pod imperatively
kubectl run nginx --image=nginx:latest
# Create pod from YAML file
kubectl apply -f pod.yaml
# Get pod details
kubectl get pods
kubectl describe pod nginx
# Access pod logs
kubectl logs nginx
kubectl logs -f nginx # Follow logs
# Execute commands in pod
kubectl exec -it nginx -- /bin/bash
Pod YAML Configuration
# simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
environment: production
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
name: http
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
env:
- name: NGINX_HOST
value: "example.com"
- name: NGINX_PORT
value: "80"
Multi-Container Pod Patterns
1. Sidecar Pattern
A helper container that extends the main container’s functionality:
# sidecar-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp-with-logging
spec:
containers:
- name: webapp
image: myapp:latest
ports:
- containerPort: 8080
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-collector
image: fluentd:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: fluentd-config
mountPath: /etc/fluentd
volumes:
- name: logs
emptyDir: {}
- name: fluentd-config
configMap:
name: fluentd-config
2. Adapter Pattern
Transforms the main container’s interface:
# adapter-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
name: metrics-adapter
spec:
containers:
- name: application
image: legacy-app:latest
ports:
- containerPort: 8080
- name: metrics-exporter
image: prometheus-exporter:latest
ports:
- containerPort: 9090
env:
- name: TARGET_APP
value: "localhost:8080"
3. Ambassador Pattern
A proxy that handles external communication:
# ambassador-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
name: database-client
spec:
containers:
- name: app
image: myapp:latest
env:
- name: DATABASE_HOST
value: "localhost:5432"
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:latest
command: ["/cloud_sql_proxy",
"-instances=<project>:<region>:<instance>=tcp:5432",
"-credential_file=/secrets/cloudsql/credentials.json"]
volumeMounts:
- name: cloudsql-instance-credentials
mountPath: /secrets/cloudsql
readOnly: true
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
4. Init Container Pattern
Containers that run before the main application:
# init-container-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
- name: database-migration
image: migrate/migrate:latest
command: ['migrate', '-path', '/migrations', '-database', 'postgres://db/myapp', 'up']
volumeMounts:
- name: migrations
mountPath: /migrations
- name: wait-for-services
image: busybox:latest
command: ['sh', '-c', 'until nc -z database-service 5432; do sleep 1; done']
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
volumes:
- name: migrations
configMap:
name: database-migrations
Pod Networking
Network Model
# Get pod IP address
kubectl get pod nginx-pod -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# nginx-pod 1/1 Running 0 5m 10.244.1.5 node-1
Key Networking Concepts:
- Single IP per Pod: All containers share the same IP
- localhost Communication: Containers in same pod communicate via localhost
- Port Binding: Containers must bind to different ports within pod
- Network Policies: Control traffic between pods
Container Port Configuration
# networking-example.yaml
apiVersion: v1
kind: Pod
metadata:
name: multi-port-app
spec:
containers:
- name: web
image: nginx:latest
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
- name: metrics
image: prometheus/nginx-exporter:latest
ports:
- containerPort: 9113
name: metrics
env:
- name: SCRAPE_URI
value: "http://localhost/nginx_status"
Pod Storage and Volumes
Volume Types
# volumes-example.yaml
apiVersion: v1
kind: Pod
metadata:
name: storage-demo
spec:
containers:
- name: app
image: busybox:latest
command: ['sh', '-c', 'echo "Hello $(date)" >> /data/log.txt && sleep 3600']
volumeMounts:
- name: data
mountPath: /data
- name: reader
image: busybox:latest
command: ['sh', '-c', 'tail -f /data/log.txt']
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
Common Volume Types
# configmap-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: app-config
# secret-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: app-secrets
# hostpath-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
containers:
- name: app
image: busybox:latest
command: ['sh', '-c', 'echo "Host data" >> /host-data/file.txt && sleep 3600']
volumeMounts:
- name: host-data
mountPath: /host-data
volumes:
- name: host-data
hostPath:
path: /tmp/host-data
type: DirectoryOrCreate
Pod Health Checks and Probes
Types of Probes
# health-checks.yaml
apiVersion: v1
kind: Pod
metadata:
name: health-check-demo
spec:
containers:
- name: webapp
image: myapp:latest
ports:
- containerPort: 8080
# Liveness probe - checks if container is alive
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Readiness probe - checks if container is ready to serve traffic
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
# Startup probe - checks if application has started
startupProbe:
httpGet:
path: /startup
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
Probe Configuration Options
# Different probe types
livenessProbe:
# HTTP probe
httpGet:
path: /health
port: 8080
httpHeaders:
- name: X-Custom-Header
value: health-check
# OR TCP probe
tcpSocket:
port: 8080
# OR Exec probe
exec:
command:
- cat
- /tmp/healthy
# Common timing parameters
initialDelaySeconds: 30 # Wait before first probe
periodSeconds: 10 # How often to probe
timeoutSeconds: 5 # Timeout for each probe
successThreshold: 1 # Minimum consecutive successes
failureThreshold: 3 # Minimum consecutive failures
Pod Resource Management
Resource Requests and Limits
# resource-management.yaml
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: myapp:latest
resources:
requests:
memory: "256Mi" # Minimum guaranteed memory
cpu: "250m" # Minimum guaranteed CPU (250 millicores)
ephemeral-storage: "1Gi" # Storage requests
limits:
memory: "512Mi" # Maximum allowed memory
cpu: "500m" # Maximum allowed CPU
ephemeral-storage: "2Gi" # Storage limits
Quality of Service Classes
# Check QoS class
kubectl get pod resource-demo -o jsonpath='{.status.qosClass}'
# Output: Guaranteed, Burstable, or BestEffort
QoS Classes:
- Guaranteed: Both requests and limits set, and they’re equal
- Burstable: Requests set, limits may be higher or unset
- BestEffort: No requests or limits set
Pod Scheduling and Node Selection
Node Selection
# node-selection.yaml
apiVersion: v1
kind: Pod
metadata:
name: node-selection-demo
spec:
# Node selector - simple node selection
nodeSelector:
disktype: ssd
zone: us-west-1a
containers:
- name: app
image: myapp:latest
Node Affinity
# node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-demo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
containers:
- name: app
image: myapp:latest
Pod Affinity and Anti-Affinity
# pod-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-demo
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- webapp
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- database
topologyKey: kubernetes.io/hostname
containers:
- name: app
image: myapp:latest
Pod Disruption and Availability
Pod Disruption Budgets
# pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
spec:
minAvailable: 2 # Minimum pods that must be available
# maxUnavailable: 1 # Alternative: maximum pods that can be unavailable
selector:
matchLabels:
app: myapp
Priority and Preemption
# priority-class.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
description: "High priority class for critical pods"
# pod-with-priority.yaml
apiVersion: v1
kind: Pod
metadata:
name: high-priority-pod
spec:
priorityClassName: high-priority
containers:
- name: app
image: myapp:latest
Pod Troubleshooting
Common Issues and Solutions
# Pod stuck in Pending
kubectl describe pod <pod-name>
# Check events for scheduling issues
# Pod stuck in CrashLoopBackOff
kubectl logs <pod-name> --previous
# Check previous container logs
# Pod not ready
kubectl get pod <pod-name> -o yaml | grep -A 10 conditions
# Check readiness conditions
# Resource issues
kubectl top pod <pod-name>
# Check resource usage
# Network issues
kubectl exec -it <pod-name> -- nslookup kubernetes.default
# Test DNS resolution
Debugging Commands
# Get detailed pod information
kubectl get pod <pod-name> -o yaml
# Check events
kubectl get events --field-selector involvedObject.name=<pod-name>
# Access pod for debugging
kubectl exec -it <pod-name> -- /bin/bash
# Copy files from pod
kubectl cp <pod-name>:/path/to/file /local/path
# Port forward for testing
kubectl port-forward pod/<pod-name> 8080:80
# Run debugging pod
kubectl run debug --image=busybox --rm -it -- /bin/sh
Best Practices for Pods
1. Always Use Health Checks
livenessProbe:
httpGet:
path: /health
port: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
2. Set Resource Requests and Limits
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
3. Use Labels Consistently
metadata:
labels:
app: myapp
version: v1.0
environment: production
team: backend
4. Handle Graceful Shutdown
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10 && nginx -s quit"]
5. Use Namespaces for Organization
kubectl create namespace production
kubectl config set-context --current --namespace=production
6. Use Init Containers for Setup
initContainers:
- name: setup
image: busybox:latest
command: ['sh', '-c', 'until nc -z database 5432; do sleep 1; done']
7. Configure Proper Restart Policies
spec:
restartPolicy: Always # Options: Always, OnFailure, Never
What to Remember
- Pods are the smallest deployable units in Kubernetes
- Multi-container pods share network, storage, and lifecycle
- Init containers run before main containers for setup tasks
- Health probes ensure application availability and correctness
- Resource management prevents resource contention and ensures QoS
- Scheduling controls determine where pods run in the cluster
- Volume types provide various storage options for different use cases
Commands I Keep Nearby
# Pod Management
kubectl get pods # List pods
kubectl get pods --all-namespaces # List all pods
kubectl get pod <name> -o wide # Detailed pod info
kubectl describe pod <name> # Pod details and events
kubectl create -f pod.yaml # Create from YAML
kubectl apply -f pod.yaml # Apply/update from YAML
kubectl delete pod <name> # Delete pod
kubectl delete -f pod.yaml # Delete from YAML file
# Pod Operations
kubectl logs <pod-name> # View logs
kubectl logs -f <pod-name> # Follow logs
kubectl logs <pod-name> -c <container> # Specific container logs
kubectl logs <pod-name> --previous # Previous container logs
kubectl exec -it <pod-name> -- /bin/bash # Execute command
kubectl exec -it <pod-name> -c <container> -- bash # Specific container
kubectl cp <pod-name>:/remote/path /local/path # Copy from pod
kubectl cp /local/path <pod-name>:/remote/path # Copy to pod
kubectl port-forward pod/<name> 8080:80 # Port forward
kubectl attach -it <pod-name> # Attach to main process
# Pod Information
kubectl get pod <name> -o yaml # YAML output
kubectl get pod <name> -o json # JSON output
kubectl get pods --show-labels # Show labels
kubectl get pods --field-selector status.phase=Running # Filter by phase
kubectl get pods --sort-by=.metadata.creationTimestamp # Sort by creation time
# Health and Status
kubectl get events --field-selector involvedObject.name=<pod-name> # Events
kubectl top pod <name> # Resource usage
kubectl get pod <name> -o jsonpath='{.status.phase}' # Pod phase
kubectl get pod <name> -o jsonpath='{.status.podIP}' # Pod IP
kubectl get pod <name> -o jsonpath='{.spec.nodeName}' # Node name
# Labels and Annotations
kubectl label pod <name> app=myapp # Add label
kubectl label pod <name> app- # Remove label
kubectl annotate pod <name> description="My app" # Add annotation
kubectl get pods -l app=myapp # Filter by label
kubectl get pods --show-labels # Show all labels
Where This Connects Next
Pods explain why higher-level workloads exist. Deployments build on this unit to handle lifecycle, rolling updates, rollback, and availability without making you manage individual pods by hand.