Foundational Patterns
Core patterns that form the building blocks of cloud-native applications in Kubernetes.
Health Probe Pattern
Ensure containers are healthy and ready to serve traffic using liveness, readiness, and startup probes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
# Liveness probe - restarts container if unhealthy
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Readiness probe - removes from service if not ready
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
# Startup probe - for slow starting containers
startupProbe:
httpGet:
path: /startup
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30 # 30 * 10 = 5 minutes max startup
Best Practices
- Use readiness probes to prevent traffic to unready pods
- Liveness probes should be simple and fast
- Startup probes for applications with long initialization
- Don't share the same endpoint for liveness and readiness
Resource Management Pattern
Define resource requests and limits to ensure proper scheduling and prevent resource starvation.
apiVersion: apps/v1
kind: Deployment
metadata:
name: resource-aware-app
spec:
template:
spec:
containers:
- name: app
image: myapp:v1
resources:
requests:
memory: "256Mi"
cpu: "250m"
ephemeral-storage: "1Gi"
limits:
memory: "512Mi"
cpu: "500m"
ephemeral-storage: "2Gi"
# QoS Class: Burstable (has requests and limits)
# Other QoS Classes:
# - Guaranteed: requests == limits for all resources
# - BestEffort: no requests or limits
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: namespace-quota
namespace: production
spec:
hard:
requests.cpu: "100"
requests.memory: 200Gi
limits.cpu: "200"
limits.memory: 400Gi
persistentvolumeclaims: "10"
services.loadbalancers: "2"
---
apiVersion: v1
kind: LimitRange
metadata:
name: mem-cpu-limit-range
namespace: production
spec:
limits:
- max:
cpu: "2"
memory: "2Gi"
min:
cpu: "100m"
memory: "128Mi"
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "250m"
memory: "256Mi"
type: Container
Declarative Deployment Pattern
Use declarative configurations and GitOps practices for reproducible deployments.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
patchesStrategicMerge:
- deployment-patch.yaml
configMapGenerator:
- name: app-config
envs:
- config.env
images:
- name: myapp
newTag: v2.1.0
replicas:
- name: web-app
count: 5
commonLabels:
app.kubernetes.io/name: myapp
app.kubernetes.io/version: v2.1.0
app.kubernetes.io/managed-by: kustomize
Init Container Pattern
Use init containers to prepare the environment before main containers start.
Database Migration
Run schema migrations before the app starts to ensure data compatibility.
Download Assets
Fetch configuration files, certificates, or static assets from external sources.
Wait for Dependencies
Block startup until required services (database, cache) are available.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-init
spec:
template:
spec:
initContainers:
# Wait for database
- name: wait-for-db
image: busybox:1.35
command: ['sh', '-c']
args:
- |
until nc -z postgres-service 5432; do
echo "Waiting for database..."
sleep 2
done
echo "Database is ready!"
# Run migrations
- name: db-migration
image: myapp:v1
command: ['python', 'manage.py', 'migrate']
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
# Download configuration
- name: fetch-config
image: busybox:1.35
command: ['wget']
args: ['-O', '/shared/config.yaml', 'http://config-server/config']
volumeMounts:
- name: shared-data
mountPath: /shared
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: shared-data
mountPath: /app/config
volumes:
- name: shared-data
emptyDir: {}
Structural Patterns
Sidecar Pattern
Extend and enhance the main container without changing it by deploying helper containers alongside.
When to Use Sidecars
Sidecars are ideal when you need to add cross-cutting concerns (logging, monitoring, security) without modifying application code. The sidecar shares the same lifecycle and network namespace as the main container.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-logging-sidecar
spec:
template:
spec:
containers:
# Main application container
- name: app
image: myapp:v1
ports:
- containerPort: 8080
volumeMounts:
- name: logs
mountPath: /var/log/app
# Logging sidecar
- name: log-forwarder
image: fluentd:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: fluentd-config
mountPath: /fluentd/etc
env:
- name: ELASTICSEARCH_HOST
value: "elasticsearch.logging.svc.cluster.local"
volumes:
- name: logs
emptyDir: {}
- name: fluentd-config
configMap:
name: fluentd-config
Service Mesh Sidecar (Envoy)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-envoy-sidecar
spec:
template:
metadata:
annotations:
sidecar.istio.io/inject: "true"
spec:
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
# Envoy sidecar (typically injected automatically by Istio)
- name: istio-proxy
image: istio/proxyv2:1.16.0
args:
- proxy
- sidecar
- --configPath=/etc/istio/proxy
- --serviceCluster=myapp
env:
- name: PILOT_AGENT_ADDR
value: istio-pilot.istio-system:15010
volumeMounts:
- name: istio-certs
mountPath: /etc/certs
securityContext:
runAsUser: 1337
Ambassador Pattern
Use a proxy container to handle external communication on behalf of the main container.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-ambassador
spec:
template:
spec:
containers:
# Main application
- name: app
image: myapp:v1
env:
- name: DATABASE_HOST
value: localhost # Connect through ambassador
- name: DATABASE_PORT
value: "5432"
# Ambassador container - handles database connections
- name: db-proxy
image: cloud-sql-proxy:latest
command:
- /cloud_sql_proxy
- -instances=project:region:instance=tcp:5432
- -credential_file=/secrets/cloudsql/key.json
volumeMounts:
- name: cloudsql-creds
mountPath: /secrets/cloudsql
readOnly: true
volumes:
- name: cloudsql-creds
secret:
secretName: cloudsql-key
Ambassador Use Cases
- Database connection pooling and proxying
- Rate limiting and circuit breaking
- Authentication and authorization
- Request/response transformation
- Service discovery and load balancing
Adapter Pattern
Transform or normalize the output of the main container to work with external systems.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-adapter
spec:
template:
spec:
containers:
# Legacy application with custom metrics format
- name: legacy-app
image: legacy-app:v1
ports:
- containerPort: 8080
volumeMounts:
- name: metrics
mountPath: /metrics
# Adapter to convert metrics to Prometheus format
- name: metrics-adapter
image: prometheus-adapter:latest
ports:
- containerPort: 9090
name: metrics
volumeMounts:
- name: metrics
mountPath: /input-metrics
command:
- /adapter
- --input-format=custom
- --output-format=prometheus
- --input-path=/input-metrics
- --port=9090
volumes:
- name: metrics
emptyDir: {}
Multi-Container Patterns Comparison
| Pattern | Purpose | Communication | Example Use Cases |
|---|---|---|---|
| Sidecar | Extend functionality | Shared volumes, localhost | Logging, monitoring, security |
| Ambassador | Proxy external services | Network proxy | Database proxy, API gateway |
| Adapter | Standardize interfaces | Data transformation | Metrics conversion, log formatting |
| Init Container | Setup and initialization | Sequential execution | Database migration, configuration |
Behavioral Patterns
Batch Job Pattern
Run finite workloads to completion using Jobs and CronJobs.
# Parallel batch processing with work queue
apiVersion: batch/v1
kind: Job
metadata:
name: batch-processor
spec:
parallelism: 5
completions: 100
backoffLimit: 3
activeDeadlineSeconds: 3600
template:
spec:
containers:
- name: worker
image: batch-processor:v1
env:
- name: QUEUE_URL
value: "redis://redis:6379/0"
command:
- python
- worker.py
args:
- --queue=tasks
- --timeout=300
restartPolicy: OnFailure
---
# Scheduled batch job
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: report-generator
image: reporter:v1
command: ["python", "generate_report.py"]
env:
- name: REPORT_TYPE
value: "daily"
- name: OUTPUT_BUCKET
value: "s3://reports/daily/"
restartPolicy: OnFailure
Circuit Breaker Pattern
Prevent cascading failures by failing fast when downstream services are unavailable.
Why Circuit Breakers Matter
Without circuit breakers, a single failing service can bring down your entire system. The pattern transitions between three states: Closed (normal), Open (failing fast), and Half-Open (testing recovery).
# Istio DestinationRule with Circuit Breaker
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: circuit-breaker
spec:
host: myservice
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 100
http2MaxRequests: 100
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
minHealthPercent: 30
Leader Election Pattern
Ensure only one instance performs certain operations using distributed coordination.
apiVersion: apps/v1
kind: Deployment
metadata:
name: singleton-processor
spec:
replicas: 3 # Multiple replicas for HA
template:
spec:
serviceAccountName: leader-election
containers:
- name: app
image: singleton-app:v1
env:
- name: ELECTION_NAME
value: "singleton-processor-leader"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
command:
- /app
args:
- --leader-elect=true
- --leader-elect-lease-duration=15s
- --leader-elect-renew-deadline=10s
- --leader-elect-retry-period=2s
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: leader-election
rules:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Retry and Backoff Pattern
Handle transient failures gracefully with exponential backoff.
# Service mesh retry configuration
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: retry-policy
spec:
hosts:
- myservice
http:
- match:
- uri:
prefix: /
route:
- destination:
host: myservice
retry:
attempts: 3
perTryTimeout: 30s
retryOn: 5xx,reset,connect-failure,refused-stream
timeout: 90s
Configuration Patterns
Immutable Configuration Pattern
Use immutable ConfigMaps and Secrets with versioning for safer updates.
# Immutable ConfigMap with version suffix
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v2
immutable: true # Cannot be modified after creation
data:
app.properties: |
server.port=8080
database.pool.size=10
cache.ttl=3600
feature-flags.json: |
{
"newFeature": true,
"betaFeature": false
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: myapp:v2
envFrom:
- configMapRef:
name: app-config-v2 # Reference specific version
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: app-config-v2
Secret Management Pattern
Securely manage sensitive data using Kubernetes Secrets and external secret stores.
# External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "demo"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: database-secret
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: database/credentials
property: username
- secretKey: password
remoteRef:
key: database/credentials
property: password
Hot Reload Configuration Pattern
Enable dynamic configuration updates without pod restarts.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hot-reload-app
spec:
template:
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: config
mountPath: /config
env:
- name: CONFIG_RELOAD_ENABLED
value: "true"
# Config reloader sidecar
- name: config-reloader
image: jimmidyson/configmap-reload:v0.5.0
args:
- --volume-dir=/config
- --webhook-url=http://localhost:8080/reload
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: app-config
GitOps Pattern
Declarative deployment using Git as the single source of truth.
# ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/k8s-config
targetRevision: HEAD
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Common Anti-Patterns to Avoid
Using Latest Tag
The 'latest' tag is mutable and can lead to inconsistent deployments. Always use specific version tags or image digests.
# BAD - Unpredictable!
spec:
containers:
- name: app
image: myapp:latest
# GOOD - Specific version
spec:
containers:
- name: app
image: myapp:v1.2.3
# OR use digest for immutability
# image: myapp@sha256:abc123...
Hardcoding Configuration
Embedding environment-specific values in container images makes them non-portable. Externalize config using ConfigMaps and Secrets.
# GOOD - Externalized configuration
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: db-config
key: host
- name: API_KEY
valueFrom:
secretKeyRef:
name: api-secrets
key: key
Not Setting Resource Limits
Pods without resource constraints can consume unlimited resources, causing node instability. Always set requests and limits.
Running as Root
Containers should never run with root privileges. Use security contexts to enforce non-root execution.
# GOOD - Non-root with restricted permissions
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:v1
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
Singleton Pods
Creating naked pods without controllers leads to no self-healing. Always use a Deployment, StatefulSet, or DaemonSet.
Best Practices Checklist
- Health Checks: Always implement liveness and readiness probes
- Resource Management: Set requests and limits for all containers
- Configuration: Externalize config using ConfigMaps and Secrets
- Security: Run as non-root, use network policies, scan images
- Observability: Implement logging, metrics, and tracing
- Resilience: Use circuit breakers, retries, and timeouts
- Scalability: Design for horizontal scaling, use HPA/VPA
- GitOps: Use declarative configuration and version control
Practice Problems
Easy Add Health Probes to a Deployment
Given a basic Deployment with an nginx container on port 80, add appropriate liveness and readiness probes.
Nginx serves a default page at /. Use httpGet probes targeting port 80. Set reasonable initial delays and periods.
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Easy Set Resource Requests and Limits
Configure a container with 256Mi memory request, 512Mi memory limit, 250m CPU request, and 500m CPU limit.
Use the resources field under the container spec, with requests and limits sub-fields.
containers:
- name: app
image: myapp:v1
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Medium Design a Logging Sidecar
Create a pod spec with a main application that writes logs to /var/log/app and a fluentd sidecar that reads and forwards them.
Use an emptyDir volume shared between both containers. Mount at the same path or different paths depending on the sidecar's config.
spec:
containers:
- name: app
image: myapp:v1
volumeMounts:
- name: log-volume
mountPath: /var/log/app
- name: log-forwarder
image: fluentd:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {}
Medium Init Container for Database Readiness
Write an init container that waits for a PostgreSQL service called "postgres" on port 5432 before the main app starts.
Use a busybox image with an until loop and nc (netcat) to check TCP connectivity.
initContainers:
- name: wait-for-db
image: busybox:1.35
command: ['sh', '-c']
args:
- |
until nc -z postgres 5432; do
echo "Waiting for postgres..."
sleep 2
done
echo "PostgreSQL is ready!"
containers:
- name: app
image: myapp:v1
Hard Implement a Complete GitOps Pipeline
Design an ArgoCD Application that auto-syncs from a Git repo, with Kustomize overlays for staging and production, retry policies, and self-healing enabled.
Use spec.syncPolicy.automated with prune and selfHeal. Set the source path to the appropriate overlay directory. Add retry with backoff configuration.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/k8s-config
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Hard Configure Circuit Breaker with Istio
Create a DestinationRule that limits connections to 100, pending requests to 50, ejects unhealthy hosts after 3 consecutive 5xx errors, and keeps them ejected for 60 seconds.
Use trafficPolicy with connectionPool for connection limits and outlierDetection for ejection behavior.
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-circuit-breaker
spec:
host: myservice
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
http2MaxRequests: 100
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 60s
maxEjectionPercent: 50