22 KiB
API Gateway & Service Mesh Patterns
Deep reference for API gateway architectures, service mesh implementation (Istio, Linkerd, Envoy), sidecar patterns, and service-to-service communication. Use when designing inter-service communication, implementing traffic management, or choosing between gateway, mesh, or hybrid topologies.
Contents
- API Gateway Patterns
- Service Mesh Architecture
- Technology Comparison
- mTLS and Service Identity
- Observability Through Mesh
- Gateway vs Mesh vs Both
- Implementation Patterns
- Anti-Patterns
- Decision Framework
- Cross-References
API Gateway Patterns
Core Gateway Responsibilities
| Function | Description | Example |
|---|---|---|
| Routing | Route requests to backend services | /api/orders/* -> orders-service |
| Authentication | Validate tokens, API keys | JWT verification, OAuth introspection |
| Rate limiting | Throttle requests per client/endpoint | 100 req/min per API key |
| Request transformation | Modify headers, body, path | Add correlation IDs, strip internal headers |
| Response aggregation | Combine multiple backend responses | BFF pattern for mobile clients |
| Load balancing | Distribute traffic across instances | Round-robin, least connections, weighted |
| Caching | Cache responses at the edge | Cache GET responses with TTL |
| Circuit breaking | Fail fast when backend is unhealthy | Open circuit after 5 consecutive failures |
| TLS termination | Handle HTTPS at the gateway | Offload TLS from backend services |
Gateway Topology Patterns
Pattern 1: Single Gateway
┌────────┐ ┌──────────────┐ ┌──────────┐
│ Client │────▶│ Gateway │────▶│ Service A│
└────────┘ │ │────▶│ Service B│
│ │────▶│ Service C│
└──────────────┘ └──────────┘
Best for: Small teams, <10 services, uniform client needs.
Pattern 2: Backend-for-Frontend (BFF)
┌──────────┐ ┌───────────────┐
│ Mobile │────▶│ Mobile BFF │───▶ Services
└──────────┘ └───────────────┘
┌──────────┐ ┌───────────────┐
│ Web │────▶│ Web BFF │───▶ Services
└──────────┘ └───────────────┘
┌──────────┐ ┌───────────────┐
│ Partner │────▶│ Partner API │───▶ Services
│ API │ │ Gateway │
└──────────┘ └───────────────┘
Best for: Different client types with distinct data needs.
Pattern 3: Federated Gateway
┌────────┐ ┌───────────────┐ ┌──────────────┐
│ Client │────▶│ Edge Gateway │────▶│ Team A GW │───▶ Team A services
└────────┘ │ (auth, rate │────▶│ Team B GW │───▶ Team B services
│ limiting) │────▶│ Team C GW │───▶ Team C services
└───────────────┘ └──────────────┘
Best for: Large organizations, multiple teams owning their own gateway configuration.
Rate Limiting Patterns
// Token bucket rate limiting (typical gateway configuration)
// Kong rate-limiting plugin configuration
const rateLimitConfig = {
plugin: 'rate-limiting',
config: {
minute: 60, // 60 requests per minute
hour: 1000, // 1000 requests per hour
policy: 'redis', // Use Redis for distributed counting
fault_tolerant: true, // Allow traffic if Redis is down
hide_client_headers: false,
redis_host: 'redis',
redis_port: 6379,
},
};
// Response headers
// X-RateLimit-Limit: 60
// X-RateLimit-Remaining: 45
// X-RateLimit-Reset: 1640000000
| Algorithm | Description | Use When |
|---|---|---|
| Token bucket | Tokens replenish at fixed rate, consumed per request | Burst-tolerant, smooth throttling |
| Sliding window | Count requests in a rolling time window | Precise limits, no burst |
| Fixed window | Count requests per fixed time interval | Simple, slight burst at window edges |
| Leaky bucket | Process requests at fixed rate, queue excess | Smooth output rate |
Request Aggregation
// BFF aggregation pattern — single client request, multiple backend calls
app.get('/api/dashboard', async (req, res) => {
const userId = req.user.id;
// Parallel fetch from multiple services
const [profile, orders, notifications, recommendations] = await Promise.all([
userService.getProfile(userId),
orderService.getRecent(userId, { limit: 5 }),
notificationService.getUnread(userId),
recommendationService.getForUser(userId),
]);
// Aggregate into client-optimized response
res.json({
user: { name: profile.name, avatar: profile.avatar },
recentOrders: orders.map(o => ({ id: o.id, status: o.status, total: o.total })),
unreadCount: notifications.length,
recommendations: recommendations.slice(0, 3),
});
});
Service Mesh Architecture
Core Concepts
A service mesh is a dedicated infrastructure layer for service-to-service communication. It uses sidecar proxies deployed alongside each service to handle networking concerns.
┌─────────────────────────────────────────────────┐
│ Control Plane │
│ (Configuration, certificates, policies) │
└───────────┬──────────────┬──────────────┬────────┘
│ │ │
┌──────▼──────┐ ┌────▼────────┐ ┌──▼──────────┐
│ Service A │ │ Service B │ │ Service C │
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
│ │ App │ │ │ │ App │ │ │ │ App │ │
│ └───┬────┘ │ │ └───┬────┘ │ │ └───┬────┘ │
│ ┌───▼────┐ │ │ ┌───▼────┐ │ │ ┌───▼────┐ │
│ │Sidecar │◄├─┼─▶│Sidecar │◄├─┼─▶│Sidecar │ │
│ │Proxy │ │ │ │Proxy │ │ │ │Proxy │ │
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
└─────────────┘ └────────────┘ └──────────────┘
Data Plane (proxies handle all traffic)
Sidecar Proxy Responsibilities
| Responsibility | Description |
|---|---|
| Traffic routing | Route requests based on rules (headers, weight, path) |
| Load balancing | Distribute traffic across service instances |
| mTLS encryption | Encrypt all service-to-service traffic |
| Circuit breaking | Prevent cascading failures |
| Retry and timeout | Automatic retry with configurable backoff |
| Observability | Emit metrics, traces, and access logs |
| Health checking | Active and passive health checks |
| Rate limiting | Per-service or per-route limits |
| Access control | Authorization policies between services |
Control Plane vs Data Plane
| Component | Control Plane | Data Plane |
|---|---|---|
| Role | Configuration and policy distribution | Request processing |
| Components | Istiod, Linkerd control plane | Envoy proxy, Linkerd proxy |
| Scaling | Single instance or HA pair | One per service instance (sidecar) |
| Failure impact | No new config updates, existing config works | Service-to-service traffic affected |
| Resource usage | Low (control only) | Per-pod overhead (CPU, memory, latency) |
Technology Comparison
Gateway Comparison
| Feature | Kong | AWS API Gateway | Envoy (standalone) | Traefik | NGINX |
|---|---|---|---|---|---|
| Deployment | Self-hosted / Cloud | Managed | Self-hosted | Self-hosted | Self-hosted |
| Plugin ecosystem | Large (Lua, Go) | AWS integrations | Filters (C++, Wasm) | Middleware | Modules |
| Rate limiting | Built-in | Built-in | Filter | Middleware | Module |
| Auth | JWT, OAuth, OIDC | IAM, Cognito, Lambda auth | ext_authz filter | ForwardAuth | Auth module |
| Observability | Prometheus, Datadog | CloudWatch | Prometheus, Zipkin | Prometheus | Stub status |
| gRPC support | Yes | Yes | Native | Yes | Yes |
| WebSocket | Yes | Yes | Yes | Yes | Yes |
| Best for | Multi-cloud, plugin needs | AWS-native workloads | High performance, mesh ingress | Docker/K8s auto-discovery | Simple, proven |
Service Mesh Comparison
| Feature | Istio | Linkerd | AWS App Mesh | Cilium Service Mesh |
|---|---|---|---|---|
| Proxy | Envoy | linkerd2-proxy (Rust) | Envoy | eBPF (no sidecar) |
| Complexity | High | Low | Medium | Medium |
| Resource overhead | Higher (Envoy sidecar) | Lower (lightweight proxy) | Medium | Lowest (kernel-level) |
| mTLS | Automatic | Automatic | Manual config | Automatic |
| Multi-cluster | Yes | Yes (limited) | Cross-account | Yes |
| Traffic management | Advanced (fault injection, mirroring) | Basic (split, retry) | Basic | Advanced |
| Observability | Rich (Kiali, Jaeger, Prometheus) | Built-in dashboard | CloudWatch, X-Ray | Hubble |
| Learning curve | Steep | Gentle | Moderate | Moderate |
| Best for | Complex mesh, advanced traffic | Simple mesh, low overhead | AWS-native workloads | High performance, eBPF |
Selection Quick Guide
Service mesh selection:
├─ < 10 services → No mesh needed. Use library-based patterns.
├─ 10-50 services, want simplicity → Linkerd
├─ 10-50 services, need advanced traffic mgmt → Istio
├─ AWS-native, managed preference → App Mesh
├─ Performance-critical, eBPF available → Cilium
└─ > 50 services, multi-cluster → Istio (with careful tuning)
mTLS and Service Identity
Mutual TLS in Service Mesh
Service A Service B
┌──────────┐ ┌──────────┐
│ │── 1. TLS handshake (client cert) ──▶│ │
│ App │◀─ 2. TLS handshake (server cert) ──│ App │
│ │── 3. Encrypted traffic ────────────▶│ │
└──────────┘ └──────────┘
Both sides present certificates issued by the mesh CA.
Identity is based on service account, not network address.
Identity-Based Authorization
# Istio AuthorizationPolicy
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: orders-policy
namespace: production
spec:
selector:
matchLabels:
app: orders-service
rules:
# Allow only payment-service and api-gateway to call orders
- from:
- source:
principals:
- cluster.local/ns/production/sa/payment-service
- cluster.local/ns/production/sa/api-gateway
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/orders/*"]
# Deny everything else (implicit deny)
Certificate Management
| Approach | Description | Complexity |
|---|---|---|
| Mesh-managed CA | Mesh control plane issues and rotates certs | Low (automatic) |
| External CA (Vault) | HashiCorp Vault issues certs, mesh distributes | Medium |
| SPIFFE/SPIRE | Standard identity framework, pluggable CAs | Medium-High |
| Manual certs | Team manages certs manually | High (do not do this) |
Recommended: Use mesh-managed CA for most deployments. Integrate with external CA (Vault, AWS ACM PCA) for enterprise compliance requirements.
Observability Through Mesh
Distributed Tracing
Service mesh proxies automatically inject trace headers and emit spans.
Client → [Gateway span] → [Service A span] → [Service B span] → [Database span]
│
[Service C span]
Each proxy adds its own span without application code changes.
Trace header propagation:
| Header | Standard | Used By |
|---|---|---|
traceparent |
W3C Trace Context | OpenTelemetry, modern systems |
x-request-id |
De facto | Envoy, Istio |
x-b3-traceid |
Zipkin B3 | Zipkin, older Istio |
Mesh-Level Metrics
Standard metrics emitted by sidecar proxies (no application instrumentation needed):
| Metric | Description | Alert On |
|---|---|---|
request_count |
Total requests per service/route | Unexpected traffic drops |
request_duration |
Latency histogram (P50, P95, P99) | P99 > SLO threshold |
response_code |
Count by status code (2xx, 4xx, 5xx) | 5xx rate > 0.1% |
tcp_connections |
Active TCP connections | Approaching connection limits |
retry_count |
Automatic retries triggered | High retry rate = unhealthy upstream |
Golden Signals Dashboard
For each service, track:
1. Latency — P50, P95, P99 response time
2. Traffic — Requests per second
3. Errors — 5xx rate as percentage of total
4. Saturation — CPU, memory, connection pool usage
Service mesh provides signals 1-3 automatically.
Signal 4 requires application-level metrics.
Service Topology Visualization
Mesh provides automatic service dependency mapping:
Tools:
- Kiali (Istio) — Service graph, health status, traffic flow
- Linkerd Viz — Dashboard with golden metrics per service
- Hubble (Cilium) — Network flow visibility
- Jaeger/Tempo — Distributed trace visualization
Gateway vs Mesh vs Both
Comparison Matrix
| Concern | API Gateway | Service Mesh | Both (Recommended) |
|---|---|---|---|
| North-south traffic (client → service) | Primary role | Not designed for | Gateway handles |
| East-west traffic (service → service) | Not designed for | Primary role | Mesh handles |
| External authentication | Yes | No | Gateway handles |
| Service-to-service auth (mTLS) | No | Yes | Mesh handles |
| Public rate limiting | Yes | No | Gateway handles |
| Internal circuit breaking | Limited | Yes | Mesh handles |
| External API versioning | Yes | No | Gateway handles |
| Internal traffic splitting | No | Yes | Mesh handles |
| TLS termination (external) | Yes | No | Gateway handles |
| mTLS (internal) | No | Yes | Mesh handles |
Recommended Architecture
┌──────────────┐
│ Internet │
└──────┬───────┘
│
┌──────▼───────┐ ← North-south boundary
│ API Gateway │ Auth, rate limiting, TLS termination, routing
│ (Kong/Envoy) │
└──────┬───────┘
│
┌──────▼───────────────────────────────┐
│ Service Mesh (Istio/Linkerd) │ ← East-west boundary
│ │ mTLS, retries, circuit breaking,
│ ┌─────┐ ┌─────┐ ┌─────┐ │ observability, traffic management
│ │Svc A│◄─▶│Svc B│◄─▶│Svc C│ │
│ └─────┘ └─────┘ └─────┘ │
└──────────────────────────────────────┘
When You Do NOT Need a Service Mesh
| Scenario | Why No Mesh |
|---|---|
| < 10 services | Overhead exceeds benefit |
| Single team, single repo | Library-based patterns suffice |
| Serverless architecture | Functions are too short-lived for sidecars |
| Low traffic internal tools | Complexity not justified |
| Early-stage startup | Focus on product, not infrastructure |
Alternatives to mesh for small deployments:
- Library-based retries and circuit breaking (p-retry, opossum)
- Application-level mTLS (cert-manager + application config)
- OpenTelemetry SDK for observability (no mesh needed)
Implementation Patterns
Canary Deployment via Mesh
# Istio: Route 95% to v1, 5% to v2
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: orders
spec:
hosts:
- orders
http:
- route:
- destination:
host: orders
subset: v1
weight: 95
- destination:
host: orders
subset: v2
weight: 5
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: orders
spec:
host: orders
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
Retry and Timeout Configuration
# Istio retry policy
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: orders
spec:
hosts:
- orders
http:
- timeout: 3s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,reset,connect-failure,retriable-4xx
route:
- destination:
host: orders
Circuit Breaker Configuration
# Istio circuit breaker
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: orders
spec:
host: orders
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 100
http2MaxRequests: 1000
maxRequestsPerConnection: 10
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50
Anti-Patterns
1. Mesh for Everything
[FAIL] Deploying a service mesh for 3 services because "everyone uses Istio"
→ Massive operational overhead for minimal benefit
[PASS] Start with library-based patterns. Adopt mesh when you have
10+ services and measurable networking pain.
2. Gateway as Service Bus
[FAIL] Putting business logic, data transformation, and orchestration in the gateway
→ Gateway becomes a bottleneck and single point of failure for logic
[PASS] Gateway handles cross-cutting concerns only (auth, rate limiting, routing).
Business logic stays in services.
3. Ignoring Sidecar Resource Overhead
[FAIL] Deploying mesh without accounting for sidecar CPU/memory per pod
→ Resource exhaustion, unexpected costs, latency increase
[PASS] Budget 50-128MB memory and 0.1-0.5 CPU per sidecar proxy.
Monitor mesh overhead as a first-class metric.
4. No Mesh Bypass for Debugging
[FAIL] No way to bypass the mesh for troubleshooting
→ Cannot isolate whether issues are mesh-related or application-related
[PASS] Maintain ability to disable sidecar injection per namespace or pod.
Have runbooks for mesh-bypass debugging.
5. mTLS Without Identity Policies
[FAIL] Enabling mTLS but no authorization policies → all services can call all services
→ mTLS only encrypts traffic, it does not authorize
[PASS] Pair mTLS with explicit AuthorizationPolicy rules.
Default deny, explicit allow per service pair.
Decision Framework
Do You Need an API Gateway?
1. Do you expose APIs to external clients? [Yes → You need a gateway]
2. Do you need centralized auth for external traffic? [Yes → Gateway]
3. Do you need rate limiting for public APIs? [Yes → Gateway]
4. Do you have multiple BFF needs (mobile, web, partner)? [Yes → Multiple gateways]
5. Internal-only services? [Gateway optional, mesh sufficient]
Do You Need a Service Mesh?
1. How many services? [< 10 → No, 10-50 → Maybe, > 50 → Yes]
2. Do you need mTLS between services? [Yes → +2]
3. Do you need advanced traffic management? [Yes → +2]
4. Is observability a gap today? [Yes → +1]
5. Does the team have K8s experience? [Yes → +1, No → -2]
6. Are you running on Kubernetes? [Yes → +1, No → -2]
Score: 0-2 → Library-based patterns
Score: 3-4 → Linkerd (simpler mesh)
Score: 5+ → Istio or Cilium (full mesh)
Gateway Selection
Choosing a gateway:
├─ AWS-native, managed preference → AWS API Gateway
├─ Multi-cloud, plugin extensibility → Kong
├─ Performance-critical, already using Envoy → Envoy as gateway
├─ Kubernetes auto-discovery → Traefik
├─ Simple reverse proxy, proven → NGINX
└─ GraphQL federation → Apollo Router
Cross-References
- modern-patterns.md — Service mesh and microservices overview
- scalability-reliability-guide.md — Load balancing, circuit breakers, resilience
- migration-modernization-guide.md — Strangler fig pattern with gateway routing
- data-architecture-patterns.md — Service-to-service data patterns
- ../software-backend/SKILL.md — Backend service implementation
- ../../ops-devops-platform/SKILL.md — Kubernetes, CI/CD, deployment strategies
- ../../software-security-appsec/SKILL.md — Zero-trust security, mTLS