Deploying a Cross-platform Windows and Linux Application to OpenShift

July 10, 2021

An application can sometimes require diverse components that span technology stacks. There may be a depency on a legacy component built for Windows which may not be suitable for deployment to Linux. The good news is it may still be suitable for deployment to Kubernetes. With a Windows node in your OpenShift cluster you can deploy cross-platform applications that can simultaneously leverage the strengths of Linux and Windows.

📓 This is part 3 of a 3 part series on OpenShift support for Windows containers. Parts: 1, 2, 3

Windows and Linux Living in Harmony

After enabling support for Windows nodes in OpenShift, deploying an app to both Linux and Windows is as easy as TODO

Deploying the NetCandy Store Cross-platform Application

We will deploy a sample application called NetCandy Store using a Helm chart.

As input we will have to provide a few parameters including the Windows node name and the ssh key used by our operator. Let’s define some environmental variables for later reference.

We will use the same ssh key we gave to the Windows Machine Config Operator as it is already trusted by the node. We need this for the image pre-pull job described below.

$ export WSSH_KEY=$(\
  oc get secret cloud-private-key -n openshift-windows-machine-config-operator \
  -o jsonpath='{.data.private-key\.pem}')

We also need to find the name of the Windows node to provide that as a Helm value.

$ export WIN_NODE=$(\
  oc get nodes -l \
  -o jsonpath='{.items[0]}')

In this case the cluster is on Azure, and our Windows node administrator is called capi rather than the default administrator. Define another environmental variable for the ssh user.

$ export WSSH_USER='capi'

Those are all the values we need for the Helm chart. Let’s create a namespace with a descriptive name to install to.

$ oc new-project --display-name="Net Candy Store" netcandystore

Next we will download the application to $CLUSTER_DIR/day2, and finally install it using the Helm chart inside the tarball.

$ curl -o $CLUSTER_DIR/day2/netcandystore-1.0.1.tgz \

$ helm install ncs \
  --namespace netcandystore \
  --timeout=1200s \
  --set ssh.username=${WSSH_USER:-administrator} \
  --set ssh.hostkey=${WSSH_KEY} \
  --set ssh.hostname=${WIN_NODE} \

We can list the chart to see it has been installed in our netcandystore namespace.

$ helm ls --namespace netcandystore
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
ncs     netcandystore   1               2021-05-24 15:10:29.909948 -0700 PDT    deployed        netcandystore-1.0.1     3.1

📺 Watch Demo: Deploying NetcandyStore app with Helm

Speeding Up Application Launch by Pre-pulling Images

As windows containers are large, this Helm Chart defines a job to pre-pull the netcandystore image. You can see it took nearly 10 minutes for this job to run in this instance.

$ oc get job/netcandystore-prepull-image
NAME                          COMPLETIONS   DURATION   AGE
netcandystore-prepull-image   1/1           9m43s      90m

That 10 minute process is something you want to be done before trying to launch your application. Ideally this would execute ahead of time on every node where the app might run.

Here is a look at that job definition. You can see in the command defnition it will ssh to the Windows node and issue a docker pull of the container image.

$ oc get job netcandystore-prepull-image -o yaml | yq e '.spec.template.spec.containers[]' -
  - /bin/bash
  - -c
  - |
    ssh ${SSHOPTS} -i /tmp/ssh/private-key.pem ${SSHUSR}@${SSHHOST} docker pull ${NCIMAGE}
  - name: NCIMAGE
  - name: SSHOPTS
    value: -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
  - name: SSHUSR
    value: capi
  - name: SSHHOST
    value: winworker-g7tsk
imagePullPolicy: Always
name: prepull-image
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
  - mountPath: /tmp/ssh
    name: sshkey
    readOnly: true

The application is comprised of 3 services. A MSSQL pod running on Linux will provide the database for the GetCategories service also running on Linux, and finally the NetcandyStore service that provides the front end of the application will run on Windows.

$ oc get services -n netcandystore
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
getcategories   ClusterIP   <none>        8080/TCP   90m
mssql           ClusterIP   <none>        1433/TCP   90m
netcandystore   ClusterIP   <none>        80/TCP     90m

$ oc get pods -n netcandystore \
    --field-selector=status.phase=Running  \
NAME                             IP             NODE
getcategories-58b98fbd48-bsc8z   win-tmk9g-worker-westus-x4gc6
mssql-1-tsrjx              win-tmk9g-worker-westus-wq4hg
netcandystore-5b989b4bdd-x4pt9     winworker-g7tsk

By describing the netcandystore pod running on Windows, you may notice that the Container ID begins with docker rather than cri-o and that the Node-Selector targetting

$ oc describe pod netcandystore-5b989b4bdd-x4pt9
Name:         netcandystore-5b989b4bdd-x4pt9
Namespace:    netcandystore
Priority:     0
Node:         winworker-g7tsk/
Start Time:   Mon, 24 May 2021 15:20:14 -0700
Labels:       app=netcandystore
Annotations: restricted
Status:       Running
Controlled By:  ReplicaSet/netcandystore-5b989b4bdd
    Container ID:   docker://3ae2156a70ed58a1f4dcb7fe2953741ff25a84e3b15c2f77b30a5f9fc44c2d3e
    Image ID:       docker-pullable://
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Mon, 24 May 2021 15:20:51 -0700
    Ready:          True
    Restart Count:  0
      categoriesMicroserviceURL:  http://getcategories:8080/categories
      /var/run/secrets/ from default-token-nsd48 (ro)
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-nsd48
    Optional:    false
QoS Class:       BestEffort
Tolerations: op=Exists for 300s
        op=Exists for 300s
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  75m   default-scheduler  Successfully assigned netcandystore/netcandystore-5b989b4bdd-x4pt9 to winworker-g7tsk
  Normal  Pulling    74m   kubelet            Pulling image ""
  Normal  Pulled     74m   kubelet            Successfully pulled image "" in 937.999ms
  Normal  Created    74m   kubelet            Created container netcandystore
  Normal  Started    74m   kubelet            Started container netcandystore

Exploring Containers Running on a Windows Node

In part 2 of this series we used ssh to login to the Windows node. Let’s do that again.

Begin by finding the IP address of the Windows node.

$ oc get nodes -l -o wide
NAME              STATUS   ROLES    AGE     VERSION                       INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION    CONTAINER-RUNTIME
winworker-g7tsk   Ready    worker   5h18m   v1.20.0-1030+cac2421340a449     <none>        Windows Server 2019 Datacenter   10.0.17763.1935   docker://19.3.14

Then after openning a remote shell to the bastion pod, we can secure shell to the Windows node at This bastion pod includes a /usr/local/bin/ script to simplify the ssh command.

📓 Azure Difference

Remember our Windows image on Azure uses capi rather than administrator, so provide that username when ssh’ing into the node.

$ oc rsh -n openshift-windows-machine-config-operator deployment/winc-ssh

sh-4.4$ capi
Could not create directory '/.ssh'.
Warning: Permanently added '' (ECDSA) to the list of known hosts.
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Users\capi>

Now we can use the docker command to see what containers are running locally.

PS C:\Users\capi> docker ps -a
CONTAINER ID        IMAGE                                          COMMAND                   CREATED             STATUS              PORTS               NAMES
3ae2156a70ed               "C:\\ServiceMonitor.e…"   2 hours ago         Up 2 hours                              k8s_netcandystore_netcandystore-5b989b4bdd-x4pt9_netcandystore_49c2f119-5ce8-4a75-aeee-5be53ac748c1_0
3c5ee98c833c   "cmd /S /C pauseloop…"    2 hours ago         Up 2 hours                              k8s_POD_netcandystore-5b989b4bdd-x4pt9_netcandystore_49c2f119-5ce8-4a75-aeee-5be53ac748c1_0

By the way, I mentioned those images are large, but how big are they? Large. They are large.

PS C:\Users\capi> docker images
REPOSITORY                               TAG              IMAGE ID          CREATED             SIZE     ltsc2019         3a7f23e29bd7      2 weeks ago         5.28GB                1809             08b77c4924fc      2 weeks ago         14.3GB     1809             ad675c9cb2d5      2 weeks ago         252MB         2021mar8.1       7e8c294b169d      2 months ago        8.09GB   1.3.0            e2b9b3d368da      15 months ago       256MB

Exploring the OpenShift Developer Topology View

Before we leave the CLI, let’s take apply some metadata that will enable the developer view of the OpenShift console to provide some visual context. By applying particular labels and annotations to our deployment resources we can enhance the visual representation of our application in the topology view.

# this pod is on windows
$ oc label deployment/netcandystore \ --overwrite

# it connects to a microservice
$ oc annotate deployment/netcandystore \'[{"apiVersion":"apps/v1","kind":"Deployment","name":"getcategories"}]'

# skipping this, but FYI<github-url><appname>

# this pod is .Net core on linux
$ oc label deployment/getcategories \ --overwrite

# it connects to a database
$ oc annotate deployment/getcategories \'[{"apiVersion":"","kind":"DeploymentConfig","name":"mssql"}]'

# this database is MS SQL
$ oc label deploymentconfig/mssql \ --overwrite

Here is the effect of those labels within the Topology View of Deployed App

And finally, here is the finished product. The Netcandy Store front end application.


OpenShift offers support for legacy Windows applications that may be suitable for containerization. Through a hybrid network configuration and a specialized machine configuration operator, both Linux and Windows workloads can work together in your OpenShift clusters.



comments powered by Disqus