Rancher’s ability to integrate both Istio and Linkerd into a single cluster is a testament to its flexibility, but it also means you’re juggling two distinct service mesh control planes, each with its own set of rules and behaviors.

Let’s see this in action. Imagine you have a simple two-service application, frontend and backend, deployed in the default namespace.

First, we’ll deploy our sample application.

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: curlimages/curl:latest
        command: ["/bin/sh", "-c", "while true; do curl -s http://backend:8080; sleep 5; done"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: nginxdemos/hello:plain-text
EOF

Now, let’s assume you’ve installed both Istio and Linkerd in your cluster, perhaps in different namespaces or using different installation profiles. Rancher’s UI or CLI would guide you through enabling these. For demonstration, let’s imagine Istio is managing the default namespace, and Linkerd is also configured to observe it (though in a real-world scenario, you’d typically choose one or have specific isolation strategies).

If Istio is injected, you’d see its sidecar in your pods:

kubectl get pods -n default -o jsonpath='{.items[*].spec.containers[*].name}'

This command, when run after Istio injection, would reveal a istio-proxy container alongside your application containers. If Linkerd were also managing this namespace, you’d similarly see a linkerd-proxy container. The core challenge arises when both are configured to manage or observe the same traffic.

The fundamental problem this integration solves is providing choice and phased migration. You might have an existing Istio deployment and want to experiment with or gradually move to Linkerd, or vice versa, without a complete application downtime. Rancher acts as the orchestrator, allowing you to enable and configure both.

Internally, Istio and Linkerd operate quite differently. Istio relies on Envoy proxies, injecting them into your pods to intercept and manage all network traffic. It uses a control plane to configure these proxies with routing rules, security policies, and telemetry collection. Linkerd, on the other hand, uses a lightweight, high-performance proxy written in Rust, also injected as a sidecar. Its control plane manages these proxies, focusing heavily on performance, security (mTLS by default), and observability.

When both are present and configured to manage the same namespace or workloads, the behavior becomes a competition for traffic interception. Typically, the first proxy injected into a pod (or the one with a more aggressive injection policy) will capture the traffic. If Istio injects first, its istio-proxy will see the frontend’s request to backend. If Linkerd injects later and has a higher precedence, it might try to re-intercept, leading to unexpected behavior or errors.

The key levers you control are:

  • Injection Policies: How you enable automatic sidecar injection for namespaces. You can disable auto-injection for a namespace for one mesh if the other is managing it.
  • Control Plane Configuration: The specific VirtualService or ServiceProfile resources you create. If Istio has a VirtualService for backend:8080, and Linkerd has a ServiceProfile for the same, the one that successfully intercepts traffic dictates the outcome.
  • Network Policies: Kubernetes NetworkPolicy resources can be used to restrict traffic, and how these interact with the mesh proxies is crucial.
  • Service Discovery: Both meshes integrate with Kubernetes service discovery, but their internal mechanisms for routing and load balancing can diverge.

The most surprising true thing about this setup is that traffic might appear to work for one mesh’s features while failing for the other’s, because only one proxy is actually handling the packets at any given moment. For instance, you might see Istio’s metrics for frontend -> backend traffic, but Linkerd’s mTLS might not be enforced because Istio’s proxy is the one managing that connection.

If you see errors like connection refused or 503 Service Unavailable after enabling both meshes, and your application pods are running, it’s highly probable that the sidecar injection is conflicting. You’ll need to carefully manage which mesh is responsible for which namespaces or workloads by disabling auto-injection for one mesh in a namespace where the other is active.

The next concept you’ll likely grapple with is how to perform phased migrations or run specific workloads on different meshes simultaneously, requiring advanced traffic splitting and policy management across both control planes.

Want structured learning?

Take the full Rancher course →