Pods & Containers

Easy 20 min read

Understanding Pods

Why Pods Matter

The Atomic Unit: Pods are the smallest deployable units in Kubernetes - not containers!

Key Insight: A Pod can contain multiple containers that share storage and network.

Real Impact: Understanding Pods is crucial for designing scalable applications.

Real-World Analogy

Think of a Pod as an apartment:

  • Pod = The apartment unit
  • Containers = Roommates sharing the apartment
  • Network = Shared WiFi (localhost communication)
  • Volumes = Shared storage closets
  • Node = The apartment building

Pod Architecture

Pod
Main Container
nginx:latest
Sidecar Container
logging-agent
Shared: Network (localhost) | Storage (volumes) | IPC

Basic Pod YAML Structure

simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
  labels:
    app: web
    environment: development
spec:
  containers:
  - name: web-container
    image: nginx:1.21
    ports:
    - containerPort: 80
      name: http
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Key Concepts

  • One IP per Pod: All containers in a Pod share the same network namespace
  • Ephemeral by Design: Pods are meant to be disposable and recreatable
  • Co-location: Containers in a Pod are always scheduled on the same node
  • Shared Lifecycle: All containers start and stop together

Pod Lifecycle

Pod Phases

Pend
Pending
Run
Running
OK
Succeeded
Fail
Failed
Phase Description What's Happening
Pending Pod accepted but not running Downloading images, waiting for resources, scheduling in progress
Running Pod bound to node At least one container running, may be starting or restarting
Succeeded All containers terminated successfully Job completed, exit code 0, won't be restarted
Failed All containers terminated, at least one failed Non-zero exit code, system error, resource issues
Unknown Pod state cannot be determined Node communication lost, network issues

Container States

check-pod-status.sh
# Get pod status
kubectl get pods

# Detailed pod information
kubectl describe pod my-pod

# Get pod events
kubectl get events --field-selector involvedObject.name=my-pod

# Watch pod status changes
kubectl get pods -w

# Check container logs
kubectl logs my-pod -c container-name

# Previous container logs (after restart)
kubectl logs my-pod -c container-name --previous

Init Containers

pod-with-init.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-init
spec:
  # Init containers run before app containers
  initContainers:
  - name: init-db
    image: busybox:1.28
    command: ['sh', '-c', 'until nc -z db-service 5432; do echo waiting for db; sleep 2; done;']
  - name: init-cache
    image: busybox:1.28
    command: ['sh', '-c', 'until nc -z cache-service 6379; do echo waiting for cache; sleep 2; done;']
  # App containers start after all init containers complete
  containers:
  - name: app
    image: myapp:latest
    ports:
    - containerPort: 8080

Init Container Rules

  • Run sequentially (one at a time)
  • Must complete successfully before app containers start
  • Perfect for setup tasks (database migrations, waiting for services)
  • Share volumes with app containers
  • Have separate resource limits

Multi-Container Patterns

Sidecar Pattern

Helper container that enhances the main container: log collection, monitoring agents, proxy/service mesh.

Ambassador Pattern

Proxy container for network communication: API gateway, database proxy, load balancing.

Adapter Pattern

Standardizes output from main container: log formatting, metrics conversion, data transformation.

Sidecar Pattern Example

sidecar-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-logging
spec:
  containers:
  # Main application container
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: logs
      mountPath: /var/log/app

  # Sidecar container for log shipping
  - name: log-shipper
    image: fluentd:latest
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
      readOnly: true
    env:
    - name: ELASTICSEARCH_HOST
      value: "elasticsearch.logging.svc.cluster.local"

  volumes:
  - name: logs
    emptyDir: {}

Ambassador Pattern Example

ambassador-pattern.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-with-proxy
spec:
  containers:
  # Main application
  - name: app
    image: myapp:latest
    env:
    - name: DATABASE_HOST
      value: "localhost:5432"  # Connect via ambassador

  # Ambassador container - database proxy
  - name: db-proxy
    image: cloudsql-proxy:latest
    command:
    - "/cloud_sql_proxy"
    - "-instances=project:region:instance=tcp:5432"

Hands-On Examples

Create Your First Multi-Container Pod

Let's create a web application with nginx and a sidecar for logs.

Step 1: Create the Pod

web-with-sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web-app
  labels:
    app: web
spec:
  containers:
  # Web server
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
    - name: logs
      mountPath: /var/log/nginx

  # Log reader sidecar
  - name: log-reader
    image: busybox
    command: ['/bin/sh']
    args: ['-c', 'tail -f /var/log/nginx/access.log']
    volumeMounts:
    - name: logs
      mountPath: /var/log/nginx

  # Content updater
  - name: content-updater
    image: busybox
    command: ['/bin/sh']
    args:
    - -c
    - |
      while true; do
        echo "<h1>Current Time: $(date)</h1>" > /html/index.html
        sleep 10
      done
    volumeMounts:
    - name: html
      mountPath: /html

  volumes:
  - name: html
    emptyDir: {}
  - name: logs
    emptyDir: {}

Step 2: Deploy and Test

deploy-commands.sh
# Create the pod
kubectl apply -f web-with-sidecar.yaml

# Check pod status
kubectl get pod web-app

# Check all containers in the pod
kubectl get pod web-app -o jsonpath='{.spec.containers[*].name}'

# Port forward to access the web server
kubectl port-forward pod/web-app 8080:80

# In another terminal, check logs from different containers
kubectl logs web-app -c nginx
kubectl logs web-app -c log-reader
kubectl logs web-app -c content-updater

# Exec into a specific container
kubectl exec -it web-app -c nginx -- /bin/sh

Step 3: Resource Management

pod-with-resources.yaml
apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        memory: "64Mi"     # Minimum guaranteed
        cpu: "250m"        # 0.25 CPU cores
      limits:
        memory: "128Mi"    # Maximum allowed
        cpu: "500m"        # 0.5 CPU cores
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 5
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 3

Troubleshooting Pods

Common Pod Issues

ImagePullBackOff

Problem: Kubernetes cannot pull the container image.

Common Causes: Image doesn't exist or typo in name, private registry requires authentication, network issues.

fix-image-pull.sh
# Check events
kubectl describe pod pod-name

# Verify image name
kubectl get pod pod-name -o jsonpath='{.spec.containers[*].image}'

# Create image pull secret for private registry
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=pass

CrashLoopBackOff

Problem: Container keeps crashing and restarting.

Common Causes: Application error/crash, missing configuration, insufficient resources.

fix-crash-loop.sh
# Check logs
kubectl logs pod-name --previous

# Check events
kubectl describe pod pod-name

# Get more details
kubectl get pod pod-name -o yaml | grep -A 10 lastState

Pending Pod

Problem: Pod stuck in Pending state.

Common Causes: Insufficient resources on nodes, node selector/affinity not matching, PVC not bound.

fix-pending.sh
# Check events
kubectl describe pod pod-name

# Check node resources
kubectl top nodes

# Check PVC status
kubectl get pvc

# Check scheduling constraints
kubectl get pod pod-name -o yaml | grep -A 5 nodeSelector

Debugging Commands

debugging.sh
# Get pod details with wide output
kubectl get pods -o wide

# Describe pod for events and status
kubectl describe pod pod-name

# Get pod logs
kubectl logs pod-name
kubectl logs pod-name -c container-name  # Specific container
kubectl logs pod-name --previous          # Previous instance
kubectl logs pod-name --tail=50           # Last 50 lines
kubectl logs pod-name -f                  # Follow logs

# Execute commands in pod
kubectl exec pod-name -- command
kubectl exec -it pod-name -- /bin/bash

# Copy files to/from pod
kubectl cp pod-name:/path/to/file ./local-file
kubectl cp ./local-file pod-name:/path/to/file

# Port forwarding for debugging
kubectl port-forward pod-name 8080:80

# Get pod YAML
kubectl get pod pod-name -o yaml

# Debug with ephemeral container (K8s 1.23+)
kubectl debug pod-name -it --image=busybox

Practice Exercises

Easy Create a Multi-Container Pod

Create a pod with nginx as the main container and haproxy as a reverse proxy. Configure shared volumes for configuration and set appropriate resource limits.

Use shared volumes between containers and remember that containers in a Pod communicate via localhost.

apiVersion: v1
kind: Pod
metadata:
  name: web-with-proxy
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "200m"
    volumeMounts:
    - name: config
      mountPath: /etc/nginx/conf.d

  - name: haproxy
    image: haproxy:alpine
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "64Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "200m"

  volumes:
  - name: config
    configMap:
      name: nginx-config

Medium Implement Init Container

Use init containers for setup tasks: create an init container that downloads content and shares it with the main container via a volume.

Init containers run sequentially before app containers start. Use an emptyDir volume to share data between init and app containers.

apiVersion: v1
kind: Pod
metadata:
  name: web-with-content
spec:
  initContainers:
  - name: content-downloader
    image: busybox
    command:
    - wget
    - "-O"
    - "/content/index.html"
    - "https://example.com/index.html"
    volumeMounts:
    - name: content
      mountPath: /content

  containers:
  - name: web-server
    image: nginx:alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - name: content
      mountPath: /usr/share/nginx/html

  volumes:
  - name: content
    emptyDir: {}

Hard Implement Health Checks

Add liveness, readiness, and startup probes to a pod. Configure appropriate delays and thresholds for each probe type.

Use httpGet for liveness, tcpSocket for readiness, and httpGet for startup. Set initialDelaySeconds higher for liveness than readiness to give the app time to start.

apiVersion: v1
kind: Pod
metadata:
  name: app-with-probes
spec:
  containers:
  - name: app
    image: nginx
    ports:
    - containerPort: 80
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 15
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 3
    readinessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
      successThreshold: 1
      failureThreshold: 3
    startupProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 0
      periodSeconds: 10
      failureThreshold: 30

Additional Challenges

  • Create a pod with all three multi-container patterns
  • Implement a pod with persistent volume claims
  • Create a debugging pod with network tools
  • Build a pod with environment-specific configuration