概述
作为一名资深运维工程师,我们经常需要在 Proxmox 虚拟化平台上创建和管理虚拟机。本文将介绍三种不同的方式在 Proxmox 上创建 Ubuntu 虚拟机:
- 通过 Proxmox 命令创建虚拟机
- 通过 Shell 脚本自动化创建虚拟机
- 使用 Proxmox API 创建虚拟机
每种方式都有其适用场景,选择合适的方式可以大大提高工作效率。
proxmox通过更多的方式创建虚拟机
通过 Proxmox 命令创建虚拟机
下载 Ubuntu Cloud 镜像
还是以ubuntu22.04版本为例
mkdir /var/lib/vz/template/qemu/
cd /var/lib/vz/template/qemu/
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
创建虚拟机模板
创建一个新的虚拟机
使用<font style="color:rgb(64, 64, 64);">qm</font>
命令创建一个新的虚拟机。假设我们创建一个ID为9000的虚拟机。
# 创建虚拟机
qm create 9000 --name "ubuntu-template" --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
导入下载的镜像
将下载的Ubuntu Cloud镜像导入到虚拟机中。
# 导入镜像
qm importdisk 9000 jammy-server-cloudimg-amd64.img local
将磁盘附加到虚拟机
将下载的Ubuntu Cloud镜像导入到虚拟机中。
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local:9000/vm-9000-disk-0.raw
配置Cloud-Init
配置Cloud-Init以支持自动化的虚拟机配置。
qm set 9000 --ide2 local:cloudinit
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --serial0 socket --vga serial0
配置网络
设置网络接口以使用DHCP:
qm set 9000 --ipconfig0 ip=dhcp
将虚拟机转换为模板
将虚拟机转换为模板,以便后续克隆。
qm template 9000
通过模板创建虚拟机
克隆模板
使用模板克隆一个新的虚拟机。假设我们创建一个ID为100的虚拟机。
qm clone 9000 100 --name "ubuntu-100"
配置Cloud-Init
为新虚拟机配置Cloud-Init参数,如用户、密码、SSH密钥等。这里就设置用户密码为:ZhangPeng1234,命令如下:
qm set 100 --ciuser ubuntu --cipassword ZhangPeng1234
注:这里可以考虑一下ssh key的方式,请自行发散!
启动虚拟机
启动新创建的虚拟机:
qm start 100
验证虚拟机
登录proxmox web控制台,双击VM ID 100的虚拟机实例。进入控制台:
输入用户名密码进入shell:
输入ip a 获取VM 100实例的ip地址:
注:ip a仍然是192.168.0.5,因为之前的VM实例我删除了这是正常的。
紧接着可以通过shell自行登录验证虚拟机实例!这里有个我很喜欢的,创建的VM 100实例的hostname 总算是我克隆模版自定义的name ubuntu-100.比较服务我个人的审美预期!
通过 Shell 脚本自动化创建虚拟机
尝试使用deepseek 将上面的步骤封装成了一个shell脚本,实现从下载镜像到创建虚拟机模板和虚拟机的流程:
vi create_vm.sh
#!/bin/bash
# 参数配置
TEMPLATE_ID=9001
VM_ID=101
VM_NAME="ubuntu-101"
USERNAME="ubuntu"
PASSWORD="securepassword"
IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
IMAGE_NAME="jammy-server-cloudimg-amd64.img"
STORAGE="local"
BRIDGE="vmbr0"
# 下载 Ubuntu Cloud 镜像
echo "下载 Ubuntu Cloud 镜像..."
wget -q $IMAGE_URL -O $IMAGE_NAME
# 创建虚拟机模板
echo "创建虚拟机模板..."
qm create $TEMPLATE_ID --name "ubuntu-template" --memory 2048 --cores 2 --net0 virtio,bridge=$BRIDGE
# 导入镜像
echo "导入镜像..."
qm importdisk $TEMPLATE_ID $IMAGE_NAME $STORAGE
# 将磁盘附加到虚拟机
echo "配置磁盘..."
qm set $TEMPLATE_ID --scsihw virtio-scsi-pci --scsi0 $STORAGE:$TEMPLATE_ID/vm-$TEMPLATE_ID-disk-0.raw
# 配置 Cloud-Init
echo "配置 Cloud-Init..."
qm set $TEMPLATE_ID --ide2 $STORAGE:cloudinit
qm set $TEMPLATE_ID --boot c --bootdisk scsi0
qm set $TEMPLATE_ID --serial0 socket --vga serial0
qm set $TEMPLATE_ID --ciuser $USERNAME --cipassword $PASSWORD
# 配置 网络
echo "配置 网络..."
qm set $TEMPLATE_ID --ipconfig0 ip=dhcp
# 将虚拟机转换为模板
echo "转换为模板..."
qm template $TEMPLATE_ID
# 克隆模板创建新虚拟机
echo "克隆模板创建新虚拟机..."
qm clone $TEMPLATE_ID $VM_ID --name $VM_NAME
# 启动虚拟机
echo "启动虚拟机..."
qm start $VM_ID
echo "虚拟机创建完成!"
增加可执行权限并执行脚本:
chmod +x create_vm.sh
./create_vm.sh
换一个方式 使用qm命令进入VM 101 id的虚拟机:
qm terminal 101
继续完善一下脚本,提示词如下:
- 每次都更新下载镜像,我需要对本地img镜像与现实比对,如果一致则略过镜像下载部分
- 当模版存在的时候,提醒我是否更新模版,如果更新模版则进行覆盖,如果否则略过进行下一步。
- 虚拟机如果存在。如果更新,则删除旧的虚拟机,创建新的虚拟机进行更新,如果否,则略过。
最终脚本如下:
#!/bin/bash
# 参数配置
TEMPLATE_ID=9001
VM_ID=101
VM_NAME="ubuntu-101"
USERNAME="ubuntu"
PASSWORD="securepassword"
IMAGE_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
IMAGE_NAME="jammy-server-cloudimg-amd64.img"
STORAGE="local"
BRIDGE="vmbr0"
# 函数:检查镜像是否存在
check_image() {
if [[ -f $IMAGE_NAME ]]; then
echo "本地镜像已存在..."
# 获取远程文件大小(以字节为单位)
REMOTE_SIZE=$(curl -sI $IMAGE_URL | grep -i content-length | awk '{print $2}' | tr -d '\r')
# 获取本地文件大小(以字节为单位)
LOCAL_SIZE=$(stat -c%s "$IMAGE_NAME")
if [[ -n "$REMOTE_SIZE" && "$LOCAL_SIZE" == "$REMOTE_SIZE" ]]; then
echo "镜像大小一致($LOCAL_SIZE 字节),使用现有镜像。"
return 0
else
echo "镜像大小不一致或无法获取远程大小"
echo "本地大小: $LOCAL_SIZE"
echo "远程大小: $REMOTE_SIZE"
read -p "是否重新下载镜像?(y/n): " REDOWNLOAD
if [[ $REDOWNLOAD == "y" ]]; then
echo "重新下载镜像..."
rm -f $IMAGE_NAME
return 1
else
echo "使用现有镜像继续..."
return 0
fi
fi
else
echo "本地镜像不存在,开始下载..."
return 1
fi
}
# 函数:检查模板是否存在并处理
check_template_existence() {
if qm list | grep -q "$TEMPLATE_ID"; then
echo "模板 ID $TEMPLATE_ID 已存在。"
read -p "是否更新模板?(y/n): " UPDATE_TEMPLATE
if [[ $UPDATE_TEMPLATE == "y" ]]; then
echo "停止现有模板..."
qm stop $TEMPLATE_ID &>/dev/null
sleep 2
echo "删除旧模板..."
qm destroy $TEMPLATE_ID
return 0
else
echo "保留现有模板。"
return 1
fi
fi
return 0
}
# 函数:检查虚拟机是否存在并处理
check_vm_existence() {
if qm list | grep -q "$VM_ID"; then
echo "虚拟机 ID $VM_ID 已存在。"
read -p "是否更新该虚拟机?(y/n): " UPDATE_VM
if [[ $UPDATE_VM == "y" ]]; then
echo "停止现有虚拟机..."
qm stop $VM_ID &>/dev/null
sleep 5
echo "删除现有虚拟机..."
qm destroy $VM_ID
return 0
else
echo "保留现有虚拟机。"
return 1
fi
fi
return 0
}
# 函数:创建模板
create_template() {
echo "创建虚拟机模板..."
qm create $TEMPLATE_ID --name "ubuntu-template" --memory 2048 --cores 2 --net0 virtio,bridge=$BRIDGE
echo "导入镜像..."
qm importdisk $TEMPLATE_ID $IMAGE_NAME $STORAGE
echo "等待磁盘导入完成..."
sleep 5
# 获取最新导入的磁盘文件
DISK_NAME=$(ls -t /var/lib/vz/images/$TEMPLATE_ID/vm-$TEMPLATE_ID-disk-*.raw | head -n1)
if [[ -z "$DISK_NAME" ]]; then
echo "错误:找不到导入的磁盘文件"
exit 1
fi
DISK_BASE=$(basename "$DISK_NAME")
echo "使用磁盘: $DISK_BASE"
echo "配置磁盘..."
qm set $TEMPLATE_ID --scsihw virtio-scsi-pci --scsi0 "$STORAGE:$TEMPLATE_ID/$DISK_BASE"
echo "配置 Cloud-Init..."
qm set $TEMPLATE_ID --ide2 $STORAGE:cloudinit
qm set $TEMPLATE_ID --boot c --bootdisk scsi0
qm set $TEMPLATE_ID --serial0 socket --vga serial0
qm set $TEMPLATE_ID --ciuser $USERNAME --cipassword $PASSWORD
echo "配置网络..."
qm set $TEMPLATE_ID --ipconfig0 ip=dhcp
# 检查是否已经是模板
if ! qm config $TEMPLATE_ID | grep -q "template: 1"; then
echo "转换为模板..."
qm template $TEMPLATE_ID
fi
}
# 函数:克隆和启动虚拟机
create_vm() {
echo "克隆模板创建新虚拟机..."
qm clone $TEMPLATE_ID $VM_ID --name $VM_NAME --full
echo "等待克隆完成..."
sleep 5
if qm status $VM_ID &>/dev/null; then
echo "启动虚拟机..."
qm start $VM_ID
else
echo "错误:虚拟机创建失败"
exit 1
fi
}
# 主程序开始
echo "开始执行虚拟机创建流程..."
# 下载镜像(如果需要)
if ! check_image; then
echo "下载 Ubuntu Cloud 镜像..."
wget $IMAGE_URL -O $IMAGE_NAME || {
echo "错误:下载镜像失败"
exit 1
}
fi
# 检查并创建/更新模板
if check_template_existence; then
create_template
fi
# 检查并创建/更新虚拟机
if check_vm_existence; then
create_vm
fi
# 显示最终状态
echo "操作完成!当前状态:"
qm list | grep -E "$TEMPLATE_ID|$VM_ID"
完美!
使用 Proxmox API 创建虚拟机
为了进一步自动化,我们可以使用 Proxmox API 来创建虚拟机。以下是一个使用 Python 脚本通过 Proxmox API 创建虚拟机的示例。其实这里我就使用claude3 将上面的脚步转换了一下:修改成使用proxmox api 实现上述功能的python脚本!
在proxmox主机上面完成如下操作,其他机器也可以,以使用apt包管理的系统为例:
apt install python3-pip
pip install proxmoxer
apt install python3-proxmoxer
使用proxmox api,脚本如下:
cat create_vm.py
#!/usr/bin/env python3
import requests
import time
import os
import sys
from proxmoxer import ProxmoxAPI
from urllib3.exceptions import InsecureRequestWarning
# 禁用不安全请求警告
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
# 配置信息
PROXMOX_HOST = "192.168.0.200"
PROXMOX_USER = "root@pam"
PROXMOX_PASSWORD = "Aa123456."
NODE_NAME = "proxmox1"
# 获取脚本所在目录的绝对路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# VM 配置
TEMPLATE_ID = 9001
VM_ID = 101
VM_NAME = "ubuntu-101"
USERNAME = "ubuntu"
PASSWORD = "securepassword"
IMAGE_URL = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
IMAGE_NAME = "jammy-server-cloudimg-amd64.img"
IMAGE_PATH = os.path.join(SCRIPT_DIR, IMAGE_NAME)
STORAGE = "local"
BRIDGE = "vmbr0"
# 超时设置(秒)
TEMPLATE_CREATE_TIMEOUT = 300 # 5分钟
VM_START_TIMEOUT = 180 # 3分钟
class ProxmoxManager:
def __init__(self):
self.proxmox = ProxmoxAPI(
PROXMOX_HOST,
user=PROXMOX_USER,
password=PROXMOX_PASSWORD,
verify_ssl=False
)
self.node = self.proxmox.nodes(NODE_NAME)
def wait_for_task_completion(self, task_upid, timeout=300):
"""等待任务完成"""
start_time = time.time()
while time.time() - start_time < timeout:
try:
task_status = self.node.tasks(task_upid).status.get()
if task_status['status'] == 'stopped':
if task_status['exitstatus'] == 'OK':
return True
else:
print(f"任务失败: {task_status.get('exitstatus', '未知错误')}")
return False
except Exception as e:
print(f"检查任务状态时出错: {str(e)}")
return False
time.sleep(2)
print("任务超时")
return False
def wait_for_vm_state(self, vmid, desired_state='running', timeout=180):
"""等待VM达到期望状态"""
print(f"等待VM {vmid} 变为 {desired_state} 状态...")
start_time = time.time()
while time.time() - start_time < timeout:
try:
status = self.node.qemu(vmid).status.current.get()
current_state = status['status']
if current_state == desired_state:
print(f"VM {vmid} 已经是 {desired_state} 状态")
return True
print(f"当前状态: {current_state}, 等待中...")
except Exception as e:
print(f"检查VM状态时出错: {str(e)}")
return False
time.sleep(5)
print(f"等待VM状态超时,当前状态: {current_state}")
return False
def check_image(self):
"""检查镜像是否存在并下载"""
if os.path.exists(IMAGE_PATH):
print("本地镜像已存在...")
response = requests.head(IMAGE_URL)
remote_size = int(response.headers.get('content-length', 0))
local_size = os.path.getsize(IMAGE_PATH)
if remote_size == local_size:
print(f"镜像大小一致({local_size} 字节),使用现有镜像。")
return True
else:
print(f"镜像大小不一致(本地:{local_size},远程:{remote_size})")
choice = input("是否重新下载镜像?(y/n): ")
if choice.lower() == 'y':
self.download_image()
return True
else:
print("本地镜像不存在,开始下载...")
self.download_image()
return True
return False
def download_image(self):
"""下载镜像"""
print("下载镜像中...")
response = requests.get(IMAGE_URL, stream=True)
total_size = int(response.headers.get('content-length', 0))
block_size = 1024 * 1024 # 1MB
with open(IMAGE_PATH, 'wb') as f:
downloaded = 0
for data in response.iter_content(block_size):
f.write(data)
downloaded += len(data)
percentage = int((downloaded / total_size) * 100)
sys.stdout.write(f"\r下载进度: {percentage}%")
sys.stdout.flush()
print("\n下载完成!")
def check_vm_exists(self, vmid):
"""检查VM是否存在"""
try:
self.node.qemu(vmid).status.current.get()
return True
except:
return False
def stop_and_destroy_vm(self, vmid):
"""停止并删除VM"""
try:
if self.check_vm_exists(vmid):
print(f"停止 VM {vmid}...")
try:
stop_task = self.node.qemu(vmid).status.stop.post()
if not self.wait_for_vm_state(vmid, 'stopped', timeout=60):
print(f"停止VM {vmid} 超时")
return False
except:
pass
print(f"删除 VM {vmid}...")
self.node.qemu(vmid).delete()
time.sleep(2)
return True
except Exception as e:
print(f"删除VM时出错: {str(e)}")
return False
return True
def create_template(self):
"""创建模板"""
print("创建模板...")
# 检查并删除已存在的模板
if self.check_vm_exists(TEMPLATE_ID):
choice = input(f"模板 {TEMPLATE_ID} 已存在,是否重新创建?(y/n): ")
if choice.lower() == 'y':
if not self.stop_and_destroy_vm(TEMPLATE_ID):
print("删除现有模板失败")
return False
else:
return True
try:
# 创建新VM作为模板
create_task = self.node.qemu.create(
vmid=TEMPLATE_ID,
name="ubuntu-template",
memory=2048,
cores=2,
net0=f"virtio,bridge={BRIDGE}",
scsihw="virtio-scsi-pci",
scsi0=f"{STORAGE}:0,import-from={IMAGE_PATH},format=raw",
ide2=f"{STORAGE}:cloudinit",
boot="c",
bootdisk="scsi0",
serial0="socket",
vga="serial0",
ciuser=USERNAME,
cipassword=PASSWORD,
ipconfig0="ip=dhcp"
)
print("等待模板创建完成...")
if not self.wait_for_task_completion(create_task, TEMPLATE_CREATE_TIMEOUT):
print("创建模板失败")
return False
# 转换为模板
print("转换为模板...")
self.node.qemu(TEMPLATE_ID).config.post(template=1)
print("模板创建成功")
return True
except Exception as e:
print(f"创建模板时出错: {str(e)}")
return False
def create_vm(self):
"""从模板创建VM"""
print(f"创建虚拟机 {VM_ID}...")
# 检查并删除已存在的VM
if self.check_vm_exists(VM_ID):
choice = input(f"VM {VM_ID} 已存在,是否重新创建?(y/n): ")
if choice.lower() == 'y':
if not self.stop_and_destroy_vm(VM_ID):
print("删除现有VM失败")
return False
else:
return True
try:
# 克隆模板
print("克隆模板...")
clone_task = self.node.qemu(TEMPLATE_ID).clone.post(
newid=VM_ID,
name=VM_NAME,
full=1
)
if not self.wait_for_task_completion(clone_task, TEMPLATE_CREATE_TIMEOUT):
print("克隆模板失败")
return False
# 启动VM
print("启动虚拟机...")
self.node.qemu(VM_ID).status.start.post()
# 等待VM运行
if not self.wait_for_vm_state(VM_ID, 'running', VM_START_TIMEOUT):
print("启动VM失败")
return False
print(f"VM {VM_ID} 创建并启动成功")
return True
except Exception as e:
print(f"创建VM时出错: {str(e)}")
return False
def show_status(self):
"""显示VM状态"""
print("\n当前VM状态:")
for vmid in [TEMPLATE_ID, VM_ID]:
try:
status = self.node.qemu(vmid).status.current.get()
print(f"VM {vmid}: {status['status']}")
except:
print(f"VM {vmid}: 不存在")
def main():
try:
proxmox = ProxmoxManager()
# 检查并下载镜像
if not proxmox.check_image():
print("镜像检查失败")
return 1
# 创建模板
if not proxmox.create_template():
print("创建模板失败")
return 1
# 创建VM
if not proxmox.create_vm():
print("创建VM失败")
return 1
# 显示状态
proxmox.show_status()
return 0
except Exception as e:
print(f"发生错误: {str(e)}")
return 1
if __name__ == "__main__":
sys.exit(main())
执行脚本输出如下:
本脚本已经验证,可以正常使用!
总结
通过deepseek claude3等chat方式,我们实现了proxmox通过更多的方式创建VM的完整流程。
技术方案对比
- 命令行方式
- 优点:操作直观,适合单次部署
- 缺点:重复性操作效率低,易出错
- 适用场景:临时测试、学习环境
- Shell脚本方式
- 优点:自动化程度高,可重复执行
- 缺点:依赖shell环境,跨平台性较差
- 适用场景:批量部署,单一环境下的自动化运维
- API方式
- 优点:灵活性强,可跨平台,易于集成
- 缺点:开发难度相对较大,需要额外的依赖
- 适用场景:需要与其他系统集成,或需要二次开发的场景
实践经验总结
1. 自动化考虑要点
- 幂等性处理:脚本需要考虑重复执行的情况
- 错误处理:完善的错误捕获和日志记录
- 参数化配置:关键参数要可配置化,提高脚本复用性
- 状态检查:执行关键操作前后的状态验证
2. 性能优化建议
- 并行处理:批量创建时考虑并行执行
- 资源控制:合理设置等待时间和超时机制
- 镜像缓存:本地保存常用镜像,避免重复下载
3. 安全性考虑
- 密码管理:避免明文密码,建议使用密钥认证
- 权限控制:最小权限原则
- 日志审计:关键操作需要记录日志
未来优化方向
- 基础设施即代码(IaC)
- 集成 Terraform 支持
- 添加 Ansible 自动化配置
- 支持云原生部署方案
- 功能扩展
- 支持更多操作系统类型
- 添加批量部署功能
- 集成监控和告警系统
- 可用性提升
- 添加 Web 界面
- 支持集群部署
- 提供 RESTful API 服务
最佳实践建议
- 标准化流程
- 建立统一的命名规范
- 制定标准化的配置模板
- 规范化的部署流程
- 运维效率
- 善用模板功能
- 建立基础镜像库
- 自动化脚本版本控制
- 问题排查
- 完善的日志记录
- 监控指标收集
- 故障恢复预案
本文详细介绍了在 Proxmox 环境下创建虚拟机的多种方法,从基础的命令行操作到高级的 API 集成,为不同场景提供了完整的解决方案。通过实践这些方法,我们不仅提高了运维效率,也建立了一套完整的自动化运维体系。在实际生产环境中,建议根据具体需求选择合适的方案,并持续优化和改进。未来我们将继续探索更多自动化运维的最佳实践,为团队提供更高效的技术解决方案。
参考资料
- Proxmox VE 官方文档:https://pve.proxmox.com/wiki/Main_Page
- Cloud-Init 文档:https://cloudinit.readthedocs.io/
- Proxmoxer Python API:https://pypi.org/project/proxmoxer/
通过本文的实践和总结,我们不仅掌握了多种创建虚拟机的方法,更重要的是建立了一套完整的自动化运维思路。希望这些内容能够帮助更多的运维工程师提升工作效率和专业能力。