vmware的python自动化:批量迁移虚拟机

目录

自动化需求

代码

使用方法


自动化需求

完成大批量虚拟机的迁移,支持仅迁移存储或者计算资源,存储迁移保留存储原格式

版本:

python3.11

pyvmomi 9.0.0.0

pyvim 3.0.3

不适用:

跨CPU迁移

迁移需要更换网段

从VSAN迁移到共享或本地存储

从共享或本地存储迁移到VSAN

适用:

同CPU架构集群内迁移计算资源

同CPU架构集群内迁移存储资源(共享到本地,共享到共享,本地到本地,本地到共享)

代码

复制代码
import ssl
import os
import time
import concurrent.futures
from collections import defaultdict
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
from pyVim.task import WaitForTask
from openpyxl import load_workbook

# ================= 配置区域 =================
VC_CONFIGS = {
    '192.168.1.250': {
        'user': 'administrator@vsphere.local',
        'pwd': 'xiaozhou@666.com'
    },
    # 示例:添加第二个vCenter
    # '192.168.1.100': {
    #     'user': 'administrator@vsphere.local',
    #     'pwd': 'YourPasswordHere'
    # }
}

# 输入文件名称
INPUT_FILE = 'vmotion.xlsx'
# 并发迁移数量
MAX_MIGRATE_WORKERS = 5


# =======================================================================


def get_obj(content, vimtype, name=None):
    """获取对象函数"""
    container = content.viewManager.CreateContainerView(content.rootFolder, vimtype, True)
    return next((item for item in container.view if item.name == name), None) if name else container.view


def read_migrate_config_from_xlsx():
    """
    从Excel读取迁移配置
    Excel格式:VC, 虚拟机名称, 迁移后主机, 迁移后存储
    """
    configs = []
    xlsx_file_path = os.path.join(os.getcwd(), INPUT_FILE)

    if not os.path.exists(xlsx_file_path):
        print(f"错误: Excel文件不存在 '{xlsx_file_path}'")
        return configs

    try:
        wb = load_workbook(xlsx_file_path, read_only=True, data_only=True)
        ws = wb.active
        rows = list(ws.iter_rows(values_only=True))
        wb.close()

        if not rows:
            print("错误: Excel文件为空")
            return configs

        header = [str(h).strip() if h else '' for h in rows[0]]
        expected_headers = ['VC', '虚拟机名称', '迁移后主机', '迁移后存储']
        if header != expected_headers:
            print(f"警告: Excel表头格式不正确")
            print(f"期望: {expected_headers}")
            print(f"实际: {header}")
            return configs

        for row in rows[1:]:
            if len(row) >= 4:
                config = {
                    'vc_ip': str(row[0]).strip() if row[0] else '',
                    'vm_name': str(row[1]).strip() if row[1] else '',
                    'target_host': str(row[2]).strip() if row[2] and str(row[2]).strip() else None,
                    'target_datastore': str(row[3]).strip() if row[3] and str(row[3]).strip() else None
                }

                # 验证
                if config['vc_ip'] not in VC_CONFIGS:
                    print(f"警告: 跳过虚拟机 '{config['vm_name']}',VC '{config['vc_ip']}' 未配置")
                    continue
                if not config['vm_name']:
                    print(f"警告: 跳过无效行(缺少虚拟机名称): {row}")
                    continue
                if not config['target_host'] and not config['target_datastore']:
                    print(f"警告: 跳过虚拟机 '{config['vm_name']}',目标主机和存储都为空")
                    continue

                # 确定迁移类型
                if config['target_host'] and config['target_datastore']:
                    config['migrate_type'] = '计算+存储同时迁移'
                elif config['target_host']:
                    config['migrate_type'] = '仅迁移计算资源'
                else:
                    config['migrate_type'] = '仅迁移存储'

                configs.append(config)
                print(f"读取配置: {config['vm_name']} -> {config['migrate_type']}")
            else:
                print(f"警告: Excel行数据不完整: {row}")

    except Exception as e:
        print(f"读取Excel文件失败: {e}")

    return configs


def migrate_single_vm(content, config):
    """
    迁移单个虚拟机
    返回: (成功/失败, 虚拟机名称, 消息)
    """
    vm_name = config['vm_name']
    target_host_name = config['target_host']
    target_datastore_name = config['target_datastore']
    migrate_type = config['migrate_type']

    try:
        # 1. 获取虚拟机对象
        vm = get_obj(content, [vim.VirtualMachine], vm_name)
        if not vm:
            return False, vm_name, f"找不到虚拟机 '{vm_name}'"

        # 检查虚拟机状态
        if vm.runtime.powerState != vim.VirtualMachinePowerState.poweredOn:
            return False, vm_name, "虚拟机未开机,不支持热迁移"

        print(f"   [{vm_name}] 开始 {migrate_type}")

        # 2. 构建 RelocateSpec
        relocate_spec = vim.vm.RelocateSpec()

        # 处理计算资源迁移(主机)
        target_host = None
        if target_host_name:
            target_host = get_obj(content, [vim.HostSystem], target_host_name)
            if not target_host:
                return False, vm_name, f"找不到目标主机 '{target_host_name}'"

            # 获取资源池
            if isinstance(target_host.parent, vim.ClusterComputeResource):
                resource_pool = target_host.parent.resourcePool
            else:
                resource_pool = target_host.resourcePool

            if not resource_pool:
                return False, vm_name, "无法获取目标资源池"

            relocate_spec.host = target_host
            relocate_spec.pool = resource_pool
            print(f"   [{vm_name}] 目标主机: {target_host_name}")

        # 处理存储迁移
        target_datastore = None
        if target_datastore_name:
            # 确定用于查找存储的主机
            search_host = target_host if target_host else vm.runtime.host
            if not search_host:
                return False, vm_name, "无法确定用于查找存储的主机"

            # 查找目标存储
            target_datastore = next((ds for ds in search_host.datastore if ds.name == target_datastore_name), None)
            if not target_datastore:
                # 兜底:全局查找存储
                target_datastore = get_obj(content, [vim.Datastore], target_datastore_name)
                if not target_datastore:
                    return False, vm_name, f"找不到目标存储 '{target_datastore_name}'"

            # 验证存储可访问性
            if not target_datastore.summary.accessible:
                return False, vm_name, f"目标存储 '{target_datastore_name}' 状态异常,不可访问"

            relocate_spec.datastore = target_datastore
            print(f"   [{vm_name}] 目标存储: {target_datastore_name}")

            # 【修复】核心:只指定diskId和datastore,vCenter自动保留原有磁盘格式
            # 移除了错误的diskBackingInfo配置,完全由vCenter处理原有磁盘属性
            disk_locators = []
            for device in vm.config.hardware.device:
                if isinstance(device, vim.vm.device.VirtualDisk):
                    disk_locator = vim.vm.RelocateSpec.DiskLocator()
                    disk_locator.diskId = device.key
                    disk_locator.datastore = target_datastore
                    # 不手动指定任何backing信息,完全保留原有配置
                    disk_locators.append(disk_locator)

            if disk_locators:
                relocate_spec.disk = disk_locators
                print(f"   [{vm_name}] 共 {len(disk_locators)} 块磁盘,保持原有格式不变")

        # 3. 网络保持不变:无需任何配置,vMotion自动保留网络配置

        # 4. 执行迁移
        print(f"   [{vm_name}] 正在提交迁移任务...")
        task = vm.RelocateVM_Task(
            spec=relocate_spec,
            priority=vim.VirtualMachine.MovePriority.defaultPriority
        )

        print(f"   [{vm_name}] 等待迁移完成...")
        WaitForTask(task)

        return True, vm_name, f"{migrate_type}成功"

    except Exception as e:
        # 打印详细异常堆栈,方便排查
        import traceback
        traceback.print_exc()
        return False, vm_name, str(e)


def process_vcenter_tasks(vc_ip, vc_cred, vm_list):
    """处理单个vCenter下的所有迁移任务"""
    print(f"\n========== 开始处理 vCenter: {vc_ip} ==========")
    si = None
    context = ssl._create_unverified_context()

    try:
        # 连接vCenter
        si = SmartConnect(
            host=vc_ip,
            user=vc_cred['user'],
            pwd=vc_cred['pwd'],
            port=443,
            sslContext=context
        )
        content = si.content
        print(f"✅ 成功连接到 vCenter: {vc_ip}")
        print(f"🚀 开始并发迁移 (并发数: {MAX_MIGRATE_WORKERS})...")

        # 并发迁移
        success_count = 0
        failed_count = 0
        failed_details = []

        with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_MIGRATE_WORKERS) as executor:
            future_to_vm = {
                executor.submit(migrate_single_vm, content, config): config
                for config in vm_list
            }

            for i, future in enumerate(concurrent.futures.as_completed(future_to_vm), 1):
                config = future_to_vm[future]
                try:
                    success, vm_name, message = future.result()
                    if success:
                        success_count += 1
                        print(f"✅ [{i}/{len(vm_list)}] {vm_name}: {message}")
                    else:
                        failed_count += 1
                        failed_details.append((vm_name, message))
                        print(f"❌ [{i}/{len(vm_list)}] {vm_name}: {message}")
                except Exception as e:
                    failed_count += 1
                    failed_details.append((config['vm_name'], str(e)))
                    print(f"❌ [{i}/{len(vm_list)}] {config['vm_name']}: {str(e)}")

        # 打印总结
        print(f"\n📊 {vc_ip} 迁移完成总结:")
        print(f"   成功: {success_count} 台")
        print(f"   失败: {failed_count} 台")
        if failed_details:
            print(f"   失败详情:")
            for name, msg in failed_details:
                print(f"     - {name}: {msg}")

    except Exception as e:
        print(f"vCenter {vc_ip} 执行错误: {e}")
        import traceback
        traceback.print_exc()
    finally:
        if si:
            Disconnect(si)
            print(f"已断开 vCenter {vc_ip} 连接")


def main():
    print("=" * 60)
    print("🚀 虚拟机批量热迁移工具 (修复版)")
    print("=" * 60)
    print(f"并发迁移数: {MAX_MIGRATE_WORKERS}")

    # 读取所有配置
    all_configs = read_migrate_config_from_xlsx()
    if not all_configs:
        print("没有有效的迁移配置数据")
        return

    # 按VC IP分组
    vc_groups = defaultdict(list)
    for config in all_configs:
        vc_groups[config['vc_ip']].append(config)

    print(f"\n共读取到 {len(all_configs)} 个迁移配置,分布在 {len(vc_groups)} 个vCenter上")

    # 逐个处理每个vCenter
    for vc_ip, vm_list in vc_groups.items():
        if vc_ip in VC_CONFIGS:
            process_vcenter_tasks(vc_ip, VC_CONFIGS[vc_ip], vm_list)
        else:
            print(f"警告: 跳过未配置的vCenter: {vc_ip}")

    print("\n所有迁移任务处理完毕")


if __name__ == "__main__":
    main()

使用方法

在代码内填入vc的有关信息,然后创建vmotion.xlsx(包含VC, 虚拟机名称, 迁移后主机, 迁移后存储四列)在代码文件同目录,运行代码即可

vmotion.xlsx示意图如下:

迁移后主机为空则代表只迁移存储

迁移后存储为空则代表只迁移计算

存储迁移保留原格式

脚本为ai编写,使用前请使用测试环境进行测试

相关推荐
等风来不如迎风去2 小时前
【linux】tar [选项] 归档文件名 要打包的文件/目录..
linux·运维·elasticsearch
Gold Steps.2 小时前
GitOps之Jenkins 构建镜像自动更新 Helm 并触发 ArgoCD 自动同步
运维·ci/cd·云原生
一殊酒2 小时前
【Docker】实战用例:前后端分离项目多容器Docker化设计
运维·docker·容器
熠速2 小时前
CI/CD功能介绍
运维·ci/cd
李长渊哦2 小时前
Nginx 反向代理实战:解决 IPv6 报错与跨网段访问指南
运维·nginx
t_guest2 小时前
自动化脚本-其他格式转utf-8
自动化
信创工程师-小杨2 小时前
银河麒麟SP3如何离线部署二进制docker
运维·docker·容器
小疙瘩2 小时前
本文记录Windows11安装Docker(Docker Desktop)的详细步骤
运维·docker·容器
杨超越luckly2 小时前
AI Agent应用指南 :自动化构建品牌数据库:提示词 + API + 结构化输出
大数据·数据库·人工智能·自动化·ai agent