Back to Blog
4 min read

Running Windows Containers on Azure Kubernetes Service

Windows containers have matured significantly, and Azure Kubernetes Service now provides excellent support for running Windows workloads alongside Linux containers. This is particularly valuable for organizations with .NET Framework applications that need modernization.

Why Windows Containers on AKS?

Many enterprises have legacy .NET Framework applications that cannot easily migrate to .NET Core. Windows containers provide a path to containerization without a complete rewrite, enabling:

  • Consistent deployment across environments
  • Better resource utilization
  • Orchestration benefits of Kubernetes
  • Gradual modernization strategy

Creating an AKS Cluster with Windows Node Pools

First, create a cluster with Azure CNI (required for Windows):

# Create resource group
az group create --name myWindowsAKS --location eastus

# Create AKS cluster with Azure CNI
az aks create \
    --resource-group myWindowsAKS \
    --name myAKSCluster \
    --node-count 2 \
    --network-plugin azure \
    --generate-ssh-keys \
    --windows-admin-username azureuser \
    --vm-set-type VirtualMachineScaleSets \
    --kubernetes-version 1.19.7

# Add Windows node pool
az aks nodepool add \
    --resource-group myWindowsAKS \
    --cluster-name myAKSCluster \
    --os-type Windows \
    --name npwin \
    --node-count 2 \
    --node-vm-size Standard_D4s_v3

Building a Windows Container Image

Here’s a Dockerfile for a .NET Framework application:

# Dockerfile for .NET Framework 4.8 application
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2019

WORKDIR /inetpub/wwwroot

# Copy published application
COPY ./publish/ .

# Configure IIS
RUN powershell -Command \
    Import-Module WebAdministration; \
    Set-ItemProperty 'IIS:\AppPools\DefaultAppPool' -Name processModel.identityType -Value LocalSystem

EXPOSE 80

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD powershell -Command \
    try { \
        $response = Invoke-WebRequest -Uri http://localhost/health -UseBasicParsing; \
        if ($response.StatusCode -eq 200) { exit 0 } \
        else { exit 1 } \
    } catch { exit 1 }

Build and push to Azure Container Registry:

# Build the image
docker build -t myacr.azurecr.io/mywindowsapp:v1 .

# Push to ACR
az acr login --name myacr
docker push myacr.azurecr.io/mywindowsapp:v1

Deploying Windows Workloads

Use node selectors to ensure pods run on Windows nodes:

# windows-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aspnet-app
  labels:
    app: aspnet-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: aspnet-app
  template:
    metadata:
      labels:
        app: aspnet-app
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: aspnet-app
        image: myacr.azurecr.io/mywindowsapp:v1
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: "2"
            memory: "4Gi"
          requests:
            cpu: "1"
            memory: "2Gi"
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 60
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: aspnet-app-service
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: aspnet-app

Mixed Workloads: Windows and Linux

A common pattern is running Windows backend services with Linux frontends:

# mixed-workloads.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: api
        image: myacr.azurecr.io/dotnet-api:v1
        ports:
        - containerPort: 8080

Handling Windows-Specific Considerations

Group Managed Service Accounts (gMSA)

For Windows authentication scenarios:

# gmsa-credential-spec.yaml
apiVersion: windows.k8s.io/v1alpha1
kind: GMSACredentialSpec
metadata:
  name: webapp-gmsa
credspec:
  ActiveDirectoryConfig:
    GroupManagedServiceAccounts:
    - Name: webapp
      Scope: DOMAIN
    HostAccountConfig:
      PluginGUID: '{859E1386-6871-4853-9E65-12F5D68B3D06}'
      PluginInput: contoso.com
  CmsPlugins:
  - ActiveDirectory
  DomainJoinConfig:
    DnsName: contoso.com
    DnsTreeName: contoso.com
    Guid: 244818ae-87ca-4fcd-92ec-e79e5252348a
    MachineAccountName: webapp
    NetBiosName: CONTOSO
    Sid: S-1-5-21-1234567890-1234567890-1234567890

Persistent Storage

Windows containers can use Azure Files for persistent storage:

# windows-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: windows-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: azurefile
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: windows-app-with-storage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: windows-storage
  template:
    metadata:
      labels:
        app: windows-storage
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: app
        image: myacr.azurecr.io/mywindowsapp:v1
        volumeMounts:
        - name: data
          mountPath: "C:\\data"
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: windows-pvc

Monitoring Windows Containers

Enable Container Insights for monitoring:

# Enable monitoring
az aks enable-addons \
    --resource-group myWindowsAKS \
    --name myAKSCluster \
    --addons monitoring \
    --workspace-resource-id /subscriptions/<subscription-id>/resourceGroups/myResourceGroup/providers/Microsoft.OperationalInsights/workspaces/myWorkspace

Conclusion

Windows containers on AKS provide a viable path for modernizing legacy .NET applications. While there are some limitations compared to Linux containers, the platform has matured significantly. The ability to run mixed workloads gives organizations flexibility in their modernization journey.

Key takeaways:

  • Always use Azure CNI for Windows node pools
  • Plan for larger node sizes due to Windows overhead
  • Use node selectors to control workload placement
  • Consider gMSA for Active Directory integration
Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.