NEW! Mirantis Academy -   Learn confidently with expert guidance and On-demand content.   Learn More

< BLOG HOME

Windows Worker Nodes for Docker Enterprise Kubernetes: Easily add and scale Windows workload capacity

image
Docker Enterprise 3.1 with Kubernetes 1.17 lets you easily add Windows Kubernetes workers to a cluster (cluster master nodes must still run on Linux), mixing them optionally with Linux workers. Newly-joined Windows workers are immediately recognized by Docker Enterprise Kubernetes, and workloads can be scheduled on them reliably via the nodeSelector element in a deployment spec. 
The ability to orchestrate Windows-based container deployments lets organizations leverage the wide availability of components in Windows container formats, both for new application development and app modernization. It provides a relatively easy on-ramp for containerizing and operating mission-critical (even legacy) Windows applications in an environment that helps guarantee availability and facilitates scaling, while also enabling underlying infrastructure management via familiar Windows-oriented policies, tooling, and affordances. Of course, it also frees users to exploit Azure Stack, and/or and other cloud platforms offering Windows Server virtual and bare metal infrastructure.

Configure Windows Server Workers

Before you add a Windows Server worker to the cluster, you of course have to have the cluster itself, which must run on Linux. If you haven't got a cluster, please set one up by following instructions in our Getting Started Blog. You only need to create a single server cluster.


The next step is to create the Windows Server node and add the Docker Enterprise 3.1 software to it so you can add it to the cluster.


The following instructions detail configuration of a Windows Server 2019 node for use as a Kubernetes worker with Docker Enterprise 3.1, using PowerShell as the Administrator. If using a cloud host, please select a Windows Server 2019 basic OS image, rather than an image preconfigured for containers.  


We start by enabling the Windows containers feature, then restart. Note backticks are used to mark newlines:

Enable-WindowsOptionalFeature `
  -All `
  -FeatureName containers `
  -Online;


Then we restart the computer, if required:

Restart-Computer;


Following restart, we set an execution policy to allow remotely-downloaded scripts to execute in the current session:

Set-ExecutionPolicy `
  -ExecutionPolicy RemoteSigned `
  -Force `
  -Scope Process;


Then we download the installation script:

Invoke-WebRequest `
  -OutFile 'install.ps1' `
  -Uri 'https://get.mirantis.com/install.ps1' `
  -UseBasicParsing;


And execute it directly:

.\install.ps1 -Channel 'test' -dockerVersion '19.03.8';


Following execution, we need to log out and back in, to update path variables:

logoff


Logging back in, we remove the installation script:

Remove-Item -Path 'install.ps1';


Following this initial configuration, all we need to do is download UCP images and store them locally in the Docker repo.


We can optionally turn off PowerShell's status bar, to increase download speed:

$ProgressPreference = 'SilentlyContinue'


Then we download the image bundle:

Invoke-WebRequest `
  -OutFile 'ucp_images.tar.gz' `
  -Uri 'https://packages.docker.com/caas/ucp_images_win_2019_3.3.0.tar.gz' `
  -UseBasicParsing;


Once downloaded, we can load the bundle into the repo:

docker load --input 'ucp_images.tar.gz';


We can then list the images: 

docker images;


And finally, clean up the downloaded bundle archive:

Remove-Item -Path 'ucp_images.tar.gz';


At this point, you can obtain from Docker Enterprise/UCP the "docker join" command required to add nodes to your cluster.  This command may be obtained by either running  “docker swarm join-token worker” from the manager node console, or by navigating to the “nodes” page in UCP  web interface where the join command is shown, ready for copying. Copy this to the PowerShell command line of your new Windows Worker node, and join it up. The node will be recognized by Docker Enterprise, and will appear in the node list (Shared Resources/Nodes) identified as a Windows node.


If you've configured Docker Enterprise to add new nodes as Kubernetes workers (Admin Settings/Orchestration), the new node will be started as a Kubernetes worker. Otherwise, by default, the node orchestrator is configured as ‘swarm.’ To switch it to Kubernetes,  run the following command from the manager node console:

docker node update <nodename> --label-add com.docker.ucp.orchestrator.kubernetes=true
docker node update <nodename> --label-rm com.docker.ucp.orchestrator.swarm

Test Deployment

You can now easily run a test deployment -- this example deploys a Windows webserver with two pods behind a load balancer. To deploy it, you'll need “kubectl” installed on your machine and authenticated to your Docker Enterprise cluster using the env.sh script in the authentication bundle downloaded from the Docker Enterprise UI for your account. See the Getting Started Blog (link) for more.


The following code is the same as presented in Kubernetes documentation.


Start by creating a namespace for your deployment:

kubectl create -f demo-namespace.yaml
# demo-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: demo
 
Then create a new file called win-webserver.yaml, and place in it the following YAML. Note that the YAML includes a (long!) embedded command that configures the webserver and creates a homepage application that responds to requests (in this case, to the IP address of the service on port 80) by identifying the IP of the pod on which the responding webserver is running. This can be used later to demonstrate load-balancing:

# win-webserver.yaml
apiVersion: v1

kind: Service
metadata:
  name: win-webserver
  namespace: demo
  labels:
    app: win-webserver
spec:
  ports:
    # the port that this service should serve on
    - port: 80
      targetPort: 80
  selector:
    app: win-webserver
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: win-webserver
    namespace: demo
  name: win-webserver
  namespace: demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: win-webserver
  template:
    metadata:
      labels:
        app: win-webserver
      name: win-webserver
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - win-webserver
              topologyKey: "kubernetes.io/hostname"
      containers:
        - name: windowswebserver
          image: mcr.microsoft.com/windows/servercore:ltsc2019
          command:
            - powershell.exe
            - -command
            - "<#code used from https://gist.github.com/wagnerandrade/5424431#> ; $$listener = New-Object System.Net.HttpListener ; $$listener.Prefixes.Add('http://*:80/') ; $$listener.Start() ; $$callerCounts = @{} ; Write-Host('Listening at http://*:80/') ; while ($$listener.IsListening) { ;$$context = $$listener.GetContext() ;$$requestUrl = $$context.Request.Url ;$$clientIP = $$context.Request.RemoteEndPoint.Address ;$$response = $$context.Response ;Write-Host '' ;Write-Host('> {0}' -f $$requestUrl) ;  ;$$count = 1 ;$$k=$$callerCounts.Get_Item($$clientIP) ;if ($$k -ne $$null) { $$count += $$k } ;$$callerCounts.Set_Item($$clientIP, $$count) ;$$ip=(Get-NetAdapter | Get-NetIpAddress); $$header='<html><body><H1>Windows Container Web Server</H1>' ;$$callerCountsString='' ;$$callerCounts.Keys | % { $$callerCountsString+='<p>IP {0} callerCount {1} ' -f $$ip[1].IPAddress,$$callerCounts.Item($$_) } ;$$footer='</body></html>' ;$$content='{0}{1}{2}' -f $$header,$$callerCountsString,$$footer ;Write-Output $$content ;$$buffer = [System.Text.Encoding]::UTF8.GetBytes($$content) ;$$response.ContentLength64 = $$buffer.Length ;$$response.OutputStream.Write($$buffer, 0, $$buffer.Length) ;$$response.Close() ;$$responseStatus = $$response.StatusCode ;Write-Host('< {0}' -f $$responseStatus)  } ; "
      nodeSelector:
        beta.kubernetes.io/os: windows



Then create the deployment as a service:

kubectl create -f win-webserver.yaml


Confirm that the service is running:

kubectl get service --namespace demo


An example response:
NAME            TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
win-webserver   NodePort   10.96.29.12   <none>        80:35048/TCP   12m


You'll see the IP address of the service listed. Visiting that in a browser will show the application. Calling the IP with curl several times in succession …

curl 10.96.29.12


… should show that the application has been deployed to two pods, with two different IP addresses, and the incoming requests are load-balanced.


Finally, delete the service and its namespace:
kubectl delete service win-webserver
kubectl delete namespace demo

Choose your cloud native journey.

Whatever your role, we’re here to help with open source tools and world-class support.

GET STARTED
NEWSLETTER

Subscribe to our bi-weekly newsletter for exclusive interviews, expert commentary, and thought leadership on topics shaping the cloud native world.

JOIN NOW