Running one container is easy. Running hundreds in production, reliably, at scale? That’s where patterns emerge.
These aren’t Kubernetes-specific (though that’s where you’ll see them most). They’re fundamental approaches to composing containers into systems that actually work.
The Sidecar Pattern
A sidecar is a helper container that runs alongside your main application container, sharing the same pod/network namespace.
| |
Common sidecar use cases:
- Logging: Collect and ship logs (Fluentd, Filebeat)
- Proxies: Service mesh sidecars (Envoy, Linkerd)
- Security: mTLS termination, secret injection (Vault Agent)
- Monitoring: Metrics exporters, health checkers
Why sidecars?
- Single responsibility: your app doesn’t need logging logic
- Independent updates: update the sidecar without touching the app
- Language agnostic: works regardless of what your app is written in
The tradeoff: More containers = more resource overhead, more complexity in debugging.
The Ambassador Pattern
An ambassador is a specialized sidecar that proxies connections to external services, abstracting away the complexity.
| |
Your application connects to localhost:6379. The ambassador handles:
- Connection pooling
- Cluster-aware routing
- Failover logic
- TLS termination
Why ambassadors?
- App stays simple (just connect to localhost)
- Complex connection logic lives in a dedicated, reusable component
- Easy to swap implementations (different Redis proxy, different cloud)
The Adapter Pattern
An adapter transforms the interface of your container to match what the system expects.
Classic example: metrics format conversion.
| |
Why adapters?
- Integrate legacy systems without modifying them
- Standardize interfaces across heterogeneous systems
- Separation of concerns (app doesn’t need to know about Prometheus)
Init Containers
Init containers run before your main containers start. They run to completion, one at a time, in order.
| |
Common init container use cases:
- Wait for dependencies to be ready
- Run database migrations
- Fetch secrets or configuration
- Set up file permissions
- Clone git repos
Key property: If any init container fails, Kubernetes restarts the pod. The main containers won’t start until all init containers succeed.
The Operator Pattern
Operators are custom controllers that encode domain knowledge about running a specific application.
Instead of:
| |
With an operator:
| |
The operator handles:
- Provisioning primary and replicas
- Configuring replication
- Automated failover
- Backup scheduling
- Version upgrades
When to use operators:
- Stateful applications with complex lifecycle (databases, message queues)
- Applications requiring domain expertise to operate correctly
- When “kubectl apply” isn’t enough
When to avoid:
- Simple stateless apps (just use a Deployment)
- When the complexity isn’t justified
- When you don’t trust the operator’s quality
Job and CronJob Patterns
Not everything runs forever. Jobs handle run-to-completion workloads.
| |
Patterns for reliable jobs:
- Idempotency: jobs might run multiple times (retries, duplicates)
- Timeouts: set
activeDeadlineSecondsto kill stuck jobs - Cleanup:
ttlSecondsAfterFinishedremoves completed pods - Concurrency:
concurrencyPolicycontrols overlap (Allow/Forbid/Replace)
The DaemonSet Pattern
Run exactly one pod per node. Perfect for node-level services.
| |
Common DaemonSet uses:
- Log collectors (one per node to collect all logs)
- Monitoring agents (node metrics, GPU metrics)
- Network plugins (CNI)
- Storage plugins (CSI node drivers)
Anti-Patterns to Avoid
The “Everything Container”
| |
This defeats the purpose of containers. You lose:
- Independent scaling
- Independent updates
- Clear failure isolation
- Resource limits per component
Instead: One process per container, composed via pods.
Sidecar Explosion
| |
Each sidecar adds CPU, memory, and complexity. At some point, your sidecars cost more than your app.
Solutions:
- Combine related functionality (one observability sidecar, not three)
- Use node-level daemons where appropriate
- Question whether each sidecar is actually necessary
Treating Pods Like VMs
| |
Containers are ephemeral. Don’t install things at runtime. Don’t SSH in to fix things. Build it into the image or don’t have it.
Choosing the Right Pattern
| Pattern | Use When | Avoid When |
|---|---|---|
| Sidecar | Cross-cutting concerns (logging, proxy) | Simple apps, resource-constrained |
| Ambassador | Complex external connections | Direct connection is fine |
| Adapter | Interface mismatch | Can modify source |
| Init Container | Pre-start setup, ordering | Ongoing processes |
| Operator | Complex stateful apps | Stateless, simple lifecycle |
| DaemonSet | Per-node functionality | Per-app functionality |
| Job | Run-to-completion | Long-running services |
Patterns are tools, not rules. The best container architecture is the simplest one that meets your requirements. Start simple. Add patterns when the problem demands them, not before.