PVE + CloudBaseInit/CloudInit + Windows 全宇宙最全最完善解决方案

相信用过 PVE 的同学都知道 Cloudinit 怎么爽,但是在 Windows 下使用会有一些问题,本文将详细介绍如何在 Windows 下使用 Cloudinit。请注意,本文并不是你找到的一些需要修改 PVE 源码的教程。官方已在最新版本中修复了此问题。

直接上结论:如果你的 PVE 在使用 Windows 的 Cloudinit 时遇到了比如无法自动设置 IP、无法自动设置密码等问题。那么请升级你的 PVE 版本 , 需要大于等于 8.2.4

聪明的你就要问了,为什么需要升级版本,究竟发生了什么?

因为在旧版本中,PVE 通过 cloudinit 创建的镜像写入的信息密码是加密的。而我们在 windows 上使用的客户端是 cloudbase-init,它无法解密这个密码,所以就会导致无法自动设置密码的问题。而你抱着这个问题去全网搜索解决方案,你会发现大部分的解决方案都是修改 PVE 源码,这样做的确可以解决问题,但是这样做的后果是你的 PVE 就不是官方的了,你需要自己维护,而且每次升级都需要重新修改源码。所以这种方法并不是最好的。

于是机智的网友向 PVE 提出了这个问题,终于在 8.2.4 版本中,PVE 官方修复了这个问题,现在我们可以直接使用官方的 PVE 来解决这个问题。 点我直达

升级 8.2.4 版本之后,你不需要修改任何内容。在选择创建 vm 的时候,make os type is any windows version. And boot the vm , install the cloudbase-init. you can click this link to get the latest stable version of cloudbase-init. 对不起,懒得切中文了

然后你需要去修改默认的配置文件,是的。爸爸给你把配置文件准备好了,直接抄吧。 修改的配置文件是 C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf
此配置文件在包含 PVE 文档的基本功能后也自行配置了一些插件。
实现的功能是修改默认的管理员账号,设置密码,设置主机名,设置网络配置,扩展磁盘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[DEFAULT]
username=administrator
groups=Administrators
inject_user_password=true
first_logon_behaviour=no
rename_admin_user=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
verbose=true
debug=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
mtu_use_dhcp_config=false
ntp_use_dhcp_config=false
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
check_latest_version=false
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
plugins=cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin,cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.createuser.CreateUserPlugin
[config_drive]
cdrom=true

这时候,可以把 vm 关机。在 pvm 界面配置 cloudinit,且挂在到 vm 上。启动 vm 就可以看到 cloudinit 生效啦!
有机智的宝宝就要问了,那怎么把自动挂在的 cloudinit 在配置完毕后自动取消挂载呢。这里就需要你修改 cloudinit 的 py 文件了

C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\metadata\services\configdrive.py

把这个文件的一整坨都替换成下面的内容,这样就可以在配置完毕后自动取消挂载了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Copyright 2020 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from oslo_log import log as oslo_logging
from cloudbaseinit import conf as cloudbaseinit_conf
from cloudbaseinit.metadata.services import baseconfigdrive
from cloudbaseinit.metadata.services import baseopenstackservice
import os
import shutil
import ctypes

CONF = cloudbaseinit_conf.CONF
LOG = oslo_logging.getLogger(__name__)


class ConfigDriveService(baseconfigdrive.BaseConfigDriveService,
baseopenstackservice.BaseOpenStackService):

def __init__(self):
super(ConfigDriveService, self).__init__(
'config-2', 'openstack\\latest\\meta_data.json')

def cleanup(self):
LOG.debug('Deleting metadata folder: %r', self._mgr.target_path)
shutil.rmtree(self._mgr.target_path, ignore_errors=True)
self._metadata_path = None
drive_letter = os.popen('wmic logicaldisk where VolumeName="config-2" get Caption | findstr /I ":"').read().strip()
if drive_letter:
LOG.debug('Ejecting metadata drive: %s', drive_letter)
ctypes.windll.WINMM.mciSendStringW(f"open {drive_letter} type cdaudio alias d_drive", None, 0, None)
ctypes.windll.WINMM.mciSendStringW("set d_drive door open", None, 0, None)