Linkerd and Istio aren’t just different service meshes; they represent fundamentally different philosophies on how to approach microservice observability and control.

Let’s see Linkerd in action, managing traffic for a simple two-service application: emojivoto.

# deployment.yaml for the 'voting' service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: voting-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: voting
      version: v1
  template:
    metadata:
      labels:
        app: voting
        version: v1
    spec:
      containers:
      - name: voting
        image: buoyantio/voting:v1
        ports:
        - containerPort: 80
---
# service.yaml for the 'voting' service
apiVersion: v1
kind: Service
metadata:
  name: voting
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: voting
    version: v1
---
# deployment.yaml for the 'emojisvc' service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: emojisvc-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: emojisvc
      version: v1
  template:
    metadata:
      labels:
        app: emojisvc
        version: v1
    spec:
      containers:
      - name: emojisvc
        image: buoyantio/emojisvc:v1
        ports:
        - containerPort: 80
---
# service.yaml for the 'emojisvc' service
apiVersion: v1
kind: Service
metadata:
  name: emojisvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: emojisvc
    version: v1

When you install Linkerd with its default settings, it injects a linkerd-proxy sidecar into each pod. This proxy intercepts all inbound and outbound network traffic for your application containers. For emojivoto, this means the voting pod now has two containers: voting and linkerd-proxy. The voting container talks to localhost:4140 (where the proxy listens for outbound traffic), and all incoming traffic directed to the voting service’s ClusterIP is routed by Kubernetes to one of the voting pods, landing in its linkerd-proxy on port 80.

Here’s what the linkerd-proxy container spec looks like when injected:

// Snippet from a pod definition with Linkerd injection
containers:
- name: voting
  image: buoyantio/voting:v1
  ports:
  - containerPort: 80
- name: linkerd-proxy
  image: public.registry.linkerd.io/linkerd/proxy:stable-2.13.0
  ports:
  - containerPort: 4140 # Outbound traffic listener
  - containerPort: 4191 # Metrics and admin API
  # ... other configurations

This simple redirection is the magic. The linkerd-proxy handles mTLS, retries, timeouts, and metrics generation without your application code needing to know. You can then query the linkerd-proxy’s metrics endpoint (port 4191) to see real-time request rates, latencies, and success rates.

curl http://localhost:4191/metrics

This output, scraped by Prometheus and often visualized in Grafana via Linkerd’s dashboards, shows you things like:

http_requests_total{direction="inbound",dst_pod="voting-v1-...",dst_service="voting",src_pod="...",src_namespace="default",status_code="200"} 1000
http_request_duration_ms_bucket{direction="inbound",dst_pod="voting-v1-...",dst_service="voting",le="50",status_code="200"} 800
# ... many more metrics

Linkerd excels at providing these core service mesh capabilities with minimal overhead. It’s designed to be fast, efficient, and easy to install and operate. The primary problem it solves is making microservices resilient and observable without significant operational burden. Its internal architecture is built around a lightweight Envoy proxy (though it has its own Rust-based proxy in newer versions for even more efficiency) that is configured by a small, Go-based control plane. This control plane watches Kubernetes resources and pushes minimal, targeted configuration to the proxies.

The most surprising aspect is how much functionality Linkerd offers with such a small footprint. For instance, its automatic mTLS is enabled by default, encrypting all traffic between pods in the mesh without any application changes. This is achieved by the control plane issuing short-lived TLS certificates to each proxy, which then mutually authenticate and encrypt communication.

Linkerd’s philosophy is "opt-in for advanced features." Things like advanced traffic splitting, fault injection, and fine-grained policy are available but require explicit configuration. This keeps the default experience simple and performant.

The next hurdle you’ll encounter is integrating external services or services not running within the mesh into your observability and resilience strategy.

Want structured learning?

Take the full Linkerd course →