Windows CaaS (Container-as-a-Service) - Part 1
Tags: kubernetes, openshift, windows, dotnet
Published: 2021-10-03 21:45:24

Co-Author: @soumyadb  
With the advent of several Kubernetes platforms, enhanced developer experience to support rapid business application development & release is all the more important. Reduction of toil is one key objective. Platform engineering teams of enterprises need to strive to offer "as-a service" for everything. Let's say your platform provider of choice is any of IBMCloud, AWS, Azure, GCP or your own stack of virtualised datacenter. And, you have deployed RedHat OpenShift Container Platform(OCP). You have a large portfolio of Microsoft .NET framework based applications that require to run on Windows containers. But, to host a .NET applications as container on OCP, developers needs to follow at least the following steps:
1. Building and publishing .NET application
2. Creating a Dockerfile with Microsoft aspnet4.8 image as base and copy instructions to copy .NET application into the container
3. Building the Dockerfile and creating the image which will be required to run the application
4. Uploading the image to any available repository(internal or external)
5. Creating manifests in OCP to pull and run this image in a container

As a platform feature, you would like to provide WCaaS ("Windows Containers-as-a-Service"). The purpose of this blog is to automate the above steps and provide WCaaS, allowing web applications to be hosted seamlessly for applications that rely on Windows OS.

##Setting up the Platform
An OCP cluster needs to be configured specially for hosting containers running Windows OS. While setting up the cluster is not a part of the service and is not within the scope of this blog, below references can help you quickly setup the platform.
1. You will need an OCP cluster with OVN Kubernetes network type. If your cluster is running on any other cluster type, like OpenShift SDN, you can change it to OVN Kubernetes. For details, check [here](https://docs.openshift.com/container-platform/4.7/networking/ovn_kubernetes_network_provider/migrate-from-openshift-sdn.html)
2. The OCP cluster must be at version 4.7 or higher
3. You will need to configure hybrid networking in OCP cluster. Also, the underlying vLAN should have DHCP enabled. For more details, see [here](https://docs.openshift.com/container-platform/4.7/networking/ovn_kubernetes_network_provider/configuring-hybrid-networking.html#configuring-hybrid-ovnkubernetes)
4. Install the WMCO (Windows Machine Config) operator as per documentation [here](https://docs.openshift.com/container-platform/4.7/windows_containers/understanding-windows-container-workloads.html#wmco-prerequisites_understanding-windows-container-workloads) and [here](https://docs.openshift.com/container-platform/4.7/windows_containers/enabling-windows-container-workloads.html). Once the WMCO operator has been installed, it will create a Machine Set object in the cluster. This object can be edited to add one or more Windows worker nodes to the OCP cluster.

## Building the Solution
We will be using Ansible to build the automation that will accept a codebase with ASP .NET application (pre-built) and create a container out of it. The first Ansible playbook will:
 
1. Run on a Windows host. Configuring Ansible to run on Windows hosts requires some additional setup. You can refer documentation [here](https://docs.ansible.com/ansible/latest/user_guide/windows_setup.html) for Windows setup and [here](https://docs.ansible.com/ansible/latest/user_guide/windows_winrm.html) for Ansible setup. We would prefer WinRM as the connection method over OpenSSH is still experimental. You will need to add some host variables either directly in your inventory file or as a part of ```group_vars/host_vars```. In the below example, the last 2 variables will be required to log in to the repository where the final image will be uploaded. As a best practice, passwords have been moved to a vault file.
```javascript
ansible_user: <Windows host username>
#ansible_password: <Windows host password>
ansible_connection: winrm
ansible_winrm_transport: basic
ansible_shell_type: powershell
ansible_winrm_scheme: http
ansible_port: 5985
docker_user: <image repository username>
#docker_pass: <image repository password>

``` 
  
2. Accept a git repository URL, username and password with which the Ansible role will clone the code on a Windows builder VM. You can also provide a project name which will help with setting up unique locations on the builder VM and the namespace in OCP. It will create a source folder on the Windows VM and clone the repository.

3. Defaults
```javascript
---
# defaults file for wsaas-deployer
project_name: finalproj
git_repo_full:
https://{{ gitops_puller_username | urlencode }}:{{ gitops_puller_password | urlencode }}@{{gitops_repo}}
branch_name: master
home_folder: C:\Users\<user name>
gitops_folder: '{{ home_folder }}\{{ project_name }}'
docker_repo: <repository name>
docker_image_path: '<full image path>'
 
``` 
4. Ansible Tasks: All tasks assume that PowerShell is installed and is configured as the default shell on the Windows host. We have PowerShell version 5.1 installed on the Windows VM.
```javascript
---
# tasks for extracting codebase from source
- name: Create project folder
   win_shell: 'New-Item -Path {{ home_folder }} -Name {{ project_name }} -ItemType "directory"'

- name: Create source folder
  win_shell: 'New-Item -Path {{ home_folder }}\{{ project_name }} -Name src -ItemType "directory"'

- name: Set Git SSL verify to false
  win_shell: 'git config --global http.sslVerify "false"'

- name: Clone repository to source folder
  win_shell: 'git clone {{ git_repo_full }} {{ home_folder }}\{{ project_name }}\src'
ignore_errors: yes
  register: result

- name: Set failure
  fail:
    msg: "Code repository cloning failed, please check ansible log for details"
  when: "'Failed to write item to store' not 
  in result.stderr"
 
``` 
* This script clones the web application executables to the src folder
```javascript
PS C:\Users\ansible\review-project> ls src
Directory: C:\Users\ansible\review-project\src
Mode LastWriteTime Length Name
 - - - - - - - - - - - - - - 
d - - - 9/30/2021 11:39 AM bin
d - - - 9/30/2021 11:39 AM Content
d - - - 9/30/2021 11:39 AM fonts
d - - - 9/30/2021 11:39 AM Scripts
d - - - 9/30/2021 11:39 AM Views
-a - - 9/30/2021 11:39 AM 32038 favicon.ico
-a - - 9/30/2021 11:39 AM 107 Global.asax
-a - - 9/30/2021 11:39 AM 3134 Web.config

``` 
 
5. A second Ansible playbook will be used to create a container image by taking Microsoft's ASP.NET runtime image as base and adding code from the src folder. This will be done by building a Dockerfile which has reference to Microsoft's ASP.NET runtime image and the source executables. This script will:

* Create a Dockerfile in the Windows VM which uses Microsoft ASP.NET runtime image as base. For documentation on Microsoft provided images related to .NET Framework, check [here](https://hub.docker.com/_/microsoft-dotnet-framework-aspnet/). Note that this image supports only the .NET Framework 4.8 runtime and will not work with .NET Core applications.

```javascript
#Build asp.net image

FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-2004
WORKDIR /inetpub/wwwroot
COPY src/.  ./
```
* This script clones the web application executables to the src folder

* The Dockerfile will then be built, tagged and uploaded to any available image repository. For the purpose of this blog, we will be using JFrog Artifactory.

* Tasks: In order to execute below tasks, Docker needs to be installed on the Windows host. We are using Docker version 20.10.5.
```javascript
---
# tasks for building web server image with website executables
- name: Copy the Dockerfile
win_copy:
src:  Dockerfile
dest: '{{ home_folder }}\{{ project_name }}'

- name: Build the aspnet docker image
win_shell: 'cd {{ home_folder }}\{{ project_name }}; docker build -t {{ project_name }}image:latest -f ./Dockerfile .'

- name: Tag the aspnet image
win_shell: 'docker tag {{ project_name }}image:latest {{ docker_repo }}/{{ docker_image_path }}/{{ project_name }}:latest'

- name: Login to Artifactory
win_shell: 'docker login -u {{ docker_user }} -p {{ docker_pass }} {{ docker_repo }}'

- name: Push the tagged aspnet image to Artifactory
win_shell: 'docker push {{ docker_repo }}/{{ docker_image_path }}/{{ project_name }}:latest'
```

* Repository:
![Jfrog Artifactory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze3y7osyapmnfu7v3j3p.png)

*At this point, you will be able to log into the Windows VM and view the image. You may also run it on local Docker and test the application.
* Docker image:
```javascript
PS C:\Users\ansible\review-project> docker images

REPOSITORY                                                                                                                                                                  TAG                     IMAGE ID            CREATED       SIZE

docker-prod-local.artifactory.yourdomain.com/caas/windows/dotnet/apps/review-project   latest                  dbac187e2437   2 days ago     5.52GB

aspnet                                                                                                                                                                             latest                  acb445991370    3 days ago     5.52GB
``` 
* Docker run of image on Windows VM:

```javascript
PS C:\Users\ansible\review-project> docker run -d -p 8082:80 --name myapp-review aspnet

ad1a32f8ba6918555c98f40ade7c8eb8abae528ba58279a1f504c2867b0251b5
``` 
* Sample Application
![Sample App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7fe5nlh1w8gxmj7ywmr.png)

The next step would be to automate the creation of a container on RedHat OCP using the image created in the previous step. We will explore this in the next part of this blog

## GitHub Repos: 
1.[Ansible-website-deployer](https://github.com/EverythingAsPlatform/ansible-website-deployer)
2.[Ansible-bootstrap-website](https://github.com/EverythingAsPlatform/ansible-bootstrap-website)
3.[Sample Web App](https://github.com/EverythingAsPlatform/openshift-sample-ap)