7 minutes
Using Windows containers with Jenkins on Kubernetes 1.14
Kubernetes 1.14 was released in March 2019 and the release brought production support for Windows Containers on Windows Server nodes. Before moving on, I would like to highlight a few things from the previous link:
- Kubernetes control plane runs in Linux (and there is no plan to change that for a full Windows Kubernetes cluster)
- Versions supported for worker nodes and containers: Windows Server 1809/Windows Server 2019
- Windows containers have to be scheduled on Windows nodes
At the time this post was written (Nov 19), AKS, GKE and EKS offer some level of support for Windows based containers (EKS is the first to offer GA support for Windows based containers). The EKS documentation provides instructions on how to setup Windows nodes using eksctl and EKS. You might want to try that since it is GA. The following are the instructions for AKS 1.14+ (where Windows Containers are still under preview) but the example and instructions related to Jenkins we will use should be able to be used in a similar setup where Kubernetes has a Windows node pool.
Infrastructure Setup and Windows Nodepools
Jenkins has the ability to use Kubernetes pods as agents to build and deploy applications thanks to the Kubernetes plugin. In this blog post, we will create a simple declarative pipeline that has Linux and Windows containers as agents in AKS.
First, we need to follow Azure’s documentation to create the needed infrastructure to be able to deploy Linux and Windows based containers. Please make sure that you review AKS documentation and are aware of the limitations before running this in a production cluster. As explained here, the registration for the preview features cannot be unregistered at this moment.
The documentation at a high level goes through the following steps (using the Azure CLI):
- Install aks-preview CLI extension for Azure CLI
- Register the Windows preview feature needed for the Windows based containers
- As mentioned in the document, the Multiple Node Pool feature is also needed to create a separate Windows node pool.
- Create a new resource group (if needed)
- Create an AKS cluster
- You can use
--nodepool-namewithaks create clusterto name your control plane node pool (i.edefault)
- You can use
- Add a Windows Server node pool
- This will be a node pool for Kubernetes pod Windows agents, we can name it
--name winage
- This will be a node pool for Kubernetes pod Windows agents, we can name it
Node pools give us the possibility to extend our Kubernetes cluster with more types of machines depending on our use and budget (see available options and default values) for AKS. As an example, we are going to add two more identical pools (one for masters and one for Linux agents) but you can pick different machine sizes and node counts depending on your need (just make sure that the VMs used for Jenkins masters support Premium Storage as Jenkins requires high IOPS for better performance):
Linux Jenkins master pool example:
az aks nodepool add \ --resource-group myResourceGroup \ --cluster-name eastUSAKS \ --os-type Linux \ --name masters \ --node-count 1 \ --kubernetes-version 1.14.6 \ --node-vm-size Standard_DS2_v2Linux Jenkins agent pool example:
az aks nodepool add \ --resource-group myResourceGroup \ --cluster-name eastUSAKS \ --os-type Linux \ --name linage \ --node-count 1 \ --kubernetes-version 1.14.6 \ --node-vm-size Standard_DS2_v2Once the pools are created, you can see them in the Azure portal:
Jenkins installation using Helm
Helm is the Kubernetes Package Manager and we can use it to install Jenkins using the official chart. If you haven’t installed Helm before, you can follow these instructions to install it. Using a nodeSelector in the values file (values.yaml) used by the chart will allow us to specify in which nodepool Jenkins will be installed (you can find master.nodeSelector option in the Jenkins chart link). For simplicity, we are only going to configure the values.yaml file so that it deploys Jenkins using such nodeSelector option but the file can include a lot more options. In this case, we need to make sure that Jenkins runs in the nodepool named masters (AKS assigns the nodepool name as the value of the agentpool tag, more on this in the next section)
- values.yaml
master:
nodeSelector:
agentpool: masters
Installing the chart (add
--namespace yourNamespaceto the command if you want to deploy Jenkins in a specific namespace):helm install --name jenkins -f values.yaml stable/jenkins
The version that was installed in this example is 2.190.2.
Once installed, follow the “NOTES” section in the console that will allow you to get your Jenkins (user: admin) password and URL. It will include something similar to this:
printf $(kubectl get secret jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode);echo
export SERVICE_IP=$(kubectl get svc jenkins --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
echo http://$SERVICE_IP:8080/login
You should be able to access Jenkins with the provided URL at this point.
Creating the pipeline with Windows and Linux Containers
Make sure to check the version of the Kubernetes plugin installed. This example uses the version 1.21.0 which supports the Windows container step.
Let’s create a pipeline by clicking
"New Item", then enter a name for your pipeline job (i.e"win-lin-pipeline") and select"Pipeline"as your job type.Select:
- Definition:
"Pipeline script from SCM" - SCM:
Git - Repository URL:
https://github.com/mluyo3414org/pod-templates.git
- Definition:
- Click
Save
Let’s take a look at the repository structure in https://github.com/mluyo3414org/pod-templates.git:
├── Jenkinsfile
├── README.md
├── linux
│ └── nodejs-pod.yaml
└── windows
└── dotnet-pod.yaml
Both
nodejs-pod.yamlanddotnet-pod.yamlare files describing the Kubernetes pod agents used in the Jenkinsfile.The
dotnet-pod.yamlhas two container definitions: a Windows basedjnlp(jenkins/jnlp-agent:latest-windows) and awindows-dotnetcontainer (mcr.microsoft.com/dotnet/core/sdk:2.1). We need to overwrite thejnlpcontainer in this pod since otherwise it will use the Linux basedjnlpcontainer defined underJenkins --> Configuration --> Pod templates. In this case, Jenkins was automatically configured with the Linux pod:jenkins/jnlp-slave:3.27-1.
kind: Pod
metadata:
name: windows
spec:
containers:
- name: jnlp
image: jenkins/jnlp-agent:latest-windows
tty: true
- name: windows-dotnet
image: mcr.microsoft.com/dotnet/core/sdk:2.1
tty: true
nodeSelector:
agentpool: winage
- The
nodejs-pod.yamlhas thenode(nodeJS) container definition and will use the default Linux basedjnlp(jenkins/jnlp-slave:3.27-1) mentioned before.
kind: Pod
metadata:
name: nodejs-app
spec:
containers:
- name: nodejs
image: node:slim
command:
- cat
tty: true
nodeSelector:
agentpool: linage
Notice both pod yaml definitions use
nodeSelector(nodeSelector) to decide where these pods should be scheduled. If this is not specified, the Kubernetes scheduler will provision the pods following the default behavior which could possibly start a pod in a node with the wrong OS. The tags used for the nodeSelectors are the default tags assigned by Azure when specifying the nodepool name:agentpool : nameOfNodePool. To find the node tags you can use the command:kubectl get nodes --show-labels. Other options to prevent scheduling errors aretaints(more info).
Scheduling error when not using nodeSelectors. Debug using kubectl describe podnameJenkinsfile:
pipeline {
agent none
options {
buildDiscarder(logRotator(numToKeepStr: '2'))
skipDefaultCheckout true
}
stages {
stage('Test-linux') {
agent {
kubernetes {
label 'nodejs-pod'
yamlFile 'linux/nodejs-pod.yaml'
}
}
steps {
checkout scm
container('nodejs') {
echo 'Hello World!'
sh 'node --version'
}
}
}
stage('Test-windows') {
agent {
kubernetes {
label 'windows-pod'
yamlFile 'windows/dotnet-pod.yaml'
}
}
steps {
bat 'dir'
container(name:'windows-dotnet'){
bat 'dotnet -h'
}
}
}
}
}
This is a declarative pipeline using
agent noneso that we can specify agents per stage.yamlFileis used to read the pod template from a file location which is also in this repo. You can either define the pod template in another file, in the same Jenkinsfile or in the Jenkins Configuration page. More info about the syntax used in this pipeline can be found here.The
node --versioncommand is executed inside thecontainerstep otherwise it will get executed inside the jnlp (Linux) container and fail as it doesn’t have node installed. Similarlybat 'dir'gets executed in the jnlp (Windows) container andbat 'dotnet -h'is executed inside the dotnet container.Here is another example on how to use a Windows container using a scripted pipeline and tested in EKS.
Conclusion
In this post we were able to discuss how you can take advantage of Jenkins and Kubernetes to use Windows containers as part of your CI/CD pipelines. The architecture and process discussed in this post are depicted by this high-level diagram of the Kubernetes cluster and some of its components.
Listen to Windows Server Containers on the Kubernetes Podcast for more information.


