Skip to content

Latest commit

 

History

History
312 lines (222 loc) · 9.6 KB

README.md

File metadata and controls

312 lines (222 loc) · 9.6 KB

Windows Docker Sample

Sample Windows apps running in Docker containers consisting of:

  • ASP.NET Framework Web API to persist JSON files to a host volume
  • .NET Framework Windows Service to watch a host volume for new files
  • Includes Docker Compose and Kubernetes manifests
graph LR
A(REST Client) -- POST --> B[ASP.NET Web API]
B-- Save --> C(Host Volume or Bind mount)
D[Windows Service] -- File Watcher --> C
Loading

Apps built with .NET Framework 4.8.1 on Visual Studio 2022.

To run Dockerized versions locally, you'll need a Docker environment like Docker Desktop and internet access to mcr.microsoft.com to pull the base container images (these are quite large, several GB in size).

MyNewService - Windows Service (.NET Framework)

To test locally without installing as a service:

  • Create the directory C:\uploads\
  • Change project Output Type to "Console Application"
  • Press F5 to build and run/debug
  • Output logs appear in the console
  • Copy a JSON file to C:\uploads\
  • PRess ENTER to exit

To test as a Windows Service:

  • installutil MyNewService.exe
  • Open the Services desktop app. Press Windows+R to open the Run box, enter services.msc, and then press Enter or select OK.
  • To start the service, choose Start from the service's shortcut menu.
  • Output logs appear in the Event Log (open the Event Viewer to see these)
  • To stop the service, choose Stop from the service's shortcut menu.
  • To uninstall the service: installutil.exe /u MyNewService.exe

WebUploaderApp - ASP.NET Framework Web API

To test locally:

  • Create the directory C:\uploads\
  • Make a HTTP POST request to the API:
curl -i -X POST -H "Content-type: application/json" https://localhost:44334/api/Docs -d @payload1.json
  • Check C:\uploads\ for the file

Run everything with Visual Studio 2022

  • Ensure multiple start-up projects are selected in the solution properties: Project(s): WebUploaderApp and MyNewService, Action: Start
  • Press F5 to build and run/debug

Dockerize the Windows Service

docker build -t mynewservice:v1.0 .
docker run --rm --name mynewservice mynewservice:v1.0
docker exec -ti mynewservice -- powershell

Get-Service -Name "MyNewService"
cd c:\uploads\
echo "test" > test1.json
exit

Dockerize the ASP.NET Web API

docker build -t webuploaderapp:v1.0 .
docker run --rm --name webuploaderapp -p 8080:80 webuploaderapp:v1.0

curl -i -X POST -H "Content-type: application/json" http://localhost:8080/api/Docs -d @payload1.json

Run everything with Docker Compose

mkdir ./uploads
docker-compose up

curl -i -X POST -H "Content-type: application/json" http://localhost:8080/api/Docs -d @payload1.json

dir ./uploads
# payload1.json

# CTRL+C to stop

Deploy to Azure Kubernetes Service

Create a basic AKS cluster (not for production, just for demo purposes)

$Cluster_Name="winappaks"
$Resource_Group="winappdemo"

# Adjust to a region near you
$Location="australiaeast"

Create the AKS cluster:

az group create --name $Resource_Group --location $Location

$Kubernetes_Latest_Version=(az aks get-versions --location $Location --query "values[?!isPreview].version" --output tsv) -split '\n' | Sort-Object {[Version]$_} -Descending | Select-Object -First 1

if (Test-Path "win-password.txt") {
    $Win_Password = Get-Content "win-password.txt"
} else {
    $Win_Password = [System.Convert]::ToBase64String((1..32|%{Get-Random -Maximum 256}))
    $Win_Password | Out-File "win-password.txt"
}

$Win_User="azureuser"

az aks create `
    --resource-group $Resource_Group `
    --name $Cluster_Name `
    --node-count 2 `
    --enable-addons monitoring `
    --generate-ssh-keys `
    --network-plugin azure `
    --enable-managed-identity `
    --windows-admin-username $Win_User `
    --windows-admin-password $Win_Password `
    --kubernetes-version $Kubernetes_Latest_Version

az aks get-credentials --resource-group $Resource_Group --name $Cluster_Name
az aks install-cli

Add a Windows node pool to the cluster:

# Add a 4 vCore node pool running Windows Server 2022
az aks nodepool add `
    --resource-group $Resource_Group `
    --cluster-name $Cluster_Name `
    --os-type Windows `
    --name npwin `
    --node-count 1 `
    --node-vm-size Standard_D4s_v3 `
    --kubernetes-version $Kubernetes_Latest_Version

Query the cluster nodes:

kubectl get nodes -o wide

Create Azure Container Registry (ACR) for storing container images:

# append random digits to make unique
$ACR_Name="winappacr$((Get-Random -Minimum 1000 -Maximum 9999).ToString())"  
az acr create --resource-group $Resource_Group --name $ACR_Name --sku Standard --location $Location

Grant image pull access to ACR from the AKS cluster:

az aks update -n $Cluster_Name -g $Resource_Group --attach-acr $ACR_Name

Build and push the container images to ACR using ACR build tasks for Windows container images:

az acr build --registry $ACR_Name --image mynewservice:v1.0 --file .\MyNewService\Dockerfile .\MyNewService\ --platform windows

az acr build --registry $ACR_Name --image webuploaderapp:v1.0 --file .\WebUploaderApp\Dockerfile .\WebUploaderApp\ --platform windows

Create a namespace for the Windows apps:

kubectl create namespace winapp

Create a custom storage class for Azure Files premium storage:

kubectl apply -f kubernetes/docs-afs.sc.yaml

Create the Azure Files PVC:

kubectl apply -f kubernetes\docs-pvc.yaml --namespace winapp

Deploy the Windows Service to the cluster:

# Update the image reference to point to your ACR name
kubectl apply -f kubernetes\mynewservice.app.yaml --namespace winapp

$MyNewService_Pod_Name=(kubectl get pods -n winapp -l app=mynewservice -o jsonpath='{.items[0].metadata.name}')

kubectl logs $MyNewService_Pod_Name -n winapp -f

Deploy the ASP.NET Web API to the cluster:

# Update the image reference to point to your ACR name
kubectl apply -f kubernetes\webuploader.app.yaml --namespace winapp

$WebUploader_Pod_Name=(kubectl get pods -n winapp -l app=webuploader -o jsonpath='{.items[0].metadata.name}')

kubectl logs $WebUploader_Pod_Name -n winapp -f

kubectl port-forward svc/webuploader 8080:80 -n winapp

curl -i -X POST -H "Content-type: application/json" http://localhost:8080/api/Docs -d @payload1.json

curl -i -X POST -H "Content-type: application/json" http://localhost:8080/api/Docs -d @payload2.json

Check the logs for MyNewService.

You can also browse the files in the Azure Files storage account in The Azure Portal - look for the Managed Resource group for AKS, unless you used static provisioning of the Azure Files storage account.

(Optional) Setup Ingress Controller

$Namespace=ingress-basic

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx `
  --create-namespace `
  --namespace $Namespace `
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

$Ingress_IP=(kubectl get services -n $Namespace ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# Name to associate with public IP address
$DNS_Label="webuploader$((Get-Random -Minimum 1000 -Maximum 9999).ToString())"
echo $DNS_Label

# Get the resource-id of the public IP
$Public_IP_Res_Id=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$Ingress_IP')].[id]" --output tsv)

# Update public IP address with DNS name
az network public-ip update --ids $Public_IP_Res_Id --dns-name $DNS_Label

# Display the FQDN
az network public-ip show --ids $Public_IP_Res_Id --query "[dnsSettings.fqdn]" --output tsv

# e.g. webuploaderXXXXXX.australiaeast.cloudapp.azure.com

kubectl apply -f .\kubernetes\webuploader.ingress.yaml -n winapp

Access the Web API using: https://webuploaderXXXXXX.australiaeast.cloudapp.azure.com/api/Docs

(Optional) Configure TLS on Ingress Controller with Let's Encrypt

# TODO: refer to https://learn.microsoft.com/en-us/azure/aks/ingress-tls

Monitoring and Logs

  • In the Azure Portal, open the AKS resource blade
  • On the left nav menu, select Monitoring / Logs and try these container log queries:
ContainerLog
| where LogEntry contains "MyNewService - "
| where LogEntry contains "Message"
ContainerLog
| where LogEntry contains "MyNewService - "
| where LogEntry contains "Message"
| where LogEntry !contains "Monitoring"
| parse LogEntry with * "MyNewService - " updateType ": " filePath
| summarize Count=count() by updateType
  • View as a Table.
  • View as a Chart.

Resources / Credits