起因
经常需要"起一台干净 Ubuntu 试个东西"。从 ISO 装 + 改源 + 加 user
+ 配 SSH 要十几分钟。如果用 cloud-init 配合官方 cloud image,30 秒
能起一台已经配好用户 / 密钥 / 时区 / 软件的 VM,全自动。
解决方案
1. 拿官方 cloud image
# Ubuntu 22.04 LTS cloud image
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
# 几百 MB,已预装 cloud-init
cloud-init 是个开机时跑的脚本框架,能读 metadata 自动配置:
- 用户 + SSH key
- hostname / 时区 / locale
- 软件包安装
- 自定义脚本
2. 写 user-data
user-data 是 cloud-init 读的 YAML 配置:
#cloud-config
hostname: devbox
timezone: Asia/Shanghai
locale: zh_CN.UTF-8
users:
- name: alice
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA... alice@laptop
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: true
packages:
- vim
- git
- curl
- htop
- tmux
- python3-pip
- build-essential
write_files:
- path: /etc/motd
content: |
Welcome to devbox - provisioned by cloud-init
runcmd:
- curl -fsSL https://get.docker.com | sh
- usermod -aG docker alice
- systemctl enable --now docker
power_state:
mode: reboot
delay: 'now'
message: 'rebooting after initial setup'
第一行 #cloud-config 必须,告诉 cloud-init 这是它能读的格式。
3. 也写 meta-data
# meta-data
instance-id: devbox-001
local-hostname: devbox
4. 打成 ISO(NoCloud datasource)
cloud-init 启动时找 cidata label 的盘读 user-data + meta-data:
mkdir cloud
mv user-data meta-data cloud/
genisoimage -output cloud-init.iso -volid cidata -joliet -rock cloud/
# 或 cloud-localds(更直接)
cloud-localds cloud-init.iso user-data meta-data
5. 用 qemu / libvirt 起 VM
# 拷贝 image(避免污染原始)
cp jammy-server-cloudimg-amd64.img devbox.qcow2
# 扩容到 30G
qemu-img resize devbox.qcow2 30G
# 启动(无图形,串口输出)
qemu-system-x86_64 \
-enable-kvm \
-m 4G -smp 4 \
-drive file=devbox.qcow2,if=virtio \
-drive file=cloud-init.iso,if=virtio,format=raw \
-nic user,hostfwd=tcp::2222-:22 \
-nographic
30 秒后 cloud-init 跑完 + reboot,外部 SSH:
ssh -p 2222 alice@localhost
# 进去就是配好的环境,docker 可用,时区对
6. 用 virt-install / virsh(更正式)
sudo virt-install \
--name devbox \
--memory 4096 --vcpus 4 \
--disk path=/var/lib/libvirt/images/devbox.qcow2,size=30 \
--disk path=/var/lib/libvirt/images/cloud-init.iso,device=cdrom \
--os-variant ubuntu22.04 \
--network bridge=virbr0 \
--import \
--noautoconsole
virsh list --all
virsh console devbox # 串口
virsh shutdown devbox
virsh start devbox
7. 在 LXD / Multipass / Proxmox 用
LXD(前面章节有提到):
lxc launch ubuntu:22.04 devbox \
--config user.user-data="$(cat user-data)"
Multipass(macOS / Windows 上跑 Ubuntu VM 最简单):
multipass launch jammy --name devbox \
--cloud-init user-data --cpus 4 --memory 4G --disk 30G
Proxmox:直接在 UI 给 VM 挂 cloud-init drive,在 web 表单填用户
名 / 密钥 / IP。
8. 公有云
AWS / GCP / Azure 创建实例时 user-data 字段贴上面的 YAML,
开机自动跑。整套 IaC 用 Terraform:
resource "aws_instance" "web" {
ami = "ami-..." # Ubuntu official AMI
instance_type = "t3.small"
user_data = file("cloud-config.yaml")
}
效果
- 实验室常备一个 user-data 模板 + Makefile,
make vm name=test30 秒
起一台 - 新员工 onboarding 给个 user-data 文件让他在自己机器上起,零摩擦
- 部署模板化:dev / staging / prod 共用 user-data,只改少量变量
- 真实灾备:服务器挂了 cloud-init + 备份恢复,重新 provision 半小时
调试 cloud-init
机器跑起来发现配置没生效:
sudo cloud-init status # done / running / error
sudo cloud-init query userdata # 当前 user-data 内容
sudo cat /var/log/cloud-init.log
sudo cat /var/log/cloud-init-output.log # runcmd 等的 stdout/stderr
/var/log/cloud-init-output.log 是最常看的——所有 runcmd 输出在这里。
强制重新跑(测试用,会改 instance-id):
sudo cloud-init clean --logs --seed --machine-id
sudo reboot
踩过的坑
-
YAML 缩进错:cloud-init 静默跳过出错段,配置部分生效部分不生效。
cloud-init schema --config-file user-data校验语法。 -
第二次启动不再跑 user-data:cloud-init 默认只在首次启动跑。
要重跑改 instance-id:/var/lib/cloud/data/instance-id或者
cloud-init clean。 -
package_upgrade 卡 grub menu prompt:apt 升级遇到内核配置
选择卡住。debconf-show grub-pc或者 user-data 加:
```yaml
bootcmd:- DEBIAN_FRONTEND=noninteractive apt-get update
```
- DEBIAN_FRONTEND=noninteractive apt-get update
-
runcmd 提早 exit:
runcmd:里某条失败不影响后面(默认 set +e)。
要按顺序 fail-fast 用bash -e包:
```yaml
runcmd:- bash -e -c 'curl ... && systemctl restart ...'
```
- bash -e -c 'curl ... && systemctl restart ...'
-
私钥不在 user-data:user-data 是明文。永远只写 public key,
secret 通过其它机制注入(vault / env / fetch from S3)。
登录后参与评论。