Skip to content

Service Mesh

Your services talk to each other constantly, and every one of those calls needs the same handful of safeguards: the traffic should be encrypted, a failed request should be retried a bounded number of times, a slow callee should hit a timeout rather than hang the caller, and every call should emit a metric and a trace span. In the reliability and observability modules you learned to build these into the service — circuit breakers, retries, instrumentation.

The trouble is that you now have twenty services in four languages, and each must implement all of it correctly and identically. The Go team’s retry logic differs subtly from the Java team’s. mTLS is configured three different ways. One service forgot to emit traces entirely. The same cross-cutting networking logic is reimplemented, inconsistently, everywhere.

You want every service-to-service call to be encrypted, retried, timed out, observed, and routed according to one consistent policy — without asking every team in every language to write and maintain that logic by hand. You want to change a policy (tighten a timeout, shift 5% of traffic to a canary) centrally, without redeploying the services. And you want it to work uniformly whether a service is in Go, Java, Python, or Rust.

So the forces are: consistent, centrally governed networking behaviour across a polyglot fleet, applied without modifying or redeploying the services that carry the business logic.

Adopt a service mesh. The mesh moves all of these communication concerns out of the service and into a sidecar proxy — a small proxy process deployed in the same unit as each service instance. Every byte the service sends or receives passes through its sidecar. The sidecar, not your code, performs mutual TLS, applies retries and timeouts, enforces routing rules, and records metrics and traces.

Because the proxy sits in the network path and is language-agnostic, the service is unaware of it. A Go service and a Java service get identical mTLS, retry, and telemetry behaviour because the same proxy implements it for both.

The fleet of sidecars is the data plane — they carry the actual traffic. A separate control plane configures them: you express policy once (this route, this timeout, this traffic split), and the control plane pushes the resulting configuration to every sidecar. Changing a policy is a control-plane update, not a service redeploy.

flowchart TB
  CP[Control plane: policy + certificates]
  subgraph A[Order Service pod]
    AS[order-service]
    AP[sidecar proxy]
    AS <--> AP
  end
  subgraph B[Payment Service pod]
    BS[payment-service]
    BP[sidecar proxy]
    BS <--> BP
  end
  AP -->|mTLS, retries, timeouts| BP
  CP -. config .-> AP
  CP -. config .-> BP
Each service runs beside a sidecar proxy (the data plane); a control plane pushes policy and certificates to every sidecar

You describe traffic behaviour as a policy the control plane distributes. This one sets a timeout and bounded retries for calls to the payment service, and splits traffic so 90% goes to the stable version and 10% to a canary — none of which requires touching the payment service’s code.

apiVersion: networking.mesh.io/v1
kind: TrafficPolicy
metadata:
name: payment-routing
spec:
host: payment-service
timeout: 2s
retries:
attempts: 3
perTryTimeout: 800ms
retryOn: 5xx,connect-failure
mtls:
mode: STRICT
trafficSplit:
- version: stable
weight: 90
- version: canary
weight: 10

The control plane translates this into configuration for every sidecar that calls payment-service. To roll the canary forward you change the weights here; no service is rebuilt or redeployed.

What you gain:

  • Consistent cross-cutting networking. mTLS, retries, timeouts, and telemetry behave identically for every service regardless of language, because one proxy implements them all.
  • Centralized, code-free control. Routing, security, and resilience policy live in the control plane. Canary releases, traffic shifts, and tighter timeouts are configuration changes, not redeploys.
  • Built-in observability and zero-trust. Every hop is automatically traced and metered, and service-to-service traffic is encrypted and authenticated by default.

What it costs you:

  • Extra latency and resource use. Every call now traverses two proxies, adding a small but real latency tax and a sidecar’s worth of CPU and memory per service instance.
  • Operational complexity. The mesh is a sophisticated distributed system you must install, upgrade, and debug; a misconfigured control plane can break communication fleet-wide.
  • Another layer to reason about. When a call fails, the answer might be in your service, the sidecar, or the control plane’s policy — more moving parts between cause and symptom.

A practical rule: a mesh earns its keep once you have many polyglot services and real needs for uniform mTLS, traffic control, and telemetry; for a handful of services it is usually more machinery than the problem warrants.

  • Service per Container — the sidecar is typically a second container deployed alongside the service container.
  • Microservice Chassis — an in-process alternative for some of the same concerns; mesh and chassis are often combined.
  • Externalized Configuration — mesh policy is itself externalized configuration, pushed by the control plane.
Where does a service mesh put communication concerns like mTLS, retries, and timeouts?
What is the difference between the data plane and the control plane?
Why can a service mesh apply identical behaviour to services written in different languages?
Which is a real cost of running a service mesh?