Linkerd’s multi-cluster feature doesn’t just let you connect services across Kubernetes clusters; it fundamentally redefines what a "service" means, allowing you to treat a service deployed in one cluster as if it were a local resource in another.

Let’s see this in action. Imagine we have two clusters, cluster-a and cluster-b. In cluster-a, we have a simple hello service:

apiVersion: v1
kind: Service
metadata:
  name: hello
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: hello
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: nginxdemos/hello:plain-text
        ports:
        - containerPort: 8080

And in cluster-b, we have a frontend service that needs to call hello:

apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8000
  selector:
    app: frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: nicolaka/netshoot # A handy image with tools like curl
        ports:
        - containerPort: 8000
        command: ["/bin/sh", "-c"]
        args:
        - |
          while true; do
            echo "Calling hello service..."
            curl --fail http://hello.default.svc.cluster.local/
            echo "Response: $?"
            sleep 5
          done

Normally, frontend in cluster-b would try to resolve hello.default.svc.cluster.local and fail, as that DNS name only exists within cluster-a.

After configuring Linkerd multi-cluster, we can "mirror" the hello service from cluster-a to cluster-b. This involves a few steps:

  1. Install Linkerd on both clusters with the multi-cluster component enabled.
  2. Create a ServiceMirror resource in cluster-b that points to the hello service in cluster-a.

Here’s what the ServiceMirror would look like:

apiVersion: linkerd.io/v1alpha1
kind: ServiceMirror
metadata:
  name: hello-mirror
  namespace: default # Namespace where the mirrored service will appear in cluster-b
spec:
  # The cluster from which to mirror the service
  cluster: cluster-a
  # The service to mirror from cluster-a
  service:
    name: hello
    namespace: default

Once this ServiceMirror is applied in cluster-b, Linkerd automatically creates a new Kubernetes Service object in cluster-b’s default namespace, also named hello. This mirrored service is not a direct proxy to the original hello service’s pods. Instead, it’s a local Kubernetes Service that directs traffic to Linkerd’s gateway in cluster-b. Linkerd’s gateway then forwards this traffic, over the established multi-cluster connection (using the linkerd-gateway service), to the Linkerd gateway in cluster-a, which finally routes it to the actual hello pods in cluster-a.

The crucial part is that the frontend deployment in cluster-b can now resolve and communicate with hello.default.svc.cluster.local as if it were local. Linkerd’s transparent proxying handles the cross-cluster routing seamlessly.

The mental model here is that Linkerd multi-cluster extends the Kubernetes service discovery and routing fabric across cluster boundaries. It achieves this by:

  • Gateway Services: Each cluster runs a linkerd-gateway service, which acts as the ingress and egress point for cross-cluster traffic.
  • Service Mirrors: These are declarative resources that tell Linkerd which services in other clusters should be made available locally in the current cluster. Linkerd watches for these mirrors and, for each one, creates a corresponding local Kubernetes Service.
  • Transparent Proxying: When a pod in cluster-b tries to reach hello.default.svc.cluster.local, Linkerd’s local proxy intercepts the request. If the destination Service is a mirrored service, the proxy knows to route it to the local linkerd-gateway.
  • Cross-Cluster Routing: The linkerd-gateway in cluster-b then establishes a secure connection to the linkerd-gateway in cluster-a (or vice-versa, depending on the direction of the request). This connection might use an underlying Kubernetes Service of type LoadBalancer or NodePort for initial connectivity, but the actual data plane traffic is managed by Linkerd’s control plane.

The ServiceMirror resource is the key control plane object. It’s not creating a new set of pods or a direct proxy; it’s a signal to Linkerd to synthesize a local Kubernetes Service that acts as a proxy entry point for the remote service. Linkerd’s control plane then ensures that the necessary cross-cluster routing configuration is in place.

What most people don’t realize is that the ServiceMirror itself doesn’t do the routing. It’s a declaration of intent. The actual routing logic is implemented by Linkerd’s data plane proxies and the control plane’s configuration of those proxies. When you apply a ServiceMirror, you’re essentially telling the Linkerd control plane, "Hey, make sure that when anything in this cluster tries to reach hello.default.svc.cluster.local, it gets sent over the multi-cluster link to cluster-a." The control plane then configures the local Linkerd proxies to intercept traffic destined for that fully qualified service name and redirect it to the appropriate cross-cluster gateway.

The next step is to explore how to secure these cross-cluster communications with TLS and how to manage failover and load balancing across mirrored services.

Want structured learning?

Take the full Linkerd course →