Windows Worker Nodes
Windows worker nodes allow Kubernetes clusters to run Windows Server containers alongside Linux workloads in the same cluster. The control plane always runs on Linux; only worker nodes can be Windows. This is a stable, production-supported configuration since Kubernetes 1.14 (GA), with significant improvements through 1.28+. Windows support targets workloads that cannot be containerized on Linux: legacy .NET Framework applications, COM+ services, Windows-specific drivers, and IIS-based services.
This page covers the node-level architecture differences, component stack, OS compatibility matrix, networking models, resource management, and operational concerns specific to Windows nodes. Windows-specific pod spec fields, security contexts, and workload patterns are covered in the workloads and security sections.
Mixed-OS Cluster Architecture
There is no Windows control plane support. etcd, kube-apiserver, kube-scheduler, and kube-controller-manager run exclusively on Linux. Windows nodes register with the same control plane and are scheduled by the same scheduler — but they require workload-level node selection to receive only Windows-compatible pods.
OS and Version Compatibility
Windows container image compatibility is strictly tied to the host OS build version. A container built on Windows Server 2022 cannot run on a Windows Server 2019 host, and vice versa — unless Hyper-V isolation is used (process isolation is default).
| Windows Server Version | Build Number | Kubernetes Support | Container Base Images |
|---|---|---|---|
| Windows Server 2019 | 1809 / 17763 | GA from 1.14 | mcr.microsoft.com/windows/servercore:ltsc2019, nanoserver:1809 |
| Windows Server 2022 | 21H2 / 20348 | GA from 1.23 | mcr.microsoft.com/windows/servercore:ltsc2022, nanoserver:ltsc2022 |
| Windows Server 2025 | 26100 | GA from 1.32 | mcr.microsoft.com/windows/servercore:ltsc2025 |
| Windows Server SAC (1903–20H2) | various | Deprecated / EOL | Avoid; use LTSC releases |
A pod scheduled to a Windows Server 2022 node must use a container image built on Windows Server 2022. Mismatched builds fail at container start with the container operating system does not match the host operating system. Use multi-arch image manifests with OS version annotations to handle this in heterogeneous fleets.
Windows Node Component Stack
kubelet (Windows binary)
The same Go binary as Linux, cross-compiled for Windows. Communicates with kube-apiserver over HTTPS. Uses CRI gRPC to talk to containerd. Manages pod lifecycle, probes, volume mounts, and node status reporting.
kube-proxy (Windows)
Implements Service routing via Windows Host Network Service (HNS) and Virtual Filtering Platform (VFP) instead of iptables/IPVS. Uses kernelspace proxy mode (default on Windows). iptables mode is not available on Windows.
containerd (Windows)
The primary supported CRI on Windows since Kubernetes 1.20. Uses the windows snapshotter. Supports process isolation (default) and Hyper-V isolation (via RuntimeClass). Managed via Windows services.
CNI plugins (Windows)
Must be Windows-compatible: Flannel (host-gw / overlay), Calico (BGP), Antrea, or cloud-provider CNIs (Azure CNI, AWS VPC CNI). Linux CNI plugins cannot run on Windows nodes.
CSI drivers (Windows)
Windows-specific CSI driver builds required. Most major drivers support Windows: Azure Disk/File CSI, AWS EBS/EFS CSI, vSphere CSI. Uses Windows SMB/iSCSI rather than NFS for shared storage.
Pause / infra container
Windows uses mcr.microsoft.com/oss/kubernetes/pause:<version> as the sandbox container. It holds the HNS network namespace. Must match the node's Windows build version.
Container Isolation Modes
Process Isolation (default)
- Containers share the host OS kernel
- Isolated via Windows Job Objects, silos, and namespaces
- Fast startup, low overhead
- Container image OS build must exactly match host build
- Default for
kubernetes.io/os: windowspods
Hyper-V Isolation
- Each container runs in a lightweight Hyper-V VM
- Full kernel isolation — different build versions allowed
- Requires Hyper-V role on the Windows Server node
- Higher overhead (~1–2s startup, ~100 MB RAM per container)
- Use RuntimeClass with
io.microsoft.virtualmachine.computetopology.processor.count - Required for untrusted multi-tenant workloads on Windows
# RuntimeClass for Hyper-V isolation
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: windows-hyperv
handler: runhcs-wcow-hypervisor # containerd handler for Hyper-V
---
# Pod using Hyper-V isolation
spec:
runtimeClassName: windows-hyperv
nodeSelector:
kubernetes.io/os: windows
containers:
- name: app
image: mcr.microsoft.com/windows/servercore:ltsc2022
Scheduling Windows Pods
Because Linux and Windows nodes coexist in the same cluster, you must explicitly constrain Windows pods to Windows nodes. The scheduler does not automatically enforce OS compatibility.
# Minimum required: nodeSelector
spec:
nodeSelector:
kubernetes.io/os: windows
# Recommended: nodeSelector + toleration for Windows-specific taints
spec:
nodeSelector:
kubernetes.io/os: windows
# optionally pin to specific Windows version:
node.kubernetes.io/windows-build: "10.0.20348" # WS2022
# If you taint your Windows nodes (recommended):
tolerations:
- key: "os"
value: "windows"
operator: Equal
effect: NoSchedule
# Prevent Linux pods from accidentally landing on Windows nodes:
# Add a taint to all Windows nodes:
kubectl taint node windows-node-1 os=windows:NoSchedule
# Linux pods don't tolerate this taint → never scheduled to Windows
# Windows pods add the toleration explicitly → works correctly
OS Version Scheduling Constraints
# Kubernetes adds node labels for Windows build version:
# node.kubernetes.io/windows-build: "10.0.17763" (WS 2019)
# node.kubernetes.io/windows-build: "10.0.20348" (WS 2022)
# node.kubernetes.io/windows-build: "10.0.26100" (WS 2025)
# Pod that requires Windows Server 2022 specifically:
spec:
nodeSelector:
kubernetes.io/os: windows
node.kubernetes.io/windows-build: "10.0.20348"
# Multi-arch image with OS version annotations (preferred):
# Use docker manifest or buildx to create a manifest list covering:
# - linux/amd64
# - windows/amd64:10.0.20348
# - windows/amd64:10.0.17763
# The runtime selects the correct layer automatically
Windows Networking
Windows container networking is fundamentally different from Linux. Instead of Linux network namespaces and iptables, Windows uses:
Host Network Service (HNS)
Windows kernel component managing virtual networks and endpoints. The kubelet and CNI plugin interact with HNS via APIs. HNS replaces the role of Linux netns, veth pairs, and bridges. Persistent across container restarts.
Virtual Filtering Platform (VFP)
Hyper-V switch extension providing programmable packet forwarding. kube-proxy in kernelspace mode programs VFP policies for Service load balancing. Replaces iptables for Service routing on Windows.
Windows CNI Network Modes
| Mode | HNS Network Type | Scope | Use case | Supported CNIs |
|---|---|---|---|---|
| L2Bridge | l2bridge | Same subnet only | On-premises, single subnet per node pool | Flannel host-gw, Calico BGP |
| Overlay | overlay | Cross-subnet via VXLAN | Cloud VMs, multi-subnet clusters | Flannel VXLAN, Antrea |
| Transparent | transparent | Direct routing | Azure CNI, AWS VPC CNI | Azure CNI, AWS VPC CNI for EKS |
kube-proxy on Windows
# kube-proxy configuration for Windows (kernelspace mode)
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "kernelspace" # only supported mode on Windows
winkernel:
enableDSR: false # Direct Server Return (requires WS 2019+ and overlay)
networkName: "Calico" # HNS network name (must match CNI network)
sourceVip: "10.96.0.0" # Required for overlay mode DSR
rootHnsEndpointName: ""
# Note: iptables and ipvs modes are NOT supported on Windows
# kernelspace mode programs VFP rules for ClusterIP, NodePort, LoadBalancer
Windows kube-proxy does not support externalTrafficPolicy: Local with all CNI configurations. sessionAffinity: ClientIP is supported from Windows Server 2022. NodePort Services work on Windows but require the port to not be blocked by Windows Firewall. Always verify firewall rules as part of Windows node setup.
Resource Management on Windows
Windows resource isolation uses fundamentally different primitives than Linux cgroups. The Kubernetes API is the same, but the underlying enforcement differs:
| Resource | Linux mechanism | Windows mechanism | Notes |
|---|---|---|---|
| CPU limit | CFS quota (cpu.cfs_quota_us) | Windows Job Object CPU rate control | Windows enforces per Job Object, not per-period CFS; behavior differs under burst |
| CPU request | cpu.shares weight | Not enforced (advisory only) | Windows has no equivalent of cpu.shares; requests only affect scheduling |
| Memory limit | memory.limit_in_bytes → OOMKill | Job Object memory limit | Process terminated (not OOMKilled via SIGKILL); exit code differs |
| Memory request | Scheduling constraint | Scheduling constraint only | Same as Linux: advisory for scheduler |
| QoS classes | Guaranteed / Burstable / BestEffort | Guaranteed / Burstable / BestEffort | Same classes, but BestEffort and Burstable enforcement is weaker on Windows |
| Ephemeral storage | overlayfs layer tracking | NTFS disk quotas (limited support) | Ephemeral storage limits have partial enforcement on Windows |
# Windows pod resource spec (same API as Linux)
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "1Gi"
# Windows-specific: no hugepages, no CPU manager static policy
# No cgroup v2 (Windows uses HCS/Job Objects)
# No NUMA-aware Memory Manager on Windows
Feature Parity: Linux vs Windows
| Feature | Linux | Windows |
|---|---|---|
| Process isolation containers | ✓ Supported | ✓ Supported |
| Hyper-V isolation | ✗ N/A | ✓ Supported |
| CPU limits enforcement | ✓ CFS quota | ~ Job Object (less precise) |
| Memory limits (OOMKill) | ✓ cgroup hard limit | ~ Job Object (different exit) |
| CPU Manager (static policy) | ✓ Supported | ✗ Not supported |
| Memory Manager (NUMA) | ✓ Supported | ✗ Not supported |
| HugePages | ✓ Supported | ✗ Not supported |
| Device plugins | ✓ Supported | ~ Supported (limited devices) |
| HostPath volumes | ✓ Supported | ~ Windows paths (C:\...) |
| emptyDir | ✓ Supported | ✓ Supported |
| ConfigMap / Secret mounts | ✓ Supported | ✓ Supported |
| Persistent Volumes (CSI) | ✓ Supported | ~ Windows CSI drivers required |
| NFS volumes | ✓ Supported | ~ Limited (SMB preferred) |
| securityContext (Linux) | ✓ Full support | ✗ Ignored |
| securityContext (Windows) | ✗ N/A | ✓ windowsOptions field |
| RunAsUser / RunAsGroup | ✓ Supported | ✗ Not supported |
| Privileged containers | ✓ Supported | ✗ Not supported (use HostProcess) |
| HostProcess containers (node admin) | ✗ N/A | ✓ GA from 1.26 |
| PodSecurityPolicy / PodSecurity | ✓ Full support | ~ Windows-applicable fields only |
| Liveness / Readiness / Startup probes | ✓ All types | ~ exec and httpGet; tcpSocket limited |
| kubectl exec | ✓ Supported | ✓ Supported (cmd.exe / PowerShell) |
| kubectl logs | ✓ Supported | ✓ Supported |
| Graceful shutdown (SIGTERM) | ✓ SIGTERM → SIGKILL | ~ CtrlC event → WM_CLOSE → TerminateProcess |
| Init containers | ✓ Supported | ✓ Supported |
| Sidecar containers | ✓ Supported | ✓ Supported (1.28+) |
| GMSA (Active Directory) | ✗ N/A | ✓ Supported (webhook required) |
| Windows RunAsUserName | ✗ N/A | ✓ Supported |
| IPv6 dual-stack | ✓ Supported | ~ Partial (overlay mode only) |
| Network Policies | ✓ Supported (CNI-dependent) | ~ Supported by Calico/Antrea on Windows |
HostProcess Containers
HostProcess containers (GA in Kubernetes 1.26) are the Windows equivalent of privileged containers on Linux. They run directly in the host network namespace and can access the node's filesystem and Windows API — used for DaemonSet-based node configuration, log collection, and network plugin management.
# HostProcess pod example (DaemonSet for node configuration)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: windows-node-agent
spec:
selector:
matchLabels:
app: windows-node-agent
template:
metadata:
labels:
app: windows-node-agent
spec:
securityContext:
windowsOptions:
hostProcess: true # enables HostProcess mode
runAsUserName: "NT AUTHORITY\\SYSTEM" # required for HostProcess
hostNetwork: true # required for HostProcess
nodeSelector:
kubernetes.io/os: windows
tolerations:
- key: "os"
operator: Equal
value: "windows"
effect: NoSchedule
containers:
- name: node-agent
image: myregistry/windows-agent:ltsc2022
command: ["powershell.exe", "-Command", "C:\\agent\\start.ps1"]
HostProcess containers run as NT AUTHORITY\SYSTEM and have complete access to the Windows node. They are equivalent to running as root with full capabilities on Linux. Restrict which images and registries are allowed to use HostProcess via admission policies (OPA Gatekeeper or Kyverno rules checking windowsOptions.hostProcess).
Group Managed Service Accounts (GMSA)
GMSA allows Windows containers to authenticate to Active Directory resources (SQL Server, file shares, APIs) using machine identity — without storing passwords in Kubernetes Secrets.
# Step 1: Install the GMSA webhook (handles credential spec injection)
# https://github.com/kubernetes-sigs/windows-gmsa
# Step 2: Create GMSACredentialSpec CRD object
apiVersion: windows.k8s.io/v1
kind: GMSACredentialSpec
metadata:
name: webapp-gmsa
spec:
credspec:
ActiveDirectoryConfig:
GroupManagedServiceAccounts:
- Name: webapp-svc-account
Scope: CONTOSO
CmsPlugins:
- PluginGUID: "{DABB1965-CB8D-40B4-B527-F78BCFFBD657}"
DomainJoinConfig:
DnsName: contoso.com
DnsTreeName: contoso.com
Guid: "{guid-of-domain}"
MachineAccountName: webapp-svc-account
NetBiosName: CONTOSO
Sid: "S-1-5-21-..."
# Step 3: ClusterRole + RoleBinding to allow service accounts to use the spec
# Step 4: Pod spec referencing the GMSA
spec:
securityContext:
windowsOptions:
gmsaCredentialSpecName: webapp-gmsa
nodeSelector:
kubernetes.io/os: windows
Windows Node Setup
Prerequisites
# Windows Server requirements:
# - Windows Server 2019 LTSC (build 17763) or 2022 LTSC (build 20348)
# - Containers feature enabled
# - Hyper-V role (only if using Hyper-V isolation)
# - At least 4 CPUs, 8 GB RAM recommended
# Enable Containers feature
Install-WindowsFeature -Name containers -Restart
# (Optional) Enable Hyper-V for Hyper-V isolation
Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart
Install containerd on Windows
# Download and install containerd for Windows
$VERSION = "1.7.13"
curl.exe -L "https://github.com/containerd/containerd/releases/download/v${VERSION}/containerd-${VERSION}-windows-amd64.tar.gz" `
-o containerd.tar.gz
tar -xvf containerd.tar.gz -C "C:\Program Files\containerd" --strip-components=1
# Create containerd service
containerd.exe config default | Out-File -Encoding ASCII C:\Windows\system32\config\systemprofile\AppData\Local\containerd\config.toml
# Register as Windows service
containerd.exe --register-service
Start-Service containerd
# Verify
Get-Service containerd
ctr.exe version
Install kubelet on Windows
# Download kubelet binary
$K8S_VERSION = "v1.29.2"
curl.exe -L "https://dl.k8s.io/release/${K8S_VERSION}/bin/windows/amd64/kubelet.exe" `
-o "C:\k\kubelet.exe"
# kubelet config for Windows
# C:\k\kubelet-config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
webhook:
enabled: true
authorization:
mode: Webhook
cgroupDriver: "" # Not applicable on Windows
containerRuntimeEndpoint: "npipe:////./pipe/containerd-containerd"
clusterDNS:
- "10.96.0.10"
clusterDomain: "cluster.local"
rotateCertificates: true
# Start kubelet as service (typically via nssm or a startup script)
# kubelet.exe --kubeconfig=C:\k\kubeconfig --config=C:\k\kubelet-config.yaml `
# --hostname-override=windows-node-1 `
# --node-labels=kubernetes.io/os=windows
Install kube-proxy on Windows
# Download kube-proxy
curl.exe -L "https://dl.k8s.io/release/${K8S_VERSION}/bin/windows/amd64/kube-proxy.exe" `
-o "C:\k\kube-proxy.exe"
# kube-proxy config
# C:\k\kube-proxy-config.yaml
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "kernelspace"
clusterCIDR: "10.244.0.0/16"
winkernel:
enableDSR: false
networkName: "Calico" # match your CNI network name
# Start: kube-proxy.exe --config=C:\k\kube-proxy-config.yaml
From Kubernetes 1.28, kubeadm join has experimental Windows support. Run kubeadm join <control-plane>:6443 --token <token> --discovery-token-ca-cert-hash <hash> on the Windows node from an elevated PowerShell session. This automates kubelet/kube-proxy installation and configuration, replacing the manual steps above for supported environments.
Storage on Windows
# Windows volume mounts use Windows paths
spec:
containers:
- name: app
image: mcr.microsoft.com/windows/servercore:ltsc2022
volumeMounts:
- name: data
mountPath: "C:\\data" # Windows path; forward slashes also work: C:/data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
# Supported volume types on Windows:
# - emptyDir (tmpfs not supported; uses NTFS temp dir)
# - configMap / secret (projected as files)
# - persistentVolumeClaim (via Windows-compatible CSI drivers)
# - hostPath (Windows paths: C:\..., D:\...)
# Azure Disk CSI on Windows:
# storageClassName: managed-csi
# accessMode: ReadWriteOnce (block device, Windows NTFS formatted)
# Azure File CSI on Windows (SMB):
# storageClassName: azurefile-csi
# accessMode: ReadWriteMany (SMB share, mounted as network drive)
# SMB volume (direct, without CSI):
spec:
volumes:
- name: smb-share
csi:
driver: smb.csi.k8s.io
readOnly: false
volumeAttributes:
source: "//smb-server.domain.com/share"
nodeStageSecretRef:
name: smb-creds
namespace: default
Logging on Windows
# Container logs: same path structure as Linux
# /var/log/pods/__//.log
# On Windows: C:\var\log\pods\...
# Windows Event Log integration (for apps writing to Event Log):
# Use a sidecar or log agent that reads Windows Event Log
# e.g., Fluent Bit Windows build with winevtlog input plugin
# Fluent Bit Windows DaemonSet configuration (simplified):
[INPUT]
Name winevtlog
Channels Application,System,Security
Interval_Sec 1
DB C:\fluent-bit\winevtlog.db
[OUTPUT]
Name es
Match *
Host elasticsearch.logging.svc.cluster.local
# Application stdout/stderr still captured by kubelet via CRI log path
# Windows applications must write to stdout/stderr for kubectl logs to work
# Use -RedirectStandardOutput / Console.WriteLine / Console.Error.WriteLine
Windows Node Diagnostics
# Check kubelet service status (PowerShell)
Get-Service kubelet
Get-EventLog -LogName Application -Source kubelet -Newest 50
# Check containerd
Get-Service containerd
ctr.exe -n k8s.io containers ls
ctr.exe -n k8s.io images ls
# Check kube-proxy
Get-Service kube-proxy
# HNS state (equivalent to ip netns / iptables on Linux):
Get-HNSNetwork # list HNS virtual networks
Get-HNSEndpoint # list container endpoints (pod IPs)
(Get-HNSEndpoint).IPAddress # all pod IPs on this node
# VFP rules (kube-proxy equivalent of iptables-save):
vfpctrl.exe /list-vmswitch-port # list switch ports
vfpctrl.exe /port /list-rule # Service rules on a port
# Check Windows node object
kubectl get node windows-node-1 -o yaml
kubectl describe node windows-node-1
# Run a diagnostic command inside a Windows container
kubectl exec -it my-windows-pod -- cmd.exe
kubectl exec -it my-windows-pod -- powershell.exe
# Windows container event logs via kubectl events
kubectl get events --field-selector involvedObject.name=my-windows-pod
Troubleshooting Runbooks
Windows pod stuck in ContainerCreating — image OS mismatch
# Symptom: pod event "the container operating system does not match the host"
# 1. Get node Windows build
kubectl get node windows-node-1 -o jsonpath='{.metadata.labels.node\.kubernetes\.io/windows-build}'
# e.g.: 10.0.20348 (Windows Server 2022)
# 2. Get container image OS version
docker manifest inspect mcr.microsoft.com/windows/servercore:ltsc2019 | \
jq '.manifests[] | select(.platform.os == "windows") | .platform'
# Look for "os.version": "10.0.17763.xxx" (WS2019 = 17763)
# WS2022 = 20348; they don't match → failure
# 3. Fix: use the correct image tag for your host:
# ltsc2019 → Windows Server 2019 (17763)
# ltsc2022 → Windows Server 2022 (20348)
# Or use a multi-arch manifest that covers both
# 4. Add node build constraint to pod spec:
spec:
nodeSelector:
kubernetes.io/os: windows
node.kubernetes.io/windows-build: "10.0.20348"
Windows pod networking broken — HNS endpoint not created
# Symptom: pod has no IP; kubectl get pod shows no IP; kubectl exec fails
# 1. Check kubelet logs for CNI errors
Get-EventLog -LogName Application -Source kubelet -Newest 100 | \
Where-Object {$_.Message -like "*CNI*" -or $_.Message -like "*network*"}
# 2. Check HNS networks exist
Get-HNSNetwork | Select-Object Name, Type, AddressPrefix
# Expected: network matching your CNI config (e.g., "Calico" or "Flannel")
# 3. Check HNS endpoints
Get-HNSEndpoint | Where-Object {$_.VirtualNetwork -eq (Get-HNSNetwork -Name "Calico").ID}
# 4. Check CNI binary and config
ls C:\opt\cni\bin\
ls C:\etc\cni\net.d\
# 5. Restart CNI DaemonSet pod on the Windows node
kubectl delete pod -n kube-system -l k8s-app=calico-node \
--field-selector spec.nodeName=windows-node-1
# 6. Check Windows Firewall rules (may be blocking pod traffic)
netsh advfirewall firewall show rule name=all | Select-String "Kubernetes"
kube-proxy failing to program VFP rules
# Symptom: Services unreachable from Windows pods
# 1. Check kube-proxy logs
Get-EventLog -LogName Application -Source "kube-proxy" -Newest 50
# 2. Verify HNS network name matches config
Get-HNSNetwork | Select-Object Name
# Compare to winkernel.networkName in kube-proxy config
# 3. Check VFP rules for a ClusterIP service
$svcIP = "10.96.0.100" # ClusterIP of the service
Get-HNSEndpoint | Where-Object {$_.IPAddress -eq $svcIP}
# 4. Restart kube-proxy
Restart-Service kube-proxy
# 5. Check Windows Server version supports kernelspace mode
# Requires Windows Server 2019 1809+
[System.Environment]::OSVersion.Version
kubelet failing to start — containerd pipe not found
# Symptom: kubelet exits with "failed to connect to containerd"
# 1. Check containerd is running
Get-Service containerd
# 2. Verify the named pipe
[System.IO.Directory]::GetFiles("\\.\pipe\") | Where-Object {$_ -like "*containerd*"}
# Expected: \\.\pipe\containerd-containerd
# 3. Check kubelet containerRuntimeEndpoint config
# Should be: "npipe:////./pipe/containerd-containerd"
# NOT: "unix:///..." (Linux path) — common copy-paste error
# 4. Check containerd config for Windows
cat C:\Windows\system32\config\systemprofile\AppData\Local\containerd\config.toml
# Verify: address = "\\\\.\\pipe\\containerd-containerd"
# 5. Restart containerd then kubelet
Restart-Service containerd
Restart-Service kubelet
Production Best Practices
- Always taint Windows nodes — add
os=windows:NoScheduleto every Windows node. This prevents Linux pods (which don't have the toleration) from landing on Windows nodes, where they'd fail to start. - Pin pods to specific Windows build versions — use
node.kubernetes.io/windows-buildin nodeSelector or nodeAffinity. Process isolation requires exact build match; shipping a multi-arch manifest list is the cleanest long-term solution. - Use containerd 1.7+ on Windows — earlier versions have known CRI compatibility issues with Kubernetes 1.26+ (v1alpha2 removal). Verify with
ctr.exe version. - Use Windows LTSC releases only — Semi-Annual Channel (SAC) releases (1903, 20H2, etc.) are EOL. Use LTSC 2019 or LTSC 2022. LTSC 2025 is available from Kubernetes 1.32+.
- Test graceful termination carefully — Windows processes receive a console control event (not SIGTERM). Ensure your .NET/Windows applications handle
AppDomain.CurrentDomain.ProcessExitorConsole.CancelKeyPress. Many Windows services do not handle termination gracefully by default. - Use HostProcess containers for node management DaemonSets — instead of running node configuration scripts directly on the VM, use HostProcess pods to deploy CNI plugins, log agents, and monitoring tools. This gives you the same GitOps/lifecycle benefits as Linux DaemonSets.
- Validate HNS state after kubelet restarts — kubelet restart on Windows can leave stale HNS endpoints. Monitor for pods that have IPs but no HNS endpoint, and add a node-level health check that recycles the CNI DaemonSet pod when this occurs.
- Windows nodes have higher base memory overhead — Windows Server Core uses ~2 GB RAM at idle before any workload. Size Windows nodes with at least 8 GB RAM minimum; 16 GB+ for production. Adjust
kubeReservedaccordingly (recommend 2 Gi minimum for memory). - Monitor Windows Event Log alongside kubectl logs — application failures often appear in Windows Event Log before they surface in container stdout. Deploy a Fluent Bit or similar agent with
winevtloginput as a HostProcess DaemonSet. - Upgrade Windows nodes in a dedicated node pool — Windows node upgrades require node drain + OS upgrade + rejoin (or blue/green node replacement). Never upgrade in-place by patching the OS while the node serves workloads. Use cluster-autoscaler with a separate Windows node group or Cluster API MachineDeployments.