Service Mesh Basics
This page is a hands-on introduction to service meshes on Kubernetes: what the moving parts are, how to install one, and how to turn on mTLS and traffic routing.
It also frames the real question - when mesh complexity pays off versus just using Ingress and NetworkPolicy.
Prerequisites
- A Kubernetes cluster on 1.34-1.36 (this page targets 1.36.2); nodes run containerd via the CRI.
kubectlmatching your cluster's minor version.- Helm 3 for chart-based installs, or the mesh's own CLI.
- Images built with Docker Engine 29.x (BuildKit) or any OCI builder; the mesh does not care how images were built.
- A metrics stack (Prometheus/Grafana) if you want to see golden metrics.
Quick install of the Linkerd CLI on macOS or Linux:
# Install the Linkerd CLI, then verify the cluster is ready
curl -fsSL https://run.linkerd.io/install | sh
export PATH=$PATH:$HOME/.linkerd2/bin
linkerd check --preBasic Examples
1. Check whether you even need a mesh
Before installing anything, list your services and ask what the mesh would do for you.
kubectl get deploy -A --no-headers | wc -l
kubectl get networkpolicy -A- If you have a handful of services and no NetworkPolicies, start with NetworkPolicy, not a mesh.
- A mesh pays off with many services, mixed languages, or a hard mTLS mandate.
- Ingress plus NetworkPolicy covers north-south routing and L3/L4 isolation without a control plane.
- Adopt a mesh to add uniform L7 features, not to replace those primitives.
2. Install a lightweight mesh (Linkerd)
Install the control plane after validating prerequisites.
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
linkerd checklinkerd install --crdsapplies custom resource definitions first; the second command installs the control plane.linkerd checkvalidates certificates, control-plane health, and API access.- The control plane runs in the
linkerdnamespace. - Nothing is meshed yet - injection is opt-in per namespace or workload.
3. Enable sidecar injection on a namespace
Meshing is controlled by an annotation.
apiVersion: v1
kind: Namespace
metadata:
name: payments
annotations:
linkerd.io/inject: enabled- New pods in this namespace get a proxy sidecar injected at admission time.
- Existing pods are not touched until they restart.
- Roll existing workloads with
kubectl rollout restart deploy -n paymentsto inject them. - You can also annotate a single Deployment's pod template instead of the whole namespace.
4. Confirm mTLS is active
With injected pods, traffic between them is automatically mutually authenticated and encrypted.
linkerd viz install | kubectl apply -f -
linkerd viz edges deployment -n payments- The
edgescommand shows which connections are mTLS-secured. - Identity is per-workload and certificates rotate automatically.
- You did not change application code to get encryption-in-transit.
- mTLS proves workload identity; it does not authenticate end users.
5. Install Istio instead (the ambient option)
Istio offers both sidecar and sidecar-less ambient modes.
istioctl install --set profile=ambient -y
kubectl label namespace payments istio.io/dataplane-mode=ambient- The
ambientprofile installs node-levelztunnelproxies for L4 mTLS. - Labeling the namespace enrolls its pods without injecting sidecars.
- L7 features are added later via a per-namespace waypoint proxy, only where needed.
- Ambient lowers per-pod overhead but adds node components to operate.
6. Keep Ingress separate from the mesh
A mesh governs east-west traffic; you still terminate external traffic at the edge.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
namespace: payments
spec:
parentRefs:
- name: public-gateway
rules:
- backendRefs:
- name: web
port: 80- Gateway API (GA) handles north-south routing at the cluster edge.
- The mesh secures and observes calls between services after they enter.
- Running both is normal and expected.
- Do not try to replace your Ingress or Gateway with the mesh.
7. Watch the cost
Every sidecar consumes resources, so measure before and after.
kubectl top pods -n payments- Compare pod CPU and memory before and after injection.
- Expect extra memory per proxy and a few milliseconds of added latency per hop.
- Multiply that overhead by your pod count to estimate cluster cost.
- If the overhead outweighs the benefit, do not mesh that namespace.
Intermediate Examples
8. Shift traffic between two versions
An L7 mesh routes by weight without redeploying either version.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
namespace: payments
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10- Ten percent of requests go to
v2for a canary. - Adjust weights to progress or roll back instantly.
- No pod restarts are needed to change the split.
- Pair this with metrics to gate promotion on success rate.
9. Enforce default-deny authorization
Lock down which workloads may call a service at L7.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: reviews-allow-web
namespace: payments
spec:
selector:
matchLabels:
app: reviews
action: ALLOW
rules:
- from:
- source:
principals:
- "cluster.local/ns/payments/sa/web"- An empty ALLOW policy plus a targeted rule creates default-deny for the selected workload.
- Identity is the cryptographic service-account principal, not an IP.
- This complements NetworkPolicy; keep both layers.
- Test policies in a staging namespace before production.
10. Set retries and timeouts deliberately
The mesh gives you resilience controls; use them carefully.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-resilience
namespace: payments
spec:
hosts:
- reviews
http:
- timeout: 2s
retries:
attempts: 2
perTryTimeout: 500ms
route:
- destination:
host: reviews- A tight timeout prevents slow calls from tying up callers.
- Cap retry attempts to avoid retry storms during an outage.
- Only retry idempotent operations.
- Watch success-rate metrics to confirm the settings help rather than hide problems.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).