Meshing Windows workloads

In this guide, we’ll walk you through an example of meshing a Windows workload with Buoyant Enterprise for Linkerd.

Prerequisites

  1. A hybrid Kubernetes cluster with both Linux and Windows nodes
  2. Buoyant Enterprise for Linkerd installed on the cluster

Step 1: Install the Linkerd Windows CNI Plugin

In order to mesh workloads that run on your Windows nodes, you need to install the Linkerd Windows CNI plugin on those nodes.

To install the CNI plugin, you can use Helm:

helm upgrade -i -n windows-cni --create-namespace \
  linkerd-enterprise-windows-cni \
  --wait \
  oci://ghcr.io/buoyantio/charts/linkerd-enterprise-windows-cni \
  --devel

This will deploy a DaemonSet on all your Windows nodes. The DaemonSet installs the CNI plugin on these nodes.

Step 2: Provision a Windows based server workload

Now you are ready to provision a Windows server workload that is meshed. First let’s create a namespace:

kubectl create ns win-test

Next, let’s create a service for the workload:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: win-webserver
  namespace: win-test
---
kind: Service
apiVersion: v1
metadata:
  name: win-webserver
  namespace: win-test
spec:
  selector:
    app: win-webserver
  ports:
  - name: http
    port: 80
EOF

Finally, the Windows application deployment:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: win-webserver
  name: win-webserver
  namespace: win-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: win-webserver
  template:
    metadata:
      labels:
        app: win-webserver
      annotations:
        linkerd.io/inject: enabled
        config.linkerd.io/proxy-image: ghcr.io/buoyantio/proxy-win
      name: win-webserver
    spec:
     containers:
      - name: windowswebserver
        image: mcr.microsoft.com/windows-cssc/python:3.9-nanoserver-ltsc2022
        command:
        - python
        - -m
        - http.server
        - "80"
        imagePullPolicy: IfNotPresent
     serviceAccountName: win-webserver
     nodeSelector:
      kubernetes.io/os: windows
EOF

Make sure your workload is up and running:

kubectl rollout status --namespace=win-test deployment win-webserver

Note that the nodeSelector of the workload is set to kubernetes.io/os: windows. The Linkerd injector uses this information to determine that this workload needs to be injected with Windows-specific configuration. In case your nodes use a different labeling scheme, you can explicitly instruct the injector to treat this workload as a Windows one by setting the linkerd.io/inject annotation to windows.

Note also that we are setting the config.linkerd.io/proxy-image annotation to point to the Windows-specific proxy image, which is ghcr.io/buoyantio/proxy-win.

Step 3: Provision a Linux client workload

Now, let’s provision a workload that we can use as a client that can communicate with the Windows server:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: linux-client
  namespace: win-test
  annotations:
    linkerd.io/inject: enabled
  labels:
    app: linux-client
spec:
  containers:
  - name: client
    image: curlimages/curl
    command:
      - "sh"
      - "-c"
      - >
        while true; do
          sleep 3600;
        done
  nodeSelector:
    kubernetes.io/os: linux
EOF

Wait for the client to start:

kubectl wait --namespace=win-test --for=condition=ready pod -l app=linux-client

Step 4: Exercise meshed traffic between client and server

Now you are ready to exercise some traffic between the client and the server:

kubectl exec -c client --stdin linux-client -n win-test -- curl -I -s  http://win-webserver

You should see output similar to:

HTTP/1.0 200 OK
server: SimpleHTTP/0.6 Python/3.9.24
date: Wed, 29 Oct 2025 08:52:15 GMT
content-type: text/html; charset=utf-8
content-length: 676

Now you can observe metrics in the client proxy and convince yourself that traffic is TLS’d:

linkerd diagnostics proxy-metrics  -n win-test pod/linux-client | grep request_total

You should see metrics that indicate outbound meshed communication with the windows web server:

request_total{
    direction="outbound"
    authority="win-webserver.win-test.svc.cluster.local"
    target_addr="10.244.1.158:80"
    target_ip="10.244.1.158"
    target_port="80"
    tls="true"
    server_id="win-webserver.win-test.serviceaccount.identity.linkerd.cluster.local"
    dst_control_plane_ns="linkerd"
    dst_deployment="win-webserver"
    dst_namespace="win-test"
    dst_pod="win-webserver-5cc489cfd4-5fbzp"
    dst_pod_template_hash="5cc489cfd4"
    dst_service="win-webserver"
    dst_serviceaccount="win-webserver"
    dst_zone="0"
    dst_zone_locality="local"
} 1f

Note that the tls label is set to true and the server_id label corresponds to the expected TLS identity of the server.

Congratulations, you have meshed communication between a Linux and Windows workload!

Learning more