这台机器是全息教室全息图像还原处理器,上面有一个面板显示显卡显存不小,于是我觉得平时不用怪可惜的,通过一番折腾发现不仅现存OK,CPU和内存更是OK,完全可以跑几个服务,可惜是Windows10,于是我进一步折腾HyperV,手动一个个创建嫌麻烦,于是结合terraform和terraform-provider-hyperv_v1.5.4搞了批量IaC,批量造出来了VM登陆不了,于是又搞了cloudinit。现在总算是初步搞好了,接下来就是搭k3s跑服务了。(感谢opencode X Big Pickle)
环境:
- 110 宿主机 (SYZX-HY2-TX / 192.168.67.110) 运行 Hyper-V;
- 120 主机 (192.168.67.120) 运行 Terraform,通过 SSH (
tfadmin) 驱动 110 的 Hyper-V。 - 产物:ubuntu01/02/03 三台 Gen2 Ubuntu 24.04 VM,静态 IP 192.168.67.201/202/203。

1. 总体架构
sql
120 主机 (Terraform) 110 宿主机 (Hyper-V)
+---------------------------+ +----------------------------------------------+
| D:\terraform\demo1\ | SSH/PS | D:\HyperV\ |
| main.tf | ==========> | ISO\ubuntu0X-cidata.iso <-- cloud-init seeds|
| provider.tf | (tfadmin) | VirtualHardDisks\ubuntu0X-fresh.vhdx (全量盘)|
| variables.tf | | CloudInit\ubuntu0X\{user-data,network-config,|
| versions.tf | | meta-data} (ISO 源) |
| terraform.tfvars | +----------------------------------------------+
+---------------------------+ |
provider 自动创建 / 更新
|
v
ubuntu01 / 02 / 03 (Gen2, 2vCPU, 2GB)
- DVD: cidata.iso (cloud-init 数据盘)
- HDD: ubuntu0X-fresh.vhdx (系统盘, 50G 动态, 全量)
- NIC: Terraform-Lab-External
- Firmware: SecureBoot=Off
数据流:CloudInit 源目录 → genisoimage 生成 cidata ISO → Terraform 挂载到 VM DVD → 首启时 cloud-init 读取 seed 完成初始化。
2. 自洽性结论
结论:配置基本自洽,一个已知且无害的持久 drift。
通过 terraform plan(在 120 上执行 refresh + plan)比对三台 VM:
| 维度 | Terraform 配置 | Hyper-V 实际 | 是否自洽 |
|---|---|---|---|
| 主机名 | ubuntu01/02/03 | 同 | ✅ |
| 网卡名 | 每台 ubuntu0X |
同 | ✅ |
| 静态 IP | cloud-init network-config: .201/.202/.203 | SSH 实测同上 | ✅ |
| 交换机 | Terraform-Lab-External | 同 | ✅ |
| 系统盘 | ubuntu0X-fresh.vhdx (loc 0) | 同 | ✅ |
| 数据盘 | ubuntu0X-cidata.iso (loc 1) | 同 | ✅ |
| SecureBoot | Off | Off | ✅ |
| CPU/内存 | 2vCPU / 2GB(512M最小/1T上限) | 同 | ✅ |
| checkpoint | Production | 同 | ✅ |
| static_mac_address | 00155D436E15/16/17 | 00155D436E18/19/1A | ⚠️ 持续 drift |
2.1 唯一 drift:static_mac_address
terraform plan 每次都报:
ini
~ static_mac_address = "00155D436E1A" -> "00155D436E15" (ubuntu01) 等
- 成因 :Bafbi/hyperv provider 的
CreateOrUpdateVmNetworkAdapters在 VM 已存在网卡时不会重建网卡改 MAC(Hyper-V 中改 MAC 必须删除重建适配器,provider 未实现)。MAC 在 VM 创建时由 Hyper-V/provider 分配(1A/19/18),配置值(15/16/17)从未真正生效。 - 为什么无害 :cloud-init 的
network-config用match: name: "eth*"匹配网卡,不依赖 MAC,因此 IP 配置正确、SSH 正常(已实测)。MAC 不属于 cloud-init 自洽依赖项。 - 建议 :可将 main.tf 中
static_mac_address更新为实际值(1A/19/18)以消除 drift,或删除static_mac_address字段。二选一,其余无任何 drift。
2.2 boot_order 的"差异"(正常现象)
- Terraform 配置 boot_order:Network → DVD → HDD
- Hyper-V 实际引导序:HD (Ubuntu EFI) → Network → SCSI(1)=DVD → SCSI(0)=HDD
这是因为 Ubuntu 首启(cloud-init 完成后)将自身的 EFI 引导项(\EFI\ubuntu\shimx64.efi)注册为第一引导项 ,UEFI 引导管理器接管了优先级。各 VM 都能正常从硬盘引导进入 Ubuntu(已实测 SSH 登录),DVD 仅作为 cloud-init 数据盘被检测,无需进入引导序变。属正常 UVEFI 行为,非问题。
3. Terraform 配置详解(120 主机 D:\terraform\demo1\)
| 文件 | 作用 | 关键内容 |
|---|---|---|
versions.tf |
声明 provider 版本 | Bafbi/hyperv 1.5.4, Terraform >= 1.5.0 |
provider.tf |
连接远端 Hyper-V | ssh=true, ssh_host=192.168.67.110, ssh_user=tfadmin, 密码取变量 |
variables.tf |
定义输入变量 | vms map(ubuntu01/02/03 ↔ IP .201/.202/.203) |
terraform.tfvars |
敏感变量值 | hyperv_password = "920805" |
main.tf |
核心资源 | hyperv_machine_instance.ubuntu,for_each 循环 3 台 |
3.1 main.tf 设计
for_each = local.vm_configs:一次定义 3 台,每台差异集中在 locals(MAC、ISO、盘路径)。- generation = 2:Gen2 UEFI(clou-init Ubuntu cloud image 必须 Gen2;Gen1 BIOS 无法引导)。
- vm_firmware :
enable_secure_boot = "Off"------ 关键。Hyper-V 默认 SecureBoot=On+MicrosoftWindows 模板,Ubuntu 的 shim 校验失败无法引导(曾导致"网络/SCSI DVD/SCSI disk 三错误"启动失败)。必须显式关闭。- boot_order:Network → DVD → HDD(意图;实际由 UEFI 主导,见 2.2)。
- hard_disk_drives →
path = ubuntu0X-fresh.vhdx:直接挂全量盘,不用差异盘。 - dvd_drives →
path = Ubuntu0X-cidata.iso:cloud-init 种子。 - network_adaptors :接
Terraform-Lab-External,静态 MAC(见 2.1 的已知 drift)。 - checkpoint_type = "Production" 、
automatic_start_action = "StartIfRunning"等:Hyper-V 生命周期参数。 - state = "Running":apply 后自动启动 VM。
3.2 provider 工作原理(Bafbi/hyperv 1.5.4)
Provider 通过 SSH 连接 110 的 Hyper-V PowerShell,执行:
CreateVm:New-VM(NoVHD)+ 全参数Set-VM(内存/CPU/检查点/路径)- 删除自动生成的默认网卡/DVD,再逐个
Add-VMNetworkAdapter、Add-VMDvdDrive、Add-VMHardDiskDrive CreateOrUpdateVmFirmwares:设 SecureBoot 等state=Running→Start-VM
注意事项:
- 创建流程不会关闭 Hyper-V 的 AutomaticCheckpoints;若需要避免 avhdx 派生,需在 VM 上手动
Set-VM -AutomaticCheckpointsEnabled $false(本环境已在 3 台的 Hyper-V 侧执行,未进 tf 配置)。CreateOrUpdateVmFirmwares传入空 vm_firmware 会直接 return,SecureBoot 维持 Hyper-V 默认(On)------所以 vm_firmware 块必须显式书写。
4. Cloud-Init 配置详解(110 D:\HyperV\CloudInit\ubuntu0X\)
每台三件套 → genisoimage 打成一个 ISO(卷标 cidata,NoCloud datasource 要求)。
| 文件 | 作用 |
|---|---|
meta-data |
instance-id: ubuntu0X-v2(触发重灌), local-hostname: ubuntu0X |
network-config |
静态 IP/网关/DNS |
user-data |
用户、SSH key、软件包、runcmd |
4.1 user-data 逐段
yaml
hostname: ubuntu0X # 主机名
manage_etc_hosts: true # 自动维护 /etc/hosts
users:
- name: hao # 普通用户
groups: [sudo, adm]
lock_passwd: false
passwd: "$6$..." # sha512 密码哈希
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys: # 5 个 ed25519 公钥
ssh_pwauth: false # 禁密码 SSH(仅密钥)
disable_root: true
package_update: true
packages: [...] # vim/curl/git/python3/pip/...(18 个)
runcmd:
- systemctl enable linux-cloud-tools-common # Hyper-V 集成服务工具
- systemctl start linux-cloud-tools-common
4.2 network-config 逐段
yaml
version: 2
renderer: networkd # netplan → systemd-networkd
ethernets:
default:
match: {name: "eth*"} # 按名称匹配,不依赖 MAC
dhcp4: false # 静态
addresses: [192.168.67.20X/24]
routes: [default via 192.168.67.1]
nameservers: [192.168.67.1, 223.5.5.5]
4.3 ISO 生成与验证
- 工具:WSL 的
genisoimage(-volid cidata -joliet -rock)。 - ISO 卷标必须为
cidata(NoCloud 大小写敏感),否则 cloud-init 不识别。 - 生成后可用
Mount-DiskImage挂载核对三个文件内容与源一致。 - 本次为加入 3 个新 SSH 公钥:改 user-data 并把
meta-data的 instance-id 改为-v2,重新生成 ISO 挂载后重启 → cloud-init 视为"新实例"重灌,authorized_keys 更新为 5 个(已验证)。
5. 镜像文件作用与形成机制(三个目录详解)
5.1 D:\HyperV\Images(基础镜像模板,只读)
| 文件 | 格式/大小 | 作用 | 形成机制 |
|---|---|---|---|
noble-server-cloudimg-amd64.img |
QCOW2 (实测 magic=QFI\xfb),~596MB |
Ubuntu 24.04 官方云镜像,体系内"唯一纯度来源" | 从 Ubuntu 官网 noble-server-cloudimg-amd64.img 直接下载,原生 QCOW2 |
ubuntu-2404-base.vhdx |
VHDX Dynamic, 逻辑 3.5GB / 实际 2.09GB | 通用 vhdx 模板基座 | qemu-img convert -f qcow2 -O vhdx 从 .img 转换而来 |
ubuntu-2404-cloud.vhdx |
VHDX Dynamic, 逻辑 3.5GB / 实际 2.09GB | cloud-ready 母体,供派生 fresh.vhdx | 同上,另一份转换副本(逻辑大小 3.5GB = img 原始展开大小) |
机制要点:Images 是模板层 ,链路起点。
qemu-img(qcow2→vhdx)输出 vhdx 逻辑容量固定为源镜像展开大小(≈3.5GB),不含云初始化。只在 dxchange 出问题时才触碰,保持只读。
5.2 D:\HyperV\VirtualHardDisks(工作盘,VM 实际挂载)
| 文件 | 类型/大小 | 作用 | 形成机制 | 当前状态 |
|---|---|---|---|---|
ubuntu0X-fresh.vhdx |
VHDX Dynamic, 逻辑 50GB / 实际 ~3.5GB | 当前系统盘(全量盘),三台 VM 挂用 | 复制 Images\ubuntu-2404-cloud.vhdx → Resize-VHD 扩容 3.5GB→50GB(文件内部索引更新,仅逻辑容量变大)→ 首启由 cloud-init 灌入系统 |
✅ 挂载使用 |
ubuntu0X.vhdx |
VHDX Dynamic, 逻辑 50GB | 早期方案的系统盘(陈旧"原盘") | 与 fresh 同源制作,但属于旧批次;后因换用 fresh 分支而弃用 | ⚠️ 残留未挂,可清理 |
ubuntu0X_<GUID>.avhdx |
Differencing(差异盘), 逻辑 50GB / 实际 ~75MB | 早期"差异盘方案"遗留 | 父盘=ubuntu0X.vhdx;由 VM 加磁盘时 Hyper-V 自动检查点 / 差分链接派生而来(文件名含父盘名+GUID) |
⚠️ 残留未挂,可清理 |
机制要点:
fresh.vhdx(全量盘) = 模板 + Resize,独立文件自含全部数据,无父盘(Parent=)。*.avhdx(差异盘) = 动态增量:VM 写入落到 avhdx,父盘 vhdx 只读。文件小(75MB),但依赖父盘链,父盘移动/删除即损坏。- 逻辑 50GB vs 实际 3.5GB:动态盘未写过的地方不占物理空间,SSH 已验证系统正常。
5.3 D:\HyperV\VMs\Virtual Machines(VM 运行时状态/配置)
目录包含三台 VM 及其状态文件(GUID 映射:5CE51815=ubuntu01, 15B98AD7=ubuntu02, 55D055A8=ubuntu03):
| 文件 | 名称/大小 | 作用 | 形成机制 |
|---|---|---|---|
<GUID>.vmcx |
73KB | VM 配置文件(XML:内存/CPU/固件/磁盘/网卡定义) | New-VM 时由 terraform provider 生成;改名/调参即更新 |
<GUID>.vmgs |
4MB | Guest State------运行时 VM 的手电筒(寄存器/设备状态) | VM 启动时创建/更新 |
<GUID>.VMRS |
2.1GB | Runtime State------VM 运行内存快照(≈VM 2GB 内存) | 原因链:automatic_stop_action="Save" + VM 当前 Running → Hyper-V 将运行内存持久化于此,用于快速保存/恢复 |
机制要点:
- VMRS = 内存镜像(2.1GB ≈ 2GB RAM),用于保存/恢复 VM 运行时上下文。
avhdx-test/avhdx-test2两个空子目录 = 本次排查期间的测试临时 VM 残留,可删除。- 同一 VM 配置默认也复制到
C:\ProgramData\Microsoft\Windows\Hyper-V(Hyper-V 默认位置),此处是其自定义ConfigurationLocation=D:\HyperV\VMs镜像。
5.4 ISO / CloudInit / 其它
| 路径 | 作用 |
|---|---|
D:\HyperV\ISO\ubuntu0X-cidata.iso |
cloud-init 种子盘(由 CloudInit 源 genisoimage 生成,卷标小写 cidata) |
D:\HyperV\CloudInit\ubuntu0X\ |
cidata 源文件(user-data / network-config / meta-data) |
D:\HyperV\Terraform\ |
预留目录(demo1 实际位于 120 主机) |
一张图看懂生态链:
scss
Ubuntu 官网 QCOW2 ──> Images\noble-server-cloudimg-amd64.img (QCOW2 596MB)
│ qemu-img convert
├──> Images\ubuntu-2404-base.vhdx / -cloud.vhdx (2.09GB, 逻辑 3.5GB)
│ │ Copy + Resize-VHD → 50GB
│ └──> VirtualHardDisks\ubuntu0X-fresh.vhdx (50G 逻辑, 全量盘)
│
├── (旧方案) Copy → ubuntu0X.vhdx ──差分派生──> ubuntu0X_<GUID>.avhdx
│
CloudInit\ubuntu0X\(user-data/network-config/meta-data)
│ genisoimage -volid cidata
└──> ISO\ubuntu0X-cidata.iso ──> VM 的 DVD 位
- VM(VMs\Virtual Machines):HDD=intro-fresh.vhdx(全量)、DVD=cidata.iso(配置种子)、VMRS/VMGS(运行态)。
- 当前运行链路完全干净 :仅
fresh.vhdx作为系统盘,*.vdhx/*.avhdx为历史方案残留。
6. 关键经验 / 踩坑记录
- SecureBoot 默认开启 是 Ubuntu VM 无法引导的最大陷阱 → vm_firmware 必须
enable_secure_boot="Off"。 - hyperv provider 不改已存在网卡的 MAC → 静态 MAC 产生永远 drift;用
eth*匹配则不受影响。 - cloud-init 首次执行靠 instance-id 去重:想重灌 → 更新 instance-id + 重新生成 ISO + 重启。
- cidata 卷标必须小写
cidata。 - Hyper-V 自动检查点会在 diff 盘上派生 avhdx ------ 如需纯全量盘,保持
AutomaticCheckpointsEnabled=$false。 - Gen2 VM 的 netboot/DVD 顺序由 UEFI 引导管理器主导,terraform 的 boot_order 只是创建意图。
7. 附:当前三台 VM 实测状态
| VM | IP | hostname | 用户 | sudo | SSH keys | 软件包 |
|---|---|---|---|---|---|---|
| ubuntu01 | 192.168.67.201 | ubuntu01 | hao | ✅(NOPASSWD) | 5 | vim/git/python3/curl/tree 等 ✅ |
| ubuntu02 | 192.168.67.202 | ubuntu02 | hao | ✅(NOPASSWD) | 5 | 同上 ✅ |
| ubuntu03 | 192.168.67.203 | ubuntu03 | hao | ✅(NOPASSWD) | 5 | 同上 ✅ |
登录方式(公钥已授权,ssh_pwauth=false):
perl
ssh hao@192.168.67.201 # 用 WHTS-PC / LAPTOP-0xxxxLUO / SCYG-PC / SYZX-HY2-TX / whys-hy2-pc 任一私钥