Automated Windows Installation With Tekton Pipelines
Introduction
This blog shows how we can easily automate a process of installing Windows VMs on KubeVirt with Tekton Pipelines.
Tekton Pipelines can be used to create a single Pipeline
that encapsulates the installation process which can be run and replicated with PipelineRuns
.
The pipeline will be built with KubeVirt Tekton Tasks, which includes all the necessary tasks for this example.
Pipeline Description
The pipeline will prepare an empty Persistent Volume Claim (PVC) and download a Windows source ISO into another PVC. Both of them will be initialized with Containerized Data Importer (CDI). It will then spin up an installation VM and use Windows Answer Files to automatically install the VM. Then the pipeline will wait for the installation to complete and will delete the installation VM while keeping the artifact PVC with the installed operating system. You can later use the artifact PVC as a base image and copy it for new VMs.
Prerequisites
- KubeVirt
v0.39.0
- Tekton Pipelines
v0.19.0
- KubeVirt Tekton Tasks
v0.3.0
Running Windows Installer Pipeline
Obtaining a URL of Windows Source ISO
First we have to obtain a Download URL of Windows Source ISO.
- Go to https://www.microsoft.com/en-us/software-download/windows10ISO. You can also obtain a server edition for evaluation at https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2019.
- Fill in the edition and
English
language (other languages need to be updated in windows-10-autounattend ConfigMap below) and go to the download page. - Right-click on the 64-bit download button and copy the download link. The link should be valid for 24 hours. We will need this URL a bit later when running the pipeline.
Preparing autounattend.xml ConfigMap
Now we have to prepare our autounattend.xml Answer File with the installation instructions. We will store it in a ConfigMap
, but optionally it can be stored in a Secret
as well.
The configuration file can be generated with Windows SIM or it can be specified manually according to Answer File Reference and Answer File Components Reference.
The following config map includes the required drivers and guest disk configuration.
It also specifies how the installation should proceed and what users should be created.
In our case it is an Administrator
user with changepassword
password.
You can also change the Answer File according to your needs by consulting the already mentioned documentation.
apiVersion: v1
kind: ConfigMap
metadata:
name: windows-10-autounattend
data:
Autounattend.xml: |
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-PnpCustomizationsWinPE" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" processorArchitecture="amd64" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>E:\viostor\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>E:\NetKVM\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>E:\viorng\w10\amd64</Path>
</PathAndCredentials>
</DriverPaths>
</component>
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>Primary</Type>
<Size>100</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Extend>true</Extend>
<Order>2</Order>
<Type>Primary</Type>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Active>true</Active>
<Format>NTFS</Format>
<Label>System Reserved</Label>
<Order>1</Order>
<PartitionID>1</PartitionID>
<TypeID>0x27</TypeID>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Active>true</Active>
<Format>NTFS</Format>
<Label>OS</Label>
<Letter>C</Letter>
<Order>2</Order>
<PartitionID>2</PartitionID>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>2</PartitionID>
</InstallTo>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
</OSImage>
</ImageInstall>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>Administrator</FullName>
<Organization></Organization>
<ProductKey>
<Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
</ProductKey>
</UserData>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<settings pass="generalize">
<component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SkipRearm>1</SkipRearm>
</component>
</settings>
<settings pass="specialize">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>en-US</UserLocale>
</component>
<component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SkipAutoActivation>true</SkipAutoActivation>
</component>
<component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CEIPEnabled>0</CEIPEnabled>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComputerName>WindowsVM</ComputerName>
<ProductKey>W269N-WFGWX-YVC9B-4J6C9-T83GX</ProductKey>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AutoLogon>
<Password>
<Value>changepassword</Value>
<PlainText>true</PlainText>
</Password>
<Enabled>true</Enabled>
<Username>Administrator</Username>
</AutoLogon>
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<NetworkLocation>Home</NetworkLocation>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
<ProtectYourPC>3</ProtectYourPC>
</OOBE>
<UserAccounts>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Password>
<Value>changepassword</Value>
<PlainText>true</PlainText>
</Password>
<Description></Description>
<DisplayName>Administrator</DisplayName>
<Group>Administrators</Group>
<Name>Administrator</Name>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
<RegisteredOrganization></RegisteredOrganization>
<RegisteredOwner>Administrator</RegisteredOwner>
<DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Description>Control Panel View</Description>
<Order>1</Order>
<CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v StartupPage /t REG_DWORD /d 1 /f</CommandLine>
<RequiresUserInput>true</RequiresUserInput>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<Description>Control Panel Icon Size</Description>
<RequiresUserInput>false</RequiresUserInput>
<CommandLine>reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel" /v AllItemsIconView /t REG_DWORD /d 0 /f</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>3</Order>
<RequiresUserInput>false</RequiresUserInput>
<CommandLine>cmd /C wmic useraccount where name="Administrator" set PasswordExpires=false</CommandLine>
<Description>Password Never Expires</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>4</Order>
<Description>Remove AutoAdminLogon</Description>
<RequiresUserInput>false</RequiresUserInput>
<CommandLine>reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /t REG_SZ /d 0 /f</CommandLine>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>5</Order>
<RequiresUserInput>false</RequiresUserInput>
<CommandLine>cmd /c shutdown /s /f /t 10</CommandLine>
<Description>Shuts down the system</Description>
</SynchronousCommand>
</FirstLogonCommands>
<TimeZone>Alaskan Standard Time</TimeZone>
</component>
</settings>
</unattend>
---
Creating the Pipeline
Let’s create a pipeline which consists of the following tasks.
create-source-dv --- create-vm-from-manifest --- wait-for-vmi-status --- cleanup-vm
|
create-base-dv --
create-source-dv
task downloads a Windows source ISO into a PVC calledwindows-10-source-*
.create-base-dv
task creates an empty PVC for new windows installation calledwindows-10-base-*
.create-vm-from-manifest
task creates a VM calledwindows-installer-*
from the empty PVC and with thewindows-10-source-*
PVC attached as a CD-ROM.wait-for-vmi-status
task waits until the VM shuts down.cleanup-vm
deletes the installer VM and ISO PVC.- The output artifact will be the
windows-10-base-*
PVC with the Windows installation.
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: windows-installer
spec:
params:
- name: winImageDownloadURL
type: string
- name: autounattendConfigMapName
default: windows-10-autounattend
type: string
tasks:
- name: create-source-dv
params:
- name: manifest
value: |
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
generateName: windows-10-source-
spec:
pvc:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 7Gi
volumeMode: Filesystem
source:
http:
url: $(params.winImageDownloadURL)
- name: waitForSuccess
value: 'true'
timeout: '2h'
taskRef:
kind: ClusterTask
name: create-datavolume-from-manifest
- name: create-base-dv
params:
- name: manifest
value: |
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
generateName: windows-10-base-
spec:
pvc:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
volumeMode: Filesystem
source:
blank: {}
- name: waitForSuccess
value: 'true'
taskRef:
kind: ClusterTask
name: create-datavolume-from-manifest
- name: create-vm-from-manifest
params:
- name: manifest
value: |
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
generateName: windows-installer-
annotation:
description: Windows VM generated by windows-installer pipeline
labels:
app: windows-installer
spec:
runStrategy: RerunOnFailure
template:
metadata:
labels:
kubevirt.io/domain: windows-installer
spec:
domain:
cpu:
sockets: 2
cores: 1
threads: 1
resources:
requests:
memory: 2Gi
devices:
disks:
- name: installcdrom
cdrom:
bus: sata
bootOrder: 1
- name: rootdisk
bootOrder: 2
disk:
bus: virtio
- name: virtiocontainerdisk
cdrom:
bus: sata
- name: sysprepconfig
cdrom:
bus: sata
interfaces:
- bridge: {}
name: default
inputs:
- type: tablet
bus: usb
name: tablet
networks:
- name: default
pod: {}
volumes:
- name: installcdrom
- name: rootdisk
- name: virtiocontainerdisk
containerDisk:
image: kubevirt/virtio-container-disk
- name: sysprepconfig
sysprep:
configMap:
name: $(params.autounattendConfigMapName)
- name: ownDataVolumes
value:
- "installcdrom:$(tasks.create-source-dv.results.name)"
- name: dataVolumes
value:
- "rootdisk:$(tasks.create-base-dv.results.name)"
runAfter:
- create-source-dv
- create-base-dv
taskRef:
kind: ClusterTask
name: create-vm-from-manifest
- name: wait-for-vmi-status
params:
- name: vmiName
value: "$(tasks.create-vm-from-manifest.results.name)"
- name: successCondition
value: "status.phase == Succeeded"
- name: failureCondition
value: "status.phase in (Failed, Unknown)"
runAfter:
- create-vm-from-manifest
timeout: '2h'
taskRef:
kind: ClusterTask
name: wait-for-vmi-status
- name: cleanup-vm
params:
- name: vmName
value: "$(tasks.create-vm-from-manifest.results.name)"
- name: delete
value: "true"
runAfter:
- wait-for-vmi-status
taskRef:
kind: ClusterTask
name: cleanup-vm
Running the Pipeline
To run the pipeline we need to create the following PipelineRun
which references our Pipeline
.
Before we do that, we should replace DOWNLOAD_URL with the Windows source URL we obtained earlier.
The PipelineRun
also specifies the serviceAccount names for all the steps/tasks and the timeout for the whole Pipeline
.
The timeout should be changed appropriately; for example if you have a slow download connection.
You can also set a timeout for each task in the Pipeline
definition.
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: windows-installer-run-
spec:
params:
- name: winImageDownloadURL
value: DOWNLOAD_URL
pipelineRef:
name: windows-installer
timeout: '5h'
serviceAccountNames:
- taskName: create-source-dv
serviceAccountName: create-datavolume-from-manifest-task
- taskName: create-base-dv
serviceAccountName: create-datavolume-from-manifest-task
- taskName: create-vm-from-manifest
serviceAccountName: create-vm-from-manifest-task
- taskName: wait-for-vmi-status
serviceAccountName: wait-for-vmi-status-task
- taskName: cleanup-vm
serviceAccountName: cleanup-vm-task
Inspecting the output
Firstly, you can inspect the progress of the windows-10-source and windows-10-base import:
kubectl get dvs | grep windows-10-
> windows-10-base-8zxwr Succeeded 100.0% 21s
> windows-10-source-jdv64 ImportInProgress 1.01% 20s
To inspect the status of the pipeline run:
kubectl get pipelinerun -l "tekton.dev/pipeline=windows-installer"
> NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME
> windows-installer-run-n2mjf Unknown Running 118s
To check the status of each task and its pods:
kubectl get pipelinerun -o yaml -l "tekton.dev/pipeline=windows-installer"
kubectl get pods -l "tekton.dev/pipeline=windows-installer"
Once the pipeline run completes, you should be left with a windows-10-base-xxxxx
PVC (backed by a DataVolume).
You can then create a new VM with a copy of this PVC to test it.
You need to replace PVC_NAME with windows-10-base-xxxxx
(you can use kubectl get dvs -o name | grep -o "windows-10-base-.*"
) and PVC_NAMESPACE with the correct namespace in the following YAML.
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
name: windows-10-vm
spec:
dataVolumeTemplates:
- apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: windows-10-vm-root
spec:
pvc:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
source:
pvc:
name: PVC_NAME
namespace: PVC_NAMESPACE
runStrategy: Halted
template:
metadata:
labels:
kubevirt.io/domain: windows-10-vm
spec:
domain:
cpu:
sockets: 2
cores: 1
threads: 1
resources:
requests:
memory: 2Gi
devices:
disks:
- name: rootdisk
bootOrder: 1
disk:
bus: virtio
- name: virtiocontainerdisk
cdrom:
bus: sata
interfaces:
- bridge: {}
name: default
inputs:
- type: tablet
bus: usb
name: tablet
networks:
- name: default
pod: {}
volumes:
- name: rootdisk
dataVolume:
name: windows-10-vm-root
- name: virtiocontainerdisk
containerDisk:
image: kubevirt/virtio-container-disk
You can start the VM and login with Administrator
: changepassword
credentials. Then you should be welcomed by your fresh VM.