Showing posts with label Docker. Show all posts
Showing posts with label Docker. Show all posts

Tuesday, 17 July 2018

Using VSTS Release Management to create a Continuous Delivery pipeline for a Kubernetes service

Introduction


My last blog post was about deploying my dockerized web application to Azure Kubernetes Service. In that post, I explained how to manually deploy docker images to a Kubernetes cluster on AKS. Though deploying manually might be a fulfilling experience, it is hardly something that we would do for an actual production system. Rather, we would want to create a deployment pipeline that automates the entire process.

In this blog post, we would create a Release using VSTS Release Management and a deployment pipeline for deploying to a Kubernetes cluster on AKS.


Creating a Release


The first step is to create a new release. From the "Build and release" menu on your VSTS project, click on the tab "Releases". Click the + button and select "Create release definition".



We are taken to the release definition page. From the template, select "Deploy to a Kubernetes Cluster".


For the names, we named our environment "dev" and gave our release the name of "DevRelease". Our deployment pipeline at this stage looks like a blank slate as shown below


Selecting Artifacts


The input to our deployment pipeline are two artifacts

1) The docker image of our web application. 

This is produced by our VSTS builds and described in the post "Creating CI and Deployment builds for docker images". Our build pushed our images out to docker hub, so we will set up our pipeline to download it from there.

2) A YAML file defining our Kubernetes service. 

Our YAML file looks like following and is already described in the post "Deploying your dockerized application to Azure Kubernetes Service". We saved it in a oneservice.yaml file and committed it to our source git repository.

apiVersion: v1
kind: Service
metadata:
  name: aspnetvuejs
  labels:
    app: asapnetvuejs
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: port-80
  selector:
    app: aspnetvuejs
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: aspnetvuejs
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: private-reg-container
          image: aspnetvuejs/web:latest
          ports:
            - containerPort: 8080
      imagePullSecrets:
        - name: docker-registry


To select the docker image artifact, click on the "+ Add" button in the Artifacts section. From the list, click option "Docker Hub".




From the list of options, select the service endpoint for your Docker Hub repository. If it's not already set up, click on the Manage button to select it.  Select the Namespace and Repository and give your source an alias. Click the Add button add your artifact.

Your application is how shown in list of Artifacts.

Now, we need to add the YAML flie containing definition of our service. To do this, click the "+ Add" button in the Artifacts section again. This time, select option "Git".  From the options displayed, select your team project and the Git repository. For branch, we selected branch "master" and for version selected latest for default version. Our final selection for Git looks as following



Click on the Add button to add the artifact. Now both the input artifact to our deployment pipeline is setup.


Setting Deployment


The next step is to configure our deployment process. Since we selected template "Deploy to a Kubernetes cluster", we already have a task setup in our deployment process as shown by the "1 phase, 1 task" link. Click on the link to view details of the deployment process. We have one task in place to run kubectl apply.



For the task arguments, we need to provide a Kubernetes Service connection. To do this, we clicked on the + New button



1) We give our connection the name "dev".

2) Set the Server URL. You can find it in the Azure portal by visiting the properties page of your Kubernetes service on Azure portal


3) To get your KubeConfig details, type in the following on your Cloud shell session

az aks get-credentials --resource-group kubedemo --name kubedemo
Your configurations are copied to the .kube/config file. Copy the contents of the file and past it in your connection string.

4) Select option "Accept Untrusted Certificate".
5) Click on Verify Connection to verify your connection. The deployment task is now ready to deploy to our Kubernetes cluster.


We will keep our kubectl command as apply and select the "Use configuration files" checkbox so that we can pass the YAML file from our Git repository.

For the Configuration file option, click on the ellipses button. From the Linked Artifacts, select the Git Artifact and select the YAML file we pushed out to our Git source code repository earlier. Click OK and our deployment process is ready for action.

The final and most important step is to Save our build release definition by clicking on the save button.


Running Deployment


At this stage, we have setup a build but it hasn't been deployed to an environment yet. To start a deployment, go back to the Release definitions view by clicking on Release link. Our new release is shown in the list, To deploy, we click on the ellipses with the release and click on "+ Release". A dialog appears asking about target environment and version for our artifacts. Selected the environment "dev" and the latest commit on our git repository. Click on the Create button to deploy to our Kubernetes Service.




Conclusion

This is the final post of my series of posts about creating docker images and deploy them to a Kubernetes cluster in Azure Cluster Service. In the series of posts, I explained

Monday, 16 July 2018

Deploying your dockerized application to Azure Kubernetes Service

Introduction


My last two blog posts were about creating docker images for an ASPNet/Javascript web application. In the first post, I described considerations to produce an optimized image. The second post was about creating a CI/CD process for producing docker images.

Though producing nice lean docker images is good karma, they need to be deployed to a container orchestration system to be deployed in a productionized environment.

The three most popular container orchestration system are Kubernetes, Mesosphere and Docker Swarm. Of these three, Kubernetes is arguably the most popular and we are going to use it to run our container. Running your own kubernetes cluster in a production environment is ostensible. Luckily all major cloud vendor provide kubernetes as a service e.g. Google Cloud has a Google Kubernetes Engine,  Amazon AWS provides the Elastic Container Service for Kubernetes (Amazon EKS) and Microsoft Azure has the Azure Kubernetes Service (AKS). We are going to use Azure Kubernetes Service to run our containers.

Provisioning a Azure Kubernetes Service


1) Log on to the azure portal https://portal.azure.com. In the search text box, type in "Kubernetes". From the result listed, click on Kubernetes Services to see the list of kubernetes services.


2) Click on the Add button to create a new kubernetes service. We are just going to use default options. We filled in details of your kubernetes cluster and click on the button Review + create button. 


Review the settings and click the Create button to create your AKS. It takes a few minutes to get a fully configured AKS to be set up.

Once the cluster is set up, you can view the kubernetes dashboard by clicking on "View Kubernetes dashboard" link and follow the steps in the page displayed. If you are not familiar with Kubernetes dashboard, it is a web based interface that displays all kubernetes resources.


Create a Kubernetes Secret


To enable our Kubernetes cluster to download images from a private DockerHub repository, we will set up a Kubernetes secret containing credentials for our DockerHub repo. To do this, click on the ">_" button to open a Cloud Shell session.

To create the secret run the following command


kubectl create secret docker-registry dockerhubcred --docker-server=https://index.docker.io/v1/ --docker-username=yourusernamee --docker-password=yourpassword --docker-email=youremailaddress


We can check the existence of the new secret by executing 

kubectl get secret

Create a Kubernetes Service


In Kubernetes, Containers are "housed" in Pods, However, pods are mortal and can be recreated at any time. Therefore, the end points of containers are exposed through an abstraction called "Kubernetes Service". 

Creation of kubernetes Service is a two stage process 

1) Create a Kubernetes Deployment: A deployment specifies a template, which include details of docker image, port, etc., replication details i.e. how many pods would be deployed as well as metadata that contains information about how pods would be selected.

2) Create a Kubernetes Service. A service is deployed by specifying the selection criteria, port to be exported and service type. 

To perform the above-mentioned steps, we created the following yaml file. 



apiVersion: v1
kind: Service
metadata:
  name: aspnetvuejs
  labels:
    app: asapnetvuejs
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: port-80
  selector:
    app: aspnetvuejs
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: aspnetvuejs
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: private-reg-container
          image: aspnetvuejs/web:latest
          ports:
            - containerPort: 8080
      imagePullSecrets:
        - name: docker-registry


To run the yaml file, I executed the following series of command on my Cloud Shell session

1) Create the yaml file by executing 
vim oneservice.yaml
Copy the yaml content above and save the file.

2) Create the service and deployed by running
kubectl apply -f oneservice.yaml

The service and deployment is now created. To view your service, I typeed in the following
kubectl get service

The public IP address of the service is displayed in the list of service shown in the result. The deployed web web application can be viewed by typing in the public Ip address. 


Conclusion and next steps


In this post, we created a kubernetes service by declaring kubernetes objects in a yaml file and running them using Cloud Shell. This is useful in explaining the steps and understanding what is involved. However, in reality we would want to do it in a well-defined deployment process. In my next post, I will explain how to deploy to a kubernetes service using VSTS release management.

Friday, 13 July 2018

Creating CI and Deployment builds for docker images

In my last blog post, I wrote about steps to create a Docker container for running aspnet/Javascript Services. In the post, we created a Docker file to create a Docker image with a website running creating on VueJs and Asp.Net core.

In real life, we would like our docker images to be pushed out to a container registry. We would also like it to be done through a team build. We would also want to have a CI build in place so every commit is vetted. This blog post details setting up the CI and deployment builds for producing docker images.

Dockerfile

To start with, lets review the docker file we created in the last post. I have modified it slightly to parameterized the exposed port

# Stage 1 - Restoring & Compiling
FROM microsoft/dotnet:2.1-sdk-alpine3.7 as builder
WORKDIR /source
RUN apk add --update nodejs nodejs-npm
COPY *.csproj .
RUN dotnet restore
COPY package.json .
RUN npm install
COPY . .
RUN dotnet publish -c Release -o /app/

# Stage 2 - Creating Image for compiled app
FROM microsoft/dotnet:2.1.1-aspnetcore-runtime-alpine3.7 as baseimage
ARG port=8080
RUN apk add --update nodejs nodejs-npm
WORKDIR /app
COPY --from=builder /app .
ENV ASPNETCORE_URLS=http://+:${port}

EXPOSE ${port}
CMD ["dotnet", "vue2spa.dll", "--server.urls", "http://+:${port}"]

The docker file creates a docker image that can be pulled and deployed in any application.

Setting up the CI Build


We will use the new YAML build feature in VSTS to setup our CI/CD build. Our YAML CI build file would have a single step that would attempt to build the docker image. The publishing of website is performed within the docker build process as described in the Dockerfile above.

Our very simple YAML file looks like following

name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr)
steps:
  - script: |
      docker build --build-arg port=8080 --rm --compress -t sampleaspjs/web .
    workingDirectory: $(Build.Repository.LocalPath)/web
    displayName: Build Docker Image

We named the yaml file as .vsts-ci.yml. Commit it and push it out to our git repository.

Now that the yaml file is in our code repo, lets go and set up the CI build.
  1. From the Build Definitions page, click on the "+ New" button to create a build definition.
  2. Select the source to VSTS Git, select your Team Project, repository and the default branch of master 
  3. From the list of template, select option YAML in the Configuration as Code classification and click Apply
  4. In the build definition, type in the build name, select the "Hosted Linux Preview" queue and select the YAML path. Make sure Continuous Integration is enabled. Save the build definition.

Now that we have the CI build, lets turn out attention to create a deployment deployment build

Setting up the product build


The steps to create our product build is similar to that of CI build except that we will have a different YAML file. Our build process will go one step further. In addition to creating the docker image, it would also push out docker images to dockerhub

Our YAML file looks like following

name: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr)
steps:
  - script: |
      docker build --build-arg port=8080 --rm --compress -t tiresias/web .
    workingDirectory: $(Build.Repository.LocalPath)/web
    displayName: Build Docker Image

  - script: |
        docker tag aspnetvuejs/web tiresias/web:$(Build.BuildNumber)
    workingDirectory: $(Build.Repository.LocalPath)/web
    displayName: Tag docker image with buil d version

  - script: |
        docker login --username your-username --password your-password
    workingDirectory: $(Build.Repository.LocalPath)/web
    displayName: Docker Login

  - script: |
        docker push aspnetvuejs/web
    workingDirectory: $(Build.Repository.LocalPath)/web
    displayName: Docker Push

We named the yaml file as .vsts-build.yml. Commit it and push it out to our git repository.

The process to set up the deployment build is same as above.
  1. From the Build Definitions page, click on the "+ New" button to create a build definition.
  2. Select the source to VSTS Git, select your Team Project, repository and the default branch of master 
  3. From the list of template, select option YAML in the Configuration as Code classification and click Apply
  4. In the build definition, type in the build name, select the "Hosted Linux Preview" queue and select the YAML path. Make sure Continuous Integration is enabled. Save the build definition.
With the product build in place, we have a mechanism of creating docker images and pushing it out to a container registry.

Next steps

In this post, I described the steps to create a CI / build process for verifying, creating and distributing docker images. In my next post, I will deploy the docker images to a Kubernetes cluster created on Azure Kubernetes Service.


Saturday, 23 June 2018

Running aspnet/JavaScriptServices on Docker

The aspnet/JavaScriptServices project is part of ASP.NET Core and allows you to use Modern Web App frameworks such as Angular, React, etc. for creating web user interfaces of your application, alongside using ASP.NET Core for your web services. If you are using VueJS, the project aspnetcore-Vue-starter does the same thing.

Since, it uses both node.js and .net core, any server hosting the website would need to have both the technologies installed on it. In addition to that, your application might be using node modules and nuget packages, which would need to be deployed on to the server. 

Creating Docker Image

When deploying your application in a docker container, typically you start with a base image. For instance, when deploying your .Net core application, your docker file would start with an instruction like 
FROM microsoft/aspnetcore

But if you are creating a node base application, your docker would start with an instruction like 
FROM node

So, what images should be used for the projects that use both node.js and .net core. The answer is to take one image and install the other library on it. We start with looking at the size of the image. The microsoft/aspnetcore is 345 MB and node:latest is 674MB. 


Both are pretty huge right? So, the first step is to find the slimmer versions of the containers which can be used for production. I found out that there is an alpine image of node, which is about 70 MB that we can use as a baseline and installed .net core on top of it.


We could do that but installing .net core on ubuntu takes far too many commands as compared to installing node.js on ubuntu. So an alternative would be to find an alpine image for dotnet core and install node on it. So, I look and yes there is certainly an alpine image for dotnet core and at about 162MB in size is just right for us.



This would do the trick. So our dockerfile to deploy node on top of dotnet would look like this 


FROM microsoft/dotnet:2.1.1-aspnetcore-runtime-alpine3.7 as baseimage
RUN apk add --update nodejs nodejs-npm

I actually added multi-stage build process in our docker file so that the compilation the application is compiled in it as well and a image produced at the end of the compilation process. The entire Dockerfile for the project produced by aspnetcore-Vue-starter project template looks follows


# Stage 1 - Restoring & Compiling
FROM microsoft/dotnet:2.1-sdk-alpine3.7 as builder
WORKDIR /source
RUN apk add --update nodejs nodejs-npm
COPY *.csproj .
RUN dotnet restore
COPY package.json .
RUN npm install
COPY . .
RUN dotnet publish -c Release -o /app/

# Stage 2 - Creating Image for compiled app
FROM microsoft/dotnet:2.1.1-aspnetcore-runtime-alpine3.7 as baseimage
RUN apk add --update nodejs nodejs-npm
WORKDIR /app
COPY --from=builder /app .
ENV ASPNETCORE_URLS=http://+:$port

# Run the application. REPLACE the name of dll with the name of the dll produced by your application
EXPOSE $port
CMD ["dotnet", "vue2spa.dll"]

PS: In case, you want to use a base image with .net core 2.1 and node already on it, you can use the base image https://hub.docker.com/r/hamidshahid/aspnetcore-plus-node/. Simply pull it by calling 


docker pull hamidshahid/aspnetcore-plus-node

Thursday, 27 October 2016

Running a VSTS Build Agent on a Windows Container

In my previous blog post, I wrote about running a VSTS build agent on a docker container. So, it was only logical to try it out on a Windows Container. I had a few pitfalls in my quest to do that and found an issue on my way, however. it all worked in the end.

Note: 

At the time of writing this blog, running vsts agent is not supported on Windows Nano server. I could only manage to run it on microsoft\windowsservercore image. 


Window Server 2016 and Windows Containers

Windows Server 2016 comes with a full container support powered by built-in operating system features. There is a great session on the internals of windows container on channel9. There are two mechanisms of setting up containers on Windows - Hyper-V Containers which are effectively light weight virtual machines and Windows Containers. I am going to use Windows Containers


1. Windows 2016 Virtual Machine

To host my containers, I got a Windows 2016 virtual machine going. At the time of writing this blog, Windows Server 2016 is still at Technical Preview 2 stage and new updates are coming frequently. They can be downloaded from the Microsoft website

The minimum build you will require is Windows 2016 Server build 14393.

Once you have installed windows and are on the virtual machine, open command prompt and type in winver. You will see a dialog like this. Make sure the build number is at least the required version



2. Install Containers Feature

Type in the following command in a PowerShell console window
Install-WindowsFeatures Containers
The feature needs a restart so type in the following
Restart-Computer -Force
Once the machine is restarted, continue with following 


3. Install Docker

The docker version deployed from the msi isn't supported on Windows Server 2016 yet. For me downloading the docker msi from the docker website didn't work and I got the following error


However, got it working by downloading the following zip file 
https://download.docker.com/components/engine/windows-server/cs-1.12/docker.zip 
and extracting it to Program Files. I did it by running the following in my PowerShell console 
Invoke-WebRequest "https://download.docker.com/components/engine/windows-server/cs-1.12/docker.zip" -OutFile "$env:TEMP\docker.zip" -UseBasicParsing
You will have two executable files in the extracted directory as shown



Add the directory to your path variable. 

Now register the dockerd service by typing the following
dockerd.exe --register-service

Alternate Installation Option

After installing docker, I found that the following was a better and easier way of install docker on Windows Server 2016. Type in the following in your powershell console.

Install-Module -Name DockerMsftProvider -Repository PSGallery -Force 
Install-Package -Name docker -ProviderName DockerMsftProvider
Restart-Computer -Force

Once installed, verify that docker is running fine your machine by type in the following 



docker run microsoft/sample-dotnet

You should see a message of the like "Welcome to .Net Core!" on your console window. This means that your docker instance is working fine.



4. Pulling microsoft/windowservercore image

So, this is where I got stuck a bit. I was trying to use microsoft/nanoserver, which is a fraction of a size of full windows image and support .Net Core. In the end, I found out that running vso agent on server on nano server is not supported yet.

So, I pulled the full server core image. You can do it by running



docker pull microsoft/windowsservercore

The image is about 8GB and takes some time to download. Once pull, run the image by typing in 


docker run microsoft/windowsservercore

At this stage, we are on a windows docker container running Windows 10. I checked in by typing [System.Environment]::OSVersion.Version and got the following version


Major  Minor  Build  Revision

-----  -----  -----  --------

10     0      14393  0


5. Running VSTS Agent


Now that we have a running container, the steps to run VSO Agent is as simple as running it on any Windows 10 machine. 


The only complication is the lack of GUI, so I used powershell to download zip file and extract it as follows



Invoke-WebRequest https://github.com/Microsoft/vsts-agent/releases/download/v2.108.0/vsts-agent-win7-x64-2.108.0.zip -outfile vsts-agent-win7-x64-2.108.0.zip
Expand-Archive -Path .\vsts-agent-win7-x64-2.108.0.zip -DestinationPath C:\vsts-agent 

You will see the usual vsts agent's files in the destination directory. Simply type in .\config.cmd and follow instructions.









Sunday, 2 October 2016

Set up a VSTS Build Agent on a Docker Container

In my last blog post, I wrote about setting up .Net core on an Ubuntu 16.04 machine. In this post, I will go a step forward and explain how to set up a container to run as your build agent.

Containers are brilliant in that they provide a rather lightweight mechanism of setting up desired software in your build agent without installing it on host machine or in a virtual machine. In this post, I will explain setting up a Docker container on a Windows 10 machine, install all the desired software  for build agent and running a build to compile an ASP.net core application on it.

This post is split into following areas
  • Setting up Docker on a Windows 10 machine
  • Create a Docker image to run Build Agent.
  • Configure and run a Build Agent on Docker.
  • Run a Build on the newly setup Build Agent.


Set up a Docker container on a Windows 10 machine

Docker on windows uses Hyper-V to create a linux virtual machine, The Docker daemon is run on this virtual machine.This means that you need at least Windows 10 professional to run Docker. We are using Windows 10 Enterprise. 
To install Docker visit the site https://docs.docker.com/docker-for-windows/ and install the .MSI by clicking the "Get Docker for Windows (stable)" button. The installation will setup Docker for you, by setting up a VM called MobyLinxuVM. It would also add Docker's bin directory to your path, so that the Docker command is available in your command window.
Every docker deployment has a "hello-world" image. To test docker, type in
docker run -it hello-world
You should see the text "Hello from Docker!" in your response.


Create a Docker image to run Build Agent

Now that docker we have a Docker instance running, lets set up a Docker image with all the software needed to run a build agent. The VSO build agent is Node.js based so installing Node.js and it's pre-requisites is a requirement. Also, since the build agent is going to build .Net Core core, we would also be installing .Net core.
To start with, we get a Docker image with the latest version of Ubuntu. At the time of writing this post, the latest version available was 16.04. So we start with getting this version. To do this run
docker run Ubuntu:16.04.
It would look for the instance locally and if it's not found download it from Docker hub. Once the command is complete, the Docker Ubuntu 16.04 image will appear in your “docker images” command

We now install Node.js, npm and vso agent onto the image and store it as another image. To do that the first step is to run the Ubuntu:16.04 image as a container. To do that, run the following command.
docker run -t -i Ubuntu:16.04
You will now be on command prompt of root. To run the desire software, run the following commands one after another
apt-get update
apt-get install npm nodejs
apt-get install nodejs-legacy
npm install vsoagent-installer –g
My Ubuntu 16.04 instance didn't have https protocol installed, so installed it by running
apt-get install apt-transport--https
We will also need to install git on it
apt-get install git
Now, to install .Net Core run the following
sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
apt-get update
apt-get install dotnet-dev-1.0.0-preview2-003131
At this stage, the container contains all the software that you need to run a vso build agent.

Configure and run Build Agent


The only thing left to do now is to configure the build agent. The agent installer allows us to easily create multiple agents on a single server. Each agent will run inside its own folder. We create a folder called MyBuildAgent1 and uses it to run the agent. To do this run the following commands
mkdir MyBuildAgent1
cd MyBuildAgent1
vsoagent-installer
All commands so far has been run using the root user. We don't want to run the build agent to run as root, so will create a user called buildagentuser and switch to it to use it. Below are the command that you need to run.

adduser buildagentuser
chown -R buildagentuser /MyBuildAgent1

The build agent is almost ready to run. At this stage we want to commit this container to save the image. This way we can use it again.

Step out of Docker container by pressing CTRL+P followed by CTRL+Q. You will be back to command prompt. Now type in "docker ps" to view the list of containers running. You will see a response like following




Here, the ubuntu:16.04 running container has the Id "aea7e12541d5". We will commit this container to create a new image. To do this, type in the following on your command prompt
docker commit -a "Hamid Shahid" -m "Basic vso .net core build agent" aea7e12541d5 basicvsogent:v1
If you are following instructions, please use your container id. You can verify it by running "docker images". You will see the new image in the list of images



Since, we just "stepped out" of Docker container we were working now, we will now reattach it and run the build agent. To do this type in
docker attach aea7e12541d5
Now start the build agent by running
node agent/vsoagent
You will be prompted about your VSTS url and the credentials to connect to. I used the following options in the prompt



The user you specify must have the service "Service Account" role in the agent pool you specified. In my case, I had added them to the VSTS group "Agent Pool 1 Service Account Users".


.
Now that my build agent is now running, I will create now create a simple build definition to run the build. We had already created a simple .Net core Asp.net application.


Run a build on the new build agent

We will now create a very simple build definition and execute it on the new agent. Our build definition has three simple steps.
1) In the first step, invoke dotnet with the argument "restore".
2) In the second, invoke dotnet with the argument "build". Make sure, you set the working folder to the source directory
3) In the third step, set the contents to "**/*.dll".
Since, we used the Default Build Pool, set it to use the default build agent.  Now run the build and let's monitor the build agent command window. We should see messages regarding the statue of the build job.

This is so awesome. Now that we have the Docker image captured as well, we can start other build agents and distribute our build load across containers. This is far more efficient than running virtual machines as build agents.
The above would only work for dot net core applications. In my next post, I will write about how to set up VSTS agents on Windows Containers, where we would also have the ability to build .Net applications.