最近需要在k8s中解锁win11虚拟机,所以进行了相关学习并记录下此篇博客
win11虚拟机配置
本地先安装win11虚拟机并准备所需要的软件,这里使用linux来创建win11虚拟机
virt-install \
--virt-type=kvm \
--name win11-render-1.6 \
--ram 16384 \
--vcpus=8 \
--os-variant=win11 \
--cdrom=/data/render/Win11_24H2_Chinese_Simplified_x64.iso \
--network=default,model=virtio \
--graphics vnc,listen=0.0.0.0 --noautoconsole \
--disk path=/data/render/virtio-win-0.1.271.iso,device=cdrom \
--disk path=/data/render/win11-render-1.6.qcow2,size=256,bus=virtio,format=qcow2 \
--boot uefi # win11默认使用uefi启动
这里有一个要注意的点是启动的时候除了挂载win11的iso还要挂载virtio-win的驱动,不然会出现个别硬件无法使用的问题
virtio-win下载地址:https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/
驱动安装
参考文档
https://kubevirt.io/user-guide/user_workloads/windows_virtio_drivers/
在安装系统的时候会出现无可选磁盘的问题,这个时候就要安装virtio-win里面的磁盘驱动(vioscsi),同时也可以把网络驱动给安装了(NetKvm)

进到系统后打开设备管理器看看那些设备需要安装驱动(从virtio-win里面安装)

解锁BitLocker
成功进入系统后先别急着对系统进行定制安装,先把磁盘的BitLocker给关闭掉,以便后面进行sysprep打包
在powershell中输入
# 查看磁盘解锁状态
manage-bde -status
BitLocker 驱动器加密: 配置工具版本 10.0.26100
版权所有 (C) 2013 Microsoft Corporation。保留所有权利。
可以使用 BitLocker 驱动器加密
保护的磁盘卷:
卷 C: []
[OS 卷]
大小: 255.19 GB
BitLocker 版本: 无
转换状态: 完全解密
已加密百分比: 0.0% # 查看解密进度
加密方法: 无
保护状态: 保护关闭
锁定状态: 已解锁
标识字段: 无
密钥保护器: 找不到
# 关闭对应磁盘BitLocker
manage-bde -off C:
cloudbase-init 安装
在完成系统定制化软件安装后,下载cloudbase-init进行系统配置 https://cloudbase.it/cloudbase-init/
使用cloudbase-init可以读取虚拟机启动时的配置进行动态配置系统信息(比如hostname、用户密码等)

如果你这个时候已经配置好系统了,可以勾选下面的选项,它将会自动执行sysprep并关机

配置cloudbase-init
cloudbase-init 的典型用法主要是通过配置文件进行配置,以确定要使用哪些服务(services)和插件(plugins),然后在这些文件中设置参数来定制它们的行为以及整体初始化过程
https://cloudbase-init.readthedocs.io/en/latest/
这里进行默认用户密码修改和hostname的配置
首先先配置对应的配置文件 cloudbase-init.conf ,这里只用配置 cloudbase-init.conf ,不用配置cloudbase-init-unattend.conf
因为我们这使用的plugins是 cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin 、cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin 和 cloudbaseinit.plugins.common.userdata.UserDataPlugin
而cloudbase-init-unattend.conf中只能使用MTU 和 hostname plugins
因为要在本地验证配置,所以使用的service是 cloudbaseinit.metadata.services.nocloudservice.NoCloudConfigDriveService
https://cloudbase-init.readthedocs.io/en/latest/tutorial.html#configuration-file
# cloudbase-init.conf metadata_services和plugins等相关配置
metadata_services=cloudbaseinit.metadata.services.nocloudservice.NoCloudConfigDriveService
plugins=cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.windows.updates.WindowsAutoUpdatesPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin
allow_reboot=false # allow the service to reboot the system
stop_service_on_exit=false
准备 NoCloudConfigDriveService 数据
The metadata is provided on a config-drive (vfat or iso9660) with the label cidata or CIDATA.
The default folder structure for NoCloud is:
- /user-data
- /meta-data
这个 NoCloudConfigDriveServic 数据是一个iso磁盘,并且名称要为 cidata 或 CIDATA
里面的文件可以有:meta-data(元数据) 和user-data(用户定义的数据)
https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html
https://cloudbase-init.readthedocs.io/en/latest/plugins.html#user-data-main
以下就是这次meta_data和user_data的配置
# cat meta_data.yaml
instance-id: test-windows
hostname: test-windows
# cat user_data.yaml
#cloud-config
set_hostname: test-windows
users:
- name: admin
passwd: 1qaz@WSX # 对应用户的初始化密码
groups:
- Administrators
runcmd:
- 'powershell -command "Write-Host \"Cloudbase-Init configuration applied successfully!\""'
进行iso打包
https://documentation.ubuntu.com/public-images/public-images-how-to/use-local-cloud-init-ds/
# 下载 cloud-localds
# dnf --enablerepo=devel install cloud-utils
# cloud-localds config-drive.iso user_data.yaml meta_data.yaml
把打包出来的config-drive.iso挂载到对应的虚拟机里面
# virsh edit --domain win11-test-cloudbase-init
...
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<source file='/data/render/cloudbase-init/config-drive.iso'/>
<target dev='sdd' bus='sata'/>
<readonly/>
<address type='drive' controller='0' bus='0' target='0' unit='3'/>
</disk>
...

PS G:\> dir
目录: G:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
--r--- 2025/7/6 23:32 49 meta-data
--r--- 2025/7/6 23:32 224 user-data
接下来可以按照项目要求安装软件,比如在windows上面安装openssh方便使用ansible进行管理
sysprep系统封装
手动进行sysprep系统封装
如果你的系统想要第一次进入时进行重新配置(oobe)就执行
oobe: 客户可以通过开箱即用体验 (OOBE) 添加用户特定的信息并接受 Microsoft 软件许可条款
%WINDIR%\system32\sysprep\sysprep.exe /generalize /shutdown /oobe /mode:vm
如果想要系统第一次进入时进入审核模式,方便系统调式就执行
%WINDIR%\system32\sysprep\sysprep.exe /generalize /shutdown /oobe /mode:vm
如果想要进入到无人接触模式就要配置好unattend文件,同时命令中指定对应的文件
%WINDIR%\system32\sysprep\sysprep.exe /generalize /oobe /shutdown /unattend:xxx /mode:vm
这里使用的是无人接触模式,如果没有其它特殊的配置,unattend.xml可以用cloudbase-init安装时会携带的unattend.xml文件
注意必须要添加 /mode:vm 参数,否则可能会出现启动蓝屏(code:INACCESSIBLE BOOT DEVICE)或者出现提示系统无法启动,请重新安装windwos等无法启动的问题
这里使用添加 /shutdown 参数确保系统打包好后自动关机
配置验证
在配置完上述的CloudBase-Init和Sysprep打包关机后,手动启动系统,在系统启动完成后可以在cloudbase-init的log目录下面查看对应的执行日志
2025-07-07 14:59:04.852 3768 INFO cloudbaseinit.metadata.services.osconfigdrive.windows [-] Config Drive found on G:\
2025-07-07 14:59:04.857 3768 DEBUG cloudbaseinit.metadata.services.baseconfigdrive [-] Metadata copied to folder: 'C:\\WINDOWS\\TEMP\\tmpbxgu6_cf' load C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\metadata\services\baseconfigdrive.py:81
2025-07-07 14:59:04.862 3768 INFO cloudbaseinit.init [-] Metadata service loaded: 'NoCloudConfigDriveService'
2025-07-07 14:59:04.864 3768 DEBUG cloudbaseinit.init [-] Instance id: test-windows configure_host C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py:202
2025-07-07 14:59:04.869 3768 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\utils\classloader.py:35
2025-07-07 14:59:04.873 3768 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\utils\classloader.py:35
2025-07-07 14:59:04.876 3768 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.plugins.windows.updates.WindowsAutoUpdatesPlugin' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\utils\classloader.py:35
2025-07-07 14:59:04.882 3768 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.plugins.common.userdata.UserDataPlugin' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\utils\classloader.py:35
2025-07-07 14:59:04.887 3768 INFO cloudbaseinit.init [-] Executing plugins for stage 'MAIN':
2025-07-07 14:59:04.888 3768 DEBUG cloudbaseinit.init [-] Plugin 'SetHostNamePlugin' execution already done, skipping _exec_plugin C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py:61
2025-07-07 14:59:04.894 3768 DEBUG cloudbaseinit.init [-] Plugin 'SetUserPasswordPlugin' execution already done, skipping _exec_plugin C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py:61
2025-07-07 14:59:04.899 3768 DEBUG cloudbaseinit.init [-] Plugin 'WindowsAutoUpdatesPlugin' execution already done, skipping _exec_plugin C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py:61
2025-07-07 14:59:04.902 3768 DEBUG cloudbaseinit.init [-] Plugin 'UserDataPlugin' execution already done, skipping _exec_plugin C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py:61
2025-07-07 14:59:04.906 3768 DEBUG cloudbaseinit.metadata.services.baseconfigdrive [-] Deleting metadata folder: 'C:\\WINDOWS\\TEMP\\tmpbxgu6_cf' cleanup C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\metadata\services\baseconfigdrive.py:93
2025-07-07 14:59:04.911 3768 INFO cloudbaseinit.init [-] Plugins execution done
从上面的日志可以看到,找到了Config Drive (G盘),获取了hostname为test-windows,并成功启动了 UserDataPlugin、SetHostNamePlugin 、SetUserPasswordPlugin 这几个插件
注意如果想要hostname配置成功,可能需要重新系统
https://cloudbase-init.readthedocs.io/en/latest/userdata.html#cloud-config
kubevirt配置
此时就完成了系统的封装,接下来把对应的qcow2镜像导出到webserver,方便后续导入cdi中
启动虚拟机用的是kubevirt组件,里面一个虚拟机的相关资源有 cdi、vm、vmi和pod,其中
cdi是用来导入虚拟机镜像的,可以用http的方式导入上面打包好的win11的镜像
vm可以理解为定义虚拟机的xml
vmi就是对应的虚拟机运行时的实例
pod就是对应k8s中的运行资源
bios配置
其中需要额外配置一下vm,因为使用的镜像为win11要使用uef启动,kubevirt默认使用的是biso启动的

cpu配置
还有就是要注意cpu的相关配置,不然任务管理中的cpu数量会跟虚拟机配置和设备管理器中显示的数量不一致

不一致的情况如下

cloudbase配置
kubevirt是支持 cloudInitNoCloud 和 cloudInitConfigDrive的配置的,并且也支持从k8s的secret中获取配置信息
具体看如下链接
https://kubevirt.io/user-guide/user_workloads/startup_scripts/#cloud-init-examples
其它参考:
https://kubevirt.io/2022/KubeVirt-installing_Microsoft_Windows_11_from_an_iso.html
至此,就可以在k8s中解锁win11虚拟机了🥰