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