前言
你是否有过这种体验:新建一台虚拟机,挂 ISO、等安装界面、选语言、设分区、配用户、改 SSH 配置、敲 ip addr 看 DHCP 分配的地址……整个过程少则五分钟,多则半小时。当你有 5 台、10 台虚拟机要批量部署时,这简直是噩梦。
Cloud-Init 就是来终结这种手工劳动的。它是云厂商(AWS、Azure、GCP)的标准初始化方案,也是你在 Proxmox VE 上实现 “一次配置、秒级克隆” 的关键工具。本文将从原理到实战,带你彻底掌握 PVE 上的 Cloud-Init 自动化部署。
1. Cloud-Init 是什么?
Cloud-Init 是 Canonical 开发的开源工具,负责在虚拟机首次启动时完成初始化工作。它的工作流程非常简单:
1
| VM 首次启动 → 读取 metadata/userdata → 执行配置 → 完成初始化 → 禁用自身
|
支持的操作:
- 🏷️ 设置主机名
- 👤 创建用户、设置密码、注入 SSH 公钥
- 🌐 配置静态 IP / DHCP / DNS
- 📦 安装软件包、执行脚本
- 💾 扩容磁盘、挂载文件系统
- 📝 写入任意文件
在 PVE 中,Cloud-Init 通过 虚拟 CD-ROM (IDE 设备) 向 VM 注入配置文件,标准名为 NoCloud 数据源。
2. 准备 Cloud-Init 镜像
PVE 内置了主流发行版的 Cloud-Init 镜像下载工具,你也可以手动导入。
2.1 使用 PVE 内置下载(推荐)
在 PVE Web UI 中,选择 local 存储 → CT 模板 / ISO 镜像,或者直接用命令:
1
2
3
4
5
6
7
| # 查看可用的 Cloud-Init 镜像
pveam available | grep -i cloud
# 输出示例:
# system ubuntu-22.04-standard_22.04-1_amd64.tar.gz
# system debian-12-standard_12.2-1_amd64.tar.gz
# ...
|
但注意:pveam 下载的是 LXC 模板,不是 Cloud-Init VM 镜像。要获取 Cloud-Init VM 镜像,直接下载官方 .qcow2 或 .img 文件:
1
2
3
4
5
6
7
8
9
10
11
| # Ubuntu 22.04 Cloud-Init 镜像
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img \
-O /var/lib/vz/template/iso/jammy-cloudimg-amd64.img
# Debian 12 Cloud-Init 镜像
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2 \
-O /var/lib/vz/template/iso/debian-12-cloudimg-amd64.qcow2
# Rocky Linux 9
wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 \
-O /var/lib/vz/template/iso/rocky-9-cloudimg-amd64.qcow2
|
2.2 调整镜像大小
官方 Cloud 镜像通常只有 2-5 GB,你需要扩容才能用:
1
2
| # 扩容到 32GB
qemu-img resize /var/lib/vz/template/iso/jammy-cloudimg-amd64.img 32G
|
⚠️ 必须在导入 PVE 之前扩容。导入后再扩容应使用 qm resize,但磁盘格式必须是 qcow2 且无快照。
2.3 导入到 Proxmox VE
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
| # 创建 VM(ID=9000,作为模板)
qm create 9000 \
--name ubuntu-2204-cloudinit-template \
--memory 2048 \
--cores 2 \
--net0 virtio,bridge=vmbr0 \
--ostype l26
# 导入磁盘
qm importdisk 9000 /var/lib/vz/template/iso/jammy-cloudimg-amd64.img local-lvm
# 附加磁盘为 scsi0
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
# 添加 Cloud-Init 驱动器(关键步骤!)
qm set 9000 --ide2 local-lvm:cloudinit
# 设置启动顺序
qm set 9000 --boot order=scsi0
# 启用 QEMU Guest Agent
qm set 9000 --agent enabled=1
# 添加串口控制台(方便 web console 调试)
qm set 9000 --serial0 socket --vga serial0
|
此时你就有了一个 “裸” 模板 VM,接下来配置 Cloud-Init 参数。
3. Cloud-Init 核心配置
3.1 基础配置:用户、密码、SSH
1
2
3
4
5
6
7
8
9
| # 设置用户名和密码
qm set 9000 --ciuser ubuntu
qm set 9000 --cipassword "YourSecurePassword123"
# 注入 SSH 公钥(推荐,避免密码登录)
qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub
# 设置主机名
qm set 9000 --cihostname cloud-template
|
💡 最佳实践:模板中只注入 SSH 公钥,不要设置密码。克隆后再按需修改。
3.2 网络配置:DHCP vs 静态 IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # DHCP(默认)
qm set 9000 --ipconfig0 ip=dhcp
# 静态 IP
qm set 9000 --ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1
# IPv6 SLAAC
qm set 9000 --ipconfig0 ip=dhcp,ip6=auto
# 多网卡
qm set 9000 \
--net0 virtio,bridge=vmbr0 \
--net1 virtio,bridge=vmbr1 \
--ipconfig0 ip=192.168.1.100/24,gw=192.168.1.1 \
--ipconfig1 ip=10.0.0.100/24
|
3.3 DNS 配置
1
2
| qm set 9000 --nameserver "8.8.8.8 1.1.1.1"
qm set 9000 --searchdomain "home.lab"
|
3.4 转换为模板
配置完成后,转换为模板以保护基础镜像:
现在 9000 变成了只读模板,无法直接启动。
4. 从模板克隆部署
4.1 完整克隆
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 从模板 9000 克隆为 VM 101
qm clone 9000 101 \
--name web-server-01 \
--full true \
--storage local-lvm
# 修改 Cloud-Init 参数(每台 VM 独立配置)
qm set 101 --cihostname web-01
qm set 101 --ipconfig0 ip=192.168.1.101/24,gw=192.168.1.1
qm set 101 --sshkeys ~/.ssh/id_ed25519.pub
qm set 101 --ciuser admin
# 启动
qm start 101
|
4.2 链接克隆(节省空间)
1
2
3
4
5
6
7
8
| qm clone 9000 102 \
--name web-server-02 \
--full false \
--storage local-lvm
qm set 102 --cihostname web-02
qm set 102 --ipconfig0 ip=192.168.1.102/24,gw=192.168.1.1
qm start 102
|
❗ 链接克隆依赖模板磁盘,删除模板前必须先 qm template 9000 --disk scsi0,size=32G 将所有链接克隆转为完整磁盘。
4.3 批量部署脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #!/bin/bash
TEMPLATE_ID=9000
NAMES=("web-01" "web-02" "db-01" "cache-01")
IPS=("192.168.1.101" "192.168.1.102" "192.168.1.103" "192.168.1.104")
for i in "${!NAMES[@]}"; do
VMID=$((200 + i))
echo "Creating ${NAMES[$i]} (VMID=$VMID, IP=${IPS[$i]})..."
qm clone $TEMPLATE_ID $VMID \
--name "${NAMES[$i]}" \
--full true \
--storage local-lvm
qm set $VMID \
--cihostname "${NAMES[$i]}" \
--ipconfig0 "ip=${IPS[$i]}/24,gw=192.168.1.1" \
--sshkeys ~/.ssh/id_ed25519.pub
qm start $VMID
echo "✅ ${NAMES[$i]} started"
done
|
5. 高级定制:vendor.yaml 与 user.yaml
PVE 的 Web UI 只能配置基础参数。要实现安装软件包、写入文件、执行脚本,你需要自定义 cicustom。
5.1 创建自定义 Cloud-Init 文件
在 PVE 存储中创建 Snippets:
1
2
3
4
5
| # 确保 snippets 存储存在
pvesm status | grep snippets
# 如果不存在,在 /etc/pve/storage.cfg 中定义
mkdir -p /var/lib/vz/snippets
|
创建 user.yaml:
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
46
47
48
49
| # /var/lib/vz/snippets/ubuntu-user.yaml
#cloud-config
hostname: {{ hostname }}
manage_etc_hosts: true
users:
- name: admin
sudo: ALL=(ALL) NOPASSWD:ALL
groups: sudo, docker
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
package_update: true
package_upgrade: true
packages:
- qemu-guest-agent
- htop
- vim
- curl
- wget
- git
- net-tools
- ca-certificates
- gnupg
write_files:
- path: /etc/sysctl.d/99-custom.conf
content: |
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
vm.swappiness=1
permissions: '0644'
- path: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
PermitRootLogin prohibit-password
PasswordAuthentication no
X11Forwarding no
permissions: '0644'
runcmd:
- sysctl --system
- systemctl restart sshd
- timedatectl set-timezone Asia/Shanghai
- [mkdir, -p, /opt/apps]
final_message: "Cloud-Init 初始化完成!系统于 $UPTIME 秒后启动。"
|
5.2 关联自定义配置
1
| qm set 101 --cicustom "user=local:snippets/ubuntu-user.yaml"
|
你还可以分离 vendor.yaml(厂商配置)和 network-config(网络配置):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 自定义网络配置
cat > /var/lib/vz/snippets/static-net.yaml << 'EOF'
version: 2
ethernets:
ens18:
dhcp4: false
addresses:
- 192.168.1.101/24
gateway4: 192.168.1.1
nameservers:
addresses: [223.5.5.5, 8.8.8.8]
EOF
qm set 101 --cicustom "user=local:snippets/ubuntu-user.yaml,network=local:snippets/static-net.yaml"
|
📌 cicustom 的格式是 key=storage:path,其中 key 可以是 user、vendor、network、meta。
6. 实时查看 Cloud-Init 日志
VM 启动后,通过 PVE Console 或 SSH 查看初始化进度:
1
2
3
4
5
6
7
8
9
10
11
| # 查看 Cloud-Init 状态(在 VM 内执行)
cloud-init status
# 输出: status: done
# 详细日志
tail -f /var/log/cloud-init-output.log
tail -f /var/log/cloud-init.log
# 分析初始化耗时
cloud-init analyze show
cloud-init analyze blame
|
在 PVE 宿主机上也可以通过 qm terminal 连接:
7. 常见发行版的 Cloud-Init 差异
| 发行版 | 默认用户名 | 镜像后缀 | 注意事项 |
|---|
| Ubuntu | ubuntu | -cloudimg-amd64.img | Agent 默认安装;密码登录默认禁用 |
| Debian | debian (12+) / root (11) | -genericcloud-amd64.qcow2 | 需手动安装 qemu-guest-agent |
| Rocky Linux | rocky | -GenericCloud-*.qcow2 | SELinux 默认 enforcing |
| AlmaLinux | almalinux | -GenericCloud-*.qcow2 | 与 Rocky 类似 |
| Fedora | fedora | -Cloud-Base-*.qcow2 | 启动后默认扩容根分区 |
| CentOS Stream | centos | -GenericCloud-*.qcow2 | 注意 EOL 日期 |
| Arch Linux | arch | 社区提供 | 滚动更新,无固定版本 |
| Alpine Linux | alpine | -cloud-*.qcow2 | 使用 busybox,无 systemd |
💡 不同发行版对 package_update 和 package_upgrade 的行为不同。Ubuntu/Debian 正常,Alpine 的 apk 需要额外处理。
8. 踩坑记录
❌ 坑 1:Cloud-Init 驱动器未添加
现象:VM 启动后卡在 Probing EDD (edd=off to disable)... 或直接进入 cloud-init 超时等待。
根因:忘记添加 IDE Cloud-Init 驱动器。
1
2
| # 修复
qm set <VMID> --ide2 local-lvm:cloudinit
|
❌ 坑 2:修改配置后不生效
现象:修改了 --ciuser 或 --ipconfig0,重启 VM 后没变化。
根因:Cloud-Init 只在首次启动运行!这是最容易被忽视的机制。
解决方案:重置 Cloud-Init 状态并重新生成 ISO:
1
2
3
4
5
6
| # 方法 1:在 VM 内重置
cloud-init clean --logs --reboot
# 方法 2:在 PVE 上重新生成配置驱动器
qm set <VMID> --ide2 none # 移除旧的
qm set <VMID> --ide2 local-lvm:cloudinit # 重建
|
❌ 坑 3:qemu-guest-agent 未安装导致 IP 不可见
现象:PVE Web UI 的 Summary 中不显示 VM IP 地址。
解决:在 user.yaml 的 packages 中添加 qemu-guest-agent,并在 PVE 中启用:
1
| qm set <VMID> --agent enabled=1
|
❌ 坑 4:DHCP 获取不到 IP
现象:Debian Cloud 镜像启动后 ip a 看不到 IP。
根因:Debian Cloud 镜像默认不启用网卡 DHCP。
解决:确保 ipconfig0 设置正确,或在自定义 network-config 中显式配置:
1
2
3
4
| version: 2
ethernets:
ens18:
dhcp4: true
|
❌ 坑 5:模板磁盘格式不兼容
现象:qm importdisk 后磁盘格式是 raw,无法做快照。
解决:
1
2
3
4
5
| # 先转换格式再导入
qemu-img convert -f qcow2 -O qcow2 source.img dest.qcow2
# 或者在 PVE 中移动磁盘时指定格式
qm move-disk <VMID> scsi0 local-lvm --format qcow2
|
9. 总结
Cloud-Init 是 Proxmox VE 自动化部署的核心工具,掌握它可以让你:
| 维度 | 结论 |
|---|
| ✅ 推荐工作流 | 下载 Cloud 镜像 → resize → 导入 PVE → 配置 SSH 公钥 + user.yaml → 转为模板 → 克隆部署 |
| ✅ 最佳实践 | 模板只设 SSH 公钥,不设密码;克隆后按需修改 hostname/IP |
| ✅ 核心命令 | qm create → qm importdisk → qm set --ide2 cloudinit → qm template → qm clone |
| ⚠️ 最易出错 | 忘记 --ide2 cloudinit、修改配置后不重置、qemu-guest-agent 未安装 |
| 🔗 关联阅读 | PVE LXC 容器完全指南 · PVE ZFS 存储配置 · WireGuard VPN 部署 |
一键从模板克隆 + SSH 密钥注入 + IP 自动配置 = 30 秒出一台可用 VM。配得好,一天部署 50 台不是梦。
本文基于 Proxmox VE 8.2 + Ubuntu 22.04 / Debian 12 Cloud 镜像验证。Cloud-Init 版本:24.1+