Linkerd’s production checklist is less about "best practices" and more about aggressively pruning the attack surface you expose to the network and external systems.
Let’s see Linkerd in action. Imagine you have a simple microservice application. You’ve deployed your services, and now you want to secure them with Linkerd.
Here’s a minimal deployment.yaml for a hypothetical webapp service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: public.ecr.aws/nginx/nginx:latest # Replace with your actual app image
ports:
- containerPort: 80
To enable Linkerd for this webapp service, you’d typically inject the Linkerd proxy. This is usually done automatically by Linkerd’s inject command or via automatic webhook injection.
linkerd inject webapp.yaml | kubectl apply -f -
After this, Linkerd’s control plane and the linkerd-proxy sidecar container will be running alongside your webapp container.
Now, let’s look at the Linkerd components involved:
linkerd-controller: The brains of the operation. It manages the control plane’s state, handles TLS certificate issuance, and orchestrates the proxies.linkerd-proxy: The sidecar container injected into your application pods. It intercepts all inbound and outbound traffic for the application container, handling mTLS, traffic splitting, metrics collection, and more.linkerd-identity: This component is responsible for issuing and distributing TLS certificates to thelinkerd-proxyinstances, enabling mutual TLS (mTLS) between services.linkerd-destination: Resolves service endpoints and provides load balancing information to the proxies.linkerd-proxy-injector: The webhook that automatically injects thelinkerd-proxysidecar into pods based on annotations.
The problem Linkerd solves is the inherent complexity of securing and observing microservices. Without it, you’d be implementing TLS, retry logic, circuit breaking, and metrics collection in every single service, leading to duplicated effort, inconsistent implementations, and a massive maintenance burden. Linkerd externalizes this cross-cutting concern into a transparent proxy.
The core mental model for Linkerd is that it treats your application as a black box. The linkerd-proxy sits at the network boundary of your pod, intercepting traffic. For outbound calls from your webapp to, say, a users service, the traffic flows webapp -> linkerd-proxy (outbound) -> network -> linkerd-proxy (inbound) -> users service. The proxies handle the TLS handshake, encryption, and decryption transparently.
The exact levers you control are primarily through Linkerd’s configuration and the annotations you apply to your Kubernetes resources. For example, to enable mTLS for a specific namespace:
apiVersion: v1
kind: Namespace
metadata:
name: my-services
annotations:
linkerd.io/inject: enabled # This triggers automatic proxy injection
linkerd.io/control-plane-tls: permissive # Or 'strict' for mTLS enforcement
Setting linkerd.io/control-plane-tls: permissive on a namespace means that Linkerd will attempt to establish mTLS with services in that namespace, but if it fails, it will fall back to unencrypted HTTP. strict would enforce mTLS, failing requests that cannot establish a secure connection.
When you configure Linkerd’s policy.linkerd.io resources, you’re defining network policies that are enforced by the linkerd-proxy. For instance, to allow only webapp to communicate with users on port 8080:
apiVersion: policy.linkerd.io/v1alpha1
kind: Server
metadata:
name: users-server
namespace: default
spec:
layanan: users
port: 8080
---
apiVersion: policy.linkerd.io/v1alpha1
kind: Authorization
metadata:
name: allow-webapp-to-users
namespace: default
spec:
server: users-server
client:
layanan: webapp
This creates a Server resource representing the users service on port 8080 and an Authorization resource that permits traffic only from the webapp service to that users server. All other traffic to users:8080 would be denied by the linkerd-proxy.
Linkerd’s observability features provide a wealth of data. When you query metrics for a service, like webapp, you’re not querying the webapp application directly. Instead, you’re querying the metrics exposed by the linkerd-proxy sidecar associated with each webapp pod. These proxies collect metrics on request volume, success rates, latency distributions (often in p5, p95, p99 buckets), and more, all aggregated and presented by Linkerd’s telemetry components.
The most surprising thing about Linkerd’s mTLS is that it operates at a layer above the Kubernetes Service abstraction. While Kubernetes Services provide a stable IP and DNS name for a set of pods, Linkerd’s mTLS is applied directly to the pod’s network interface, managed by the linkerd-proxy. This means that even if your application directly accesses another service’s ClusterIP, the linkerd-proxy intercepts and secures that traffic, ensuring mTLS is enforced regardless of how the application attempts to connect. It doesn’t rely on the Kubernetes Service object for its security context, only for discovery.
The next step is to explore how Linkerd’s traffic mirroring feature can be used to test new versions of your services without impacting live users.