今天我们来聊一聊,如何使用cloud-init在Linux上创建虚拟机,我们会用到kvm和QEMU。
KVM 的全称是 Kernel-based Virtual Machine,即基于内核的虚拟机。它是一种内建于 Linux 内核的开源虚拟化技术。不过 KVM 只是一个内核模块,单独的 KVM 不能看作一个完整的虚拟机软件。好在 Linux 中还有另一个强大的工具 QEMU。QEMU 提供了完整的虚拟化能力,但原始的 QEMU 只提供软件模拟,性能不是很高。而当我们在 QEMU 中集成 KVM 后,就能获得接近裸机的性能,这正是我们想要的。
下面就以 Ubuntu 为例,来看一下如何开启和安装 KVM/QEMU 相关的软件。
-
检查硬件虚拟化支持
在终端中运行如下命令,如果返回值大于 0,表示CPU 支持硬件虚拟化。如果返回 0,则需要在 BIOS 中开启虚拟化功能,不同品牌的主板位置可能有所不同,网上搜索一下即可找到对应指南。
egrep -c '(vmx|svm)' /proc/cpuinfo
-
安装 KVM 相关软件包
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager
下面详细介绍一下这些软件包的作用:
-
qemu-kvm
: 提供 KVM 内核模块和 QEMU 用户空间程序,是虚拟化的核心组件。 -
libvirt-daemon-system
: libvirt 守护进程,用于管理虚拟机。它会自动将libvirtd
服务配置为开机自启。 -
libvirt-clients
: 提供用于与 libvirt 守护进程交互的命令行工具,如virsh
。 -
bridge-utils
: 包含用于创建和管理网络桥接的工具,这对于配置虚拟机网络非常有用。 -
virt-manager
: 一个图形用户界面 (GUI) 工具,用于创建和管理虚拟机(如果系统没有图形界面,可以不用安装这个)。
-
-
将当前用户添加到 libvirt 用户组
这一步主要是为了让我们能够管理虚拟机而无需每次都使用
sudo
。如果不介意使用sudo
命令,可以跳过这一步。sudo adduser $USER libvirt
添加后,重新登录使组成员身份生效。
-
验证运行状态
使用以下命令检查 libvirtd 服务是否正常运行:
sudo systemctl is-active libvirtd
如果输出显示
active
,就表示服务已成功启动并正常运行。
下面重头戏来了,如何批量创建虚拟机!
接下来我们将利用 cloud-init 工具来自动初始化虚拟机。我们选择 Ubuntu(我个人很喜欢这个发行版)来进行演示。
-
ubuntu cloud 镜像下载
wget -P ~/Downloads https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
-
安装 cloud-localds 工具
sudo apt install cloud-image-utils
-
创建默认的初始化数据
# 创建基础目录 mkdir -p ~/kvm/ubuntu-vms cd ~/kvm/ubuntu-vms # 使用的 cloud image cp ~/Downloads/jammy-server-cloudimg-amd64.img base.img authorized_key="ssh-rsa <你的系统的的公钥,使用ssh-keygen -t rsa 命令创建>" if [ -f ~/.ssh/id_rsa.pub ]; then authorized_key=$(cat ~/.ssh/id_rsa.pub) fi # 用户名、公钥等自定义内容(示例) cat > user-data <<EOF #cloud-config users: - name: ubuntu ssh-authorized-keys: - ${authorized_key} sudo: ALL=(ALL) NOPASSWD:ALL groups: sudo shell: /bin/bash lock_passwd: false plain_text_passwd: "ubuntu" ssh_pwauth: true runcmd: - systemctl enable serial-getty@ttyS0.service - systemctl start serial-getty@ttyS0.service - sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="console=ttyS0,115200n8 console=tty0 /' /etc/default/grub - update-grub EOF
-
创建虚拟机
for i in 01 02 03; do name="ubuntu-$i" disk="$name.img" seed="$name-seed.iso" # 创建磁盘(基于 cloud image) qemu-img create -f qcow2 -b base.img -F qcow2 $disk 10G # 创建 cloud-init ISO(指定 hostname) cat > meta-data <<EOF instance-id: $name local-hostname: $name EOF cloud-localds $seed user-data meta-data # 创建虚拟机,参数可以自行调整 virt-install \ --name $name \ --ram 2048 \ --vcpus 2 \ --os-variant ubuntu22.04 \ --disk path=$disk,format=qcow2 \ --disk path=$seed,device=cdrom \ --network network=default,model=virtio \ --graphics none \ --console pty,target_type=serial \ --import \ --noautoconsole done
- 使用 cloud-init 自动配置 SSH、公钥、串口终端等;
- 镜像大小为 10GB(稀疏分配);
- 使用 NAT 网络(default 网络);
- –graphics none 和 --console 保证可以用 virsh console 登录。
上面的脚本创建了三个虚拟机,分别为 ubuntu-01,ubuntu-02,ubuntu-03,使用下面命令看一下虚拟机运行情况
sudo virsh list --all
正常情况应该有类似如下的输出:
Id Name State --------------------------- 4 ubuntu-01 running 5 ubuntu-02 running 6 ubuntu-03 running
我们试着连接一下虚拟机看一下
sudo virsh console ubuntu-01
会有下面的输出:
Connected to domain 'ubuntu-01' Escape character is ^] (Ctrl + ])
再按一下回车,进入登录界面:
Connected to domain 'ubuntu-01' Escape character is ^] (Ctrl + ]) ubuntu-01 login:
输入我们预定义的用户 ubuntu,发现这里需要输入密码,输入我们预定义的密码即可以登录成功
Connected to domain 'ubuntu-01' Escape character is ^] (Ctrl + ]) ubuntu-01 login: ubuntu Password:
也可以在宿主系统中直接使用 ssh 登录,虚拟机的 ip 地址可以在 console 使用密码登录后使用 ip address 命令获取,或者使用如下命令,不登录系统获取 ip 地址
sudo virsh domifaddr ubuntu-01
会得到类似下面的输出
Name MAC address Protocol Address ------------------------------------------------------------------------------- vnet1 52:54:00:ae:aa:e8 ipv4 192.168.122.231/24
在这个例子中我们可以直接使用 ssh ubuntu@192.168.122.231 然后输入密码之后,就可以使用 ssh 登录我们的虚拟机了。
注意:我们上面的演示默认都是使用 qemu 的system模式,所以操作的时候要使用sudo,除了 system 模式,qemu 还提供了一个session模式。session 模式的介绍不在本文中讨论。
其他:
附上几个有用的shell代码,放在~/.bashrc
或者 ~/.zshrc
文件中,方便操作虚拟机:
# 获取虚拟机对应的ip地址,使用方法 vmip <虚拟机名>
vmip() {
local name="$1"
sudo virsh domifaddr "$name" | awk '/ipv4/ {print $4}' | cut -d'/' -f1
}
# 登录虚拟机支持: ssh <虚拟机名> 登录,以及 ssh <用户名>@<虚拟机名> 登录
vmssh() {
local input="$1"
local user host ip
if [[ "$input" == *"@"* ]]; then
user="${input%@*}"
host="${input#*@}"
else
user="ubuntu"
host="$input"
fi
# 如果 host 是 IP 地址(纯数字加点)
if [[ "$host" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
ip="$host"
else
ip=$(sudo virsh domifaddr "$host" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)
if [[ -z "$ip" ]]; then
echo "❌ 无法获取虚拟机 $host 的 IP 地址" >&2
return 1
fi
fi
echo "🔗 正在连接 $user@$ip ..."
ssh "$user@$ip"
}
# 删除虚拟机及其磁盘 使用方法 vmrm <虚拟机名>
vmrm() {
local name="$1"
if [[ -z "$name" ]]; then
echo "⚠️ 请输入要删除的虚拟机名称,例如:vmrm ubuntu-01"
return 1
fi
echo "⚠️ 即将删除虚拟机:$name"
read "confirm?确认删除该虚拟机及其磁盘?[y/N]: "
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "取消删除。"
return 0
fi
# 获取磁盘路径
local disk
disk=$(sudo virsh domblklist "$name" | awk '/^vda/ {print $2}')
echo "🔻 销毁虚拟机..."
sudo virsh destroy "$name" 2>/dev/null
echo "🧹 删除虚拟机定义..."
sudo virsh undefine "$name" --remove-all-storage 2>/dev/null
# 若磁盘未自动删除,尝试手动删除
if [[ -n "$disk" && -f "$disk" ]]; then
echo "🗑️ 删除磁盘文件 $disk"
sudo rm -f "$disk"
fi
echo "✅ 虚拟机 $name 删除完成。"
}
alias vmall="sudo virsh list --all"