Linkerd can actually make your microservices faster by eliminating synchronous network hops.
Let’s see Linkerd in action. Imagine we have two simple services, webapp and api, that talk to each other.
First, we need to install Linkerd. We’ll use the install command with a few flags to customize it for a local development setup.
linkerd install --crds | kubectl apply -f -
linkerd install --controller-log-level debug | kubectl apply -f -
This installs the Linkerd control plane components into the linkerd namespace. The --crds flag ensures the Custom Resource Definitions (CRDs) are applied first, which is crucial for the rest of the installation to succeed. The --controller-log-level debug flag is useful for troubleshooting if things go sideways, giving us more insight into what the control plane is doing.
Now, let’s deploy our webapp and api services. For this example, we’ll use simple Docker images.
# webapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: your-dockerhub-username/webapp:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector:
app: webapp
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
# api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: your-dockerhub-username/api:latest
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
ports:
- protocol: TCP
port: 5000
targetPort: 5000
Apply these to your cluster:
kubectl apply -f webapp.yaml
kubectl apply -f api.yaml
To enable Linkerd’s features for these services, we need to inject the Linkerd proxy into their pods. The easiest way to do this is by annotating the namespace where your services reside. Let’s assume they are in the default namespace.
kubectl label namespace default linkerd.io/inject=enabled
After applying the label, Kubernetes will trigger a rolling update of your webapp and api deployments. When the new pods start, they will automatically have the Linkerd proxy container added. You can verify this by looking at the pods:
kubectl get pods -l app=webapp
kubectl get pods -l app=api
You should see two containers listed for each pod: your application container and a linkerd-proxy container.
Now, webapp can call api using its Kubernetes service name (http://api:5000), and Linkerd will transparently intercept and manage that traffic. Linkerd provides observability into this communication.
Let’s check the metrics:
linkerd stat deploy -n default
This command will show you traffic statistics for the webapp and api deployments, including request volume, success rates, and latency percentiles. You’ll see data even though you didn’t modify your application code to send metrics.
The core problem Linkerd solves here is the inherent unreliability and observability gap in distributed systems. Without Linkerd, if webapp makes a request to api and api is slow or fails, webapp has no easy way to know why. It just experiences a timeout or error. Linkerd, by sitting as a transparent proxy alongside your application, can observe this traffic. It understands the HTTP or gRPC protocol, can detect retriable errors, and can inject delays or failures in a controlled manner for testing.
Internally, Linkerd injects a lightweight, high-performance Rust proxy (called linkerd-proxy) into each pod. This proxy is configured via the Linkerd control plane. When your webapp process makes an outgoing network request to api, it actually sends it to localhost on a specific port that the linkerd-proxy is listening on. The proxy then makes the actual network call to the api service, applying policies, collecting metrics, and potentially retrying if configured. For incoming requests to your webapp, the linkerd-proxy also intercepts them before they reach your application container.
The exact levers you control are primarily through Linkerd’s Custom Resources. For instance, you can define ServiceProfile objects to give Linkerd more specific knowledge about your API’s routes, allowing for more granular metrics and advanced routing. You can also configure TrafficSplit resources to implement canary deployments and A/B testing by directing a percentage of traffic to a new version of a service.
One key aspect is that Linkerd doesn’t require any changes to your application code or Docker images, beyond the initial namespace annotation for injection. This means you can gradually adopt a service mesh without a massive re-architecture. The proxy handles TLS encryption, retries, load balancing, and observability transparently, allowing developers to focus on business logic.
The next step after getting basic observability is often implementing advanced traffic management, like canary releases or fault injection, to test resilience.