cloud-init + Ubuntu cloud image:30 秒起一台开发 VM

起因

经常需要"起一台干净 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=test 30 秒
    起一台
  • 新员工 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

踩过的坑

  1. YAML 缩进错:cloud-init 静默跳过出错段,配置部分生效部分不生效。
    cloud-init schema --config-file user-data 校验语法。

  2. 第二次启动不再跑 user-data:cloud-init 默认只在首次启动跑。
    要重跑改 instance-id:/var/lib/cloud/data/instance-id 或者
    cloud-init clean

  3. package_upgrade 卡 grub menu prompt:apt 升级遇到内核配置
    选择卡住。debconf-show grub-pc 或者 user-data 加:
    ```yaml
    bootcmd:

    • DEBIAN_FRONTEND=noninteractive apt-get update
      ```
  4. runcmd 提早 exitruncmd: 里某条失败不影响后面(默认 set +e)。
    要按顺序 fail-fast 用 bash -e 包:
    ```yaml
    runcmd:

    • bash -e -c 'curl ... && systemctl restart ...'
      ```
  5. 私钥不在 user-data:user-data 是明文。永远只写 public key,
    secret 通过其它机制注入(vault / env / fetch from S3)。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。