Meshing Windows workloads

Buoyant Enterprise for Linkerd provides the ability for injecting Windows workloads. This allows you to run hybrid clusters that have a mixture of meshed workloads scheduled on both the Windows and Linux nodes.

Prerequisites

  • Kubernetes cluster that has both Linux and Windows nodes
  • Buoyant Enterprise for Linkerd installed on the cluster

Step 1: Install Buoyant Linkerd Windows CNI Plugin

In order to mesh workloads that run on your Windows nodes, you need to install the CNI plugin provided by Buoyant. The reason for doing that is that Kubernetes has no support for running privileges containers on Windows nodes. It is perfectly fine to run your Windows workloads in CNI mode while your Linux ones are using the proxy-init container for network configuration.

In order to install the CNI plugin on the node, 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. Now you are ready to provision some Windows workloads.

Step 2: Provision a Windows based server workload

Now you are ready to provision a windows server workload that is meshed:

kubectl create ns win-test

# create a service for the workload
cat <<EOF > windows-service.yaml
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

kubectl apply -f windows-service.yaml


# create a Deployment
cat <<EOF > windows-deployment.yaml
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

kubectl apply -f windows-deployment.yaml

Make sure your workload is up and running by executing:

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.

Another important adjustment that you need to make is to set 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 > linux-client.yaml
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

kubectl apply -f linux-client.yaml

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-ed:

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.

Learning more