What is a Service Mesh?
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It makes communication reliable, fast, and secure without requiring changes to application code. The mesh provides features like load balancing, encryption, observability, and traffic management through a network of lightweight proxies deployed alongside each service instance.
Service Mesh Features
- Mutual TLS (mTLS): Automatic encryption and authentication between all services
- Traffic Management: Canary deployments, A/B testing, circuit breaking, retry policies
- Observability: Distributed tracing, metrics collection, and access logs without code changes
- Load Balancing: Intelligent request distribution based on latency, health, and capacity
- Access Control: Fine-grained authorization policies for service-to-service calls
Architecture: Control Plane and Data Plane
A service mesh consists of two components. The data plane is made up of sidecar proxies (like Envoy) deployed alongside each service instance. All traffic flows through these proxies. The control plane (like Istio or Linkerd) configures the proxies and manages policies, certificates, and routing rules.
Component Responsibilities
| Component | Technology | Role |
|---|---|---|
| Data Plane Proxy | Envoy | Intercepts all inbound/outbound traffic for each service |
| Control Plane | Istio / Linkerd | Configures proxies, manages certificates, enforces policies |
| Service Discovery | Kubernetes / Consul | Tracks available service instances and their health |
| Observability | Jaeger / Prometheus / Grafana | Collects traces, metrics, and logs from the mesh |
Istio Configuration Examples
// Istio VirtualService - Traffic routing (YAML as reference)
// In practice these are Kubernetes manifests, not TypeScript
// But here's how you'd manage them programmatically:
interface IstioVirtualService {
apiVersion: string;
kind: string;
metadata: { name: string; namespace: string };
spec: {
hosts: string[];
http: Array<{
match?: Array<{ headers?: Record }>;
route: Array<{
destination: { host: string; subset: string };
weight: number;
}>;
retries?: { attempts: number; perTryTimeout: string };
timeout?: string;
}>;
};
}
// Canary deployment: 90% to v1, 10% to v2
const canaryRouting: IstioVirtualService = {
apiVersion: "networking.istio.io/v1beta1",
kind: "VirtualService",
metadata: { name: "order-service", namespace: "production" },
spec: {
hosts: ["order-service"],
http: [{
route: [
{ destination: { host: "order-service", subset: "v1" }, weight: 90 },
{ destination: { host: "order-service", subset: "v2" }, weight: 10 },
],
retries: { attempts: 3, perTryTimeout: "2s" },
timeout: "10s",
}],
},
};
// Header-based routing: beta users get v2
const betaRouting: IstioVirtualService = {
apiVersion: "networking.istio.io/v1beta1",
kind: "VirtualService",
metadata: { name: "order-service", namespace: "production" },
spec: {
hosts: ["order-service"],
http: [
{
match: [{ headers: { "x-user-group": { exact: "beta" } } }],
route: [{ destination: { host: "order-service", subset: "v2" }, weight: 100 }],
},
{
route: [{ destination: { host: "order-service", subset: "v1" }, weight: 100 }],
},
],
},
};
The Sidecar Pattern
The sidecar pattern deploys a helper process alongside each service instance. In a service mesh, the sidecar is an Envoy proxy that intercepts all network traffic. The application code is unaware of the proxy — it just makes normal HTTP or gRPC calls.
// Your application code does NOT change with a service mesh
// The sidecar handles everything transparently
// Without service mesh - you handle retries, timeouts, TLS yourself
class OrderClient {
async getOrder(id: string): Promise {
// Manual retry logic
for (let attempt = 0; attempt < 3; attempt++) {
try {
const response = await fetch(`https://order-service/orders/${id}`, {
headers: { Authorization: `Bearer ${await this.getToken()}` },
signal: AbortSignal.timeout(5000),
});
return response.json();
} catch (error) {
if (attempt === 2) throw error;
await sleep(1000 * Math.pow(2, attempt)); // Exponential backoff
}
}
throw new Error("Unreachable");
}
}
// With service mesh - the sidecar handles retries, TLS, timeouts
class OrderClientWithMesh {
async getOrder(id: string): Promise {
// Simple HTTP call — the Envoy sidecar handles:
// - mTLS encryption
// - Retries with exponential backoff
// - Circuit breaking
// - Load balancing
// - Distributed tracing headers
const response = await fetch(`http://order-service/orders/${id}`);
return response.json();
}
}
When to Use a Service Mesh
- Use when: You have 10+ microservices, need mTLS everywhere, want traffic management without code changes
- Skip when: You have fewer than 5 services, the added complexity and resource overhead is not justified
- Resource cost: Each sidecar proxy consumes memory (50-100MB) and CPU — multiply by number of pods
- Latency: The extra network hop through the sidecar adds 1-3ms per request