Windows 10 + Hyper-V + Terraform + cloud-init:批量产出 3 台 Ubuntu 的实践笔记

这台机器是全息教室全息图像还原处理器,上面有一个面板显示显卡显存不小,于是我觉得平时不用怪可惜的,通过一番折腾发现不仅现存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-configmatch: name: "eth*" 匹配网卡,不依赖 MAC,因此 IP 配置正确、SSH 正常(已实测)。MAC 不属于 cloud-init 自洽依赖项。
  • 建议 :可将 main.tfstatic_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_drivespath = ubuntu0X-fresh.vhdx:直接挂全量盘,不用差异盘。
  • dvd_drivespath = 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,执行:

  1. CreateVmNew-VM(NoVHD)+ 全参数 Set-VM(内存/CPU/检查点/路径)
  2. 删除自动生成的默认网卡/DVD,再逐个 Add-VMNetworkAdapterAdd-VMDvdDriveAdd-VMHardDiskDrive
  3. CreateOrUpdateVmFirmwares:设 SecureBoot 等
  4. state=RunningStart-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.vhdxResize-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. 关键经验 / 踩坑记录

  1. SecureBoot 默认开启 是 Ubuntu VM 无法引导的最大陷阱 → vm_firmware 必须 enable_secure_boot="Off"
  2. hyperv provider 不改已存在网卡的 MAC → 静态 MAC 产生永远 drift;用 eth* 匹配则不受影响。
  3. cloud-init 首次执行靠 instance-id 去重:想重灌 → 更新 instance-id + 重新生成 ISO + 重启。
  4. cidata 卷标必须小写 cidata
  5. Hyper-V 自动检查点会在 diff 盘上派生 avhdx ------ 如需纯全量盘,保持 AutomaticCheckpointsEnabled=$false
  6. 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 任一私钥
相关推荐
学者猫头鹰1 小时前
Spring AI 教程(下篇)
ai编程
Flynt2 小时前
我给 Claude Code 装了 Ponytail,代码量直接砍了一半
agent·ai编程·claude
ServBay2 小时前
谷歌发布 Gemini 3.8 Flash,官方跑分很厉害,但又被嘲了?
aigc·ai编程·gemini
全栈弄潮儿5 小时前
AI 编程进阶实战:我为什么要做这个专栏
chatgpt·openai·ai编程
海鸥815 小时前
AIOps(智能运维)理解
运维·人工智能
码农小韩5 小时前
Linux应用开发(一)——Linux的标准IO
linux·运维·服务器
路弥行至5 小时前
Linux (ARM64 / Jetson) 挂载 exFAT 移动硬盘排障与离线安装指南
linux·运维·服务器·经验分享·笔记·jetson·exfat
漫谈数据智理5 小时前
主动元数据为何是数据编织的核心——从“连接数据”走向“感知、理解与行动”的 Data Fabric
运维·fabric
晴天小庭5 小时前
Skynet AI 论坛邀请你参加一场AI觉醒实验
aigc·openai·ai编程