Cloud Native Architecture Cheat Sheet
Covers the twelve-factor app principles, microservices patterns, containers, and the CNCF landscape that define cloud native systems.
Twelve-Factor App (selected)
Core methodology for building cloud native applications.
- Codebase- One codebase tracked in version control, many deploys
- Config- Store config in environment variables, never in code
- Dependencies- Explicitly declare and isolate dependencies (no reliance on system-wide packages)
- Backing Services- Treat databases, queues, caches as attached resources via config, swappable without code change
- Processes- Execute the app as stateless processes; persist state in a backing store
- Disposability- Fast startup and graceful shutdown to support elastic scaling
- Logs- Treat logs as event streams, write to stdout, let the environment aggregate them
CNCF Landscape (key projects)
Common tools associated with cloud native architecture.
- Kubernetes- Container orchestration, the de facto cloud native runtime
- Envoy- High-performance proxy used as the data plane in many service meshes
- Istio / Linkerd- Service mesh implementations for traffic management, mTLS, and observability
- Prometheus- Metrics collection and alerting standard for cloud native systems
- Helm- Package manager for Kubernetes, templating manifests into 'charts'
- OpenTelemetry- Vendor-neutral instrumentation standard for traces, metrics, and logs
Health/Readiness Endpoint Pattern
A minimal Express health check enabling orchestrators to manage lifecycle.
app.get('/healthz', (req, res) => { res.status(200).json({ status: 'ok' }); // liveness});app.get('/readyz', async (req, res) => { const dbOk = await checkDatabaseConnection(); res.status(dbOk ? 200 : 503).json({ ready: dbOk }); // readiness});
Circuit Breaker for Resilient Service Calls
Prevents cascading failures by short-circuiting calls to a downstream service once its error rate crosses a threshold, giving it time to recover.
const CircuitBreaker = require('opossum');async function callInventoryService(sku) { const res = await fetch(`http://inventory/api/stock/${sku}`); if (!res.ok) throw new Error(`upstream ${res.status}`); return res.json();}const breaker = new CircuitBreaker(callInventoryService, { timeout: 3000, // fail fast after 3s errorThresholdPercentage: 50, resetTimeout: 10000 // try again after 10s (half-open)});breaker.fallback(() => ({ stock: null, degraded: true }));app.get('/stock/:sku', async (req, res) => { res.json(await breaker.fire(req.params.sku));});
Sidecar Proxy Pattern (Envoy)
In a service mesh, each pod runs a sidecar proxy that intercepts all in/out traffic, offloading retries, mTLS, and telemetry from application code.
static_resources: listeners: - name: outbound_listener address: socket_address: { address: 0.0.0.0, port_value: 15001 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager route_config: virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: cluster: upstream_service retry_policy: retry_on: 5xx num_retries: 3
Saga Pattern for Distributed Transactions
Coordinates a multi-service business transaction as a sequence of local transactions plus compensating actions, since distributed 2PC doesn't scale in microservices.
async function placeOrderSaga(order) { const steps = []; try { const payment = await paymentService.charge(order); steps.push(() => paymentService.refund(payment.id)); const reservation = await inventoryService.reserve(order); steps.push(() => inventoryService.release(reservation.id)); await shippingService.schedule(order); } catch (err) { // Compensate in reverse order on any failure for (const compensate of steps.reverse()) { await compensate(); } throw err; }}
Graceful Shutdown on SIGTERM
Cloud native orchestrators send SIGTERM before hard-killing a container; the process must drain in-flight requests within the grace period or connections are dropped mid-response.
const server = app.listen(8080);process.on('SIGTERM', async () => { console.log('SIGTERM received, draining connections...'); server.close(() => { console.log('HTTP server closed'); }); await pool.end(); // close DB connection pool await messageBusClient.disconnect(); setTimeout(() => process.exit(0), 8000).unref(); // Ensure terminationGracePeriodSeconds in the pod spec // is longer than this drain window (default 30s)});
Common Cloud Native Anti-Patterns
Pitfalls teams hit when migrating a monolith to a cloud native architecture without rethinking the design.
- Distributed monolith- Services are split by team boundary but still deploy in lockstep and share a database, inheriting microservice complexity with none of the independence
- Chatty synchronous calls- A single user request fans out into a deep synchronous call chain across services, multiplying latency and failure surface
- Shared database anti-pattern- Multiple services writing to the same schema recreates tight coupling; each service should own its data and expose it via API or events
- Missing idempotency keys- Retried requests (common under network partitions) create duplicate side effects like double-charging without an idempotency key on write operations
- Config baked into images- Environment-specific values compiled into the container image violate the config-as-environment-variable factor and force rebuilds per environment
- No backpressure- Services without rate limiting or queue-based load leveling collapse under traffic spikes instead of degrading gracefully
Separate liveness from readiness probes — a pod can be alive (process running) but not ready (still warming a cache or connecting to a DB); conflating the two causes premature traffic routing or unnecessary restarts.