Pod is K8s' smallest schedulable unit. Master state transitions and inter-container patterns for reliability. Subtle differences in startup/shutdown ordering and probe configs directly affect production behavior.
Pod Lifecycle State Machine
Creation → PodScheduled → Init Containers run serially → Main containers start → PostStart hooks → readiness must pass before service sends traffic → liveness failures trigger restart → deletion sends SIGTERM → preStop fires → terminationGracePeriodSeconds hard kill.
apiVersion: v1
kind: Pod
metadata: { name: web-app-pod, labels: { app: web } }
spec:
terminationGracePeriodSeconds: 60
shareProcessNamespace: true
serviceAccountName: web-sa
initContainers:
- name: wait-for-db
image: bitnami/postgresql:16
command: ["/bin/sh", "-c", "until pg_isready -h postgres -p 5432 -U app; do echo wait; sleep 3; done;"]
- name: migrate-db
image: registry.example.com/app:${APP_VERSION}
command: ["alembic", "upgrade", "head"]
envFrom: [{ configMapRef: { name: web-config } }]
volumeMounts: [{ name: app-secret, mountPath: /run/secrets, readOnly: true }]
- name: fetch-assets
image: amazon/aws-cli
command: ["aws", "s3", "sync", "s3://app-assets/static/", "/var/www/static/", "--delete"]
volumeMounts: [{ name: static-assets, mountPath: /var/www/static }]
containers:
- name: app
image: registry.example.com/app:${APP_VERSION}
ports: [{ containerPort: 8000, name: http }]
lifecycle:
postStart: { exec: { command: ["/bin/sh", "-c", "echo started >> /var/log/app/boot.log"] } }
preStop:
exec: { command: ["/bin/sh", "-c", "sleep 5; kill -SIGTERM 1; while pidof app >/dev/null; do sleep 1; done;"] }
readinessProbe: { httpGet: { path: /health/ready, port: http }, initialDelaySeconds: 3, periodSeconds: 5 }
livenessProbe: { httpGet: { path: /health/live, port: http }, initialDelaySeconds: 15, periodSeconds: 20, failureThreshold: 3 }
startupProbe: { httpGet: { path: /health/started, port: http }, periodSeconds: 5, failureThreshold: 30 }
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 1000m, memory: 1Gi }
volumeMounts:
- { name: static-assets, mountPath: /app/static, readOnly: true }
- { name: app-logs, mountPath: /var/log/app }
- name: envoy-sidecar
image: envoyproxy/envoy:v1.30
lifecycle:
preStop: { exec: { command: ["/bin/sh", "-c", "curl -X POST http://localhost:15000/healthcheck/fail; sleep 10;"] } }
- name: fluentbit-sidecar
image: fluent/fluent-bit:3.0
volumeMounts: [{ name: app-logs, mountPath: /var/log/app, readOnly: true }]
volumes:
- { name: static-assets, emptyDir: {} }
- { name: app-logs, emptyDir: {} }
- { name: app-secret, secret: { secretName: web-secret } }
Init Containers and Sidecar Patterns
Init Containers execute in order; only when all succeed do mains start. Perfect for DB waits, migrations, asset pulls. Sidecars share network/volumes with the main app, providing non-intrusive proxies (istio/envoy) or log shipping (fluentbit).
| Container Type | Startup Timing | Relationship to App | Use Cases |
|---|---|---|---|
| Init Container | Before main, serialized | Runs-once success required | DB waits, migrations |
| Sidecar | Parallel with main | Same lifecycle, exits together | Service Mesh proxy |
| Sidecar | Parallel with main | Same lifecycle | Log collector fluentbit |
| Main App | After init succeeds | Business process | Web API server |
| Ephemeral | kubectl debug any time | Temporary injection | Debug broken pods |
Best Practices
Always add startupProbe for slow-booting apps. Sleep a few seconds in preStop to let endpoints deregister from services before killing process - prevents race where traffic arrives as app dies. Readiness should check DB dependencies, not just process liveness.