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.

Resources for Further Learning