vmware的python自动化:获取VC下所有ESXI信息
·
目录
自动化需求
获取VC下所有esxi的信息,便于统计以及查询(支持多集群),除基本信息外还包括了VMK接口信息以及硬盘信息的获取
版本:
python3.11
pyvmomi 9.0.0.0
pyvim 3.0.3
代码
import ssl
import socket
import datetime
import concurrent.futures
import pandas as pd
from collections import defaultdict
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim
from pyVmomi import vmodl
from cryptography import x509
from cryptography.hazmat.backends import default_backend
# ================= 配置区域 =================
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'
# }
}
# 导出文件名称
today = datetime.datetime.now().strftime("%Y%m%d")
OUTPUT_FILE = f"ESXi主机巡检_{today}.xlsx"
# 【性能配置】根据你的环境调整
CERT_FETCH_CONCURRENT = 50 # 证书获取并发数,内网环境可开到100
CERT_TIMEOUT = 3 # 证书获取超时时间,内网3秒足够
PARSE_CONCURRENT = 20 # 主机数据解析并发数
# =======================================================================
def create_property_filter_spec(container, vim_type, properties):
"""创建 PropertyCollector 过滤器规范(批量拉取核心)"""
obj_spec = vmodl.query.PropertyCollector.ObjectSpec()
obj_spec.obj = container
obj_spec.selectSet = [
vmodl.query.PropertyCollector.TraversalSpec(
name='traverseEntities',
path='view',
skip=False,
type=vim.view.ContainerView
)
]
prop_spec = vmodl.query.PropertyCollector.PropertySpec()
prop_spec.type = vim_type
prop_spec.pathSet = properties
filter_spec = vmodl.query.PropertyCollector.FilterSpec()
filter_spec.objectSet = [obj_spec]
filter_spec.propSet = [prop_spec]
return filter_spec
def build_cluster_host_map(content):
"""
【性能核心优化】一次性构建集群-主机映射表
避免上千台主机循环内重复API调用找集群
"""
cluster_map = {}
try:
# 一次性拉取所有集群
cluster_container = content.viewManager.CreateContainerView(
content.rootFolder, [vim.ClusterComputeResource], True
)
all_clusters = cluster_container.view
cluster_container.Destroy()
# 构建主机-集群映射
for cluster in all_clusters:
cluster_name = cluster.name
if hasattr(cluster, 'host') and cluster.host:
for host in cluster.host:
cluster_map[host._moId] = cluster_name # 用moId做key,100%匹配
print(f" 集群映射构建完成,共 {len(all_clusters)} 个集群,{len(cluster_map)} 台主机")
return cluster_map
except Exception as e:
print(f" 构建集群映射失败: {str(e)},将使用兜底方案")
return {}
def build_network_cache(content):
"""构建分布式端口组缓存,一次性拉取全量数据"""
network_cache = {}
try:
container = content.viewManager.CreateContainerView(
content.rootFolder, [vim.Network], True
)
all_networks = container.view
container.Destroy()
for net in all_networks:
if isinstance(net, vim.dvs.DistributedVirtualPortgroup):
pg_key = net.key
pg_name = net.name
network_cache[pg_key] = pg_name
network_cache[pg_name] = pg_name
else:
pg_name = net.name
network_cache[pg_name] = pg_name
print(f" 网络缓存构建完成,共 {len(network_cache)} 个端口组")
return network_cache
except Exception as e:
print(f" 构建网络缓存失败: {str(e)}")
return {}
def get_esxi_certificate_expiry_batch(host_ip_list, max_workers, timeout):
"""
【性能核心优化】批量并发获取证书过期时间
替代之前的串行获取,上千台主机几分钟就能跑完
"""
cert_result = {}
def fetch_single_cert(host_ip):
try:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
with socket.create_connection((host_ip, 443), timeout=timeout) as sock:
with context.wrap_socket(sock, server_hostname=host_ip) as ssock:
cert_der = ssock.getpeercert(binary_form=True)
cert = x509.load_der_x509_certificate(cert_der, default_backend())
not_after = cert.not_valid_after_utc if hasattr(cert, 'not_valid_after_utc') else cert.not_valid_after
if not_after.tzinfo:
not_after = not_after.replace(tzinfo=None)
return host_ip, not_after
except Exception:
return host_ip, None
# 并发执行
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_ip = {executor.submit(fetch_single_cert, ip): ip for ip in host_ip_list}
for future in concurrent.futures.as_completed(future_to_ip):
host_ip, not_after = future.result()
cert_result[host_ip] = not_after
return cert_result
def get_host_serial_number(system_info, bios_info):
"""
深度优化:在已拉取的属性里,用10种方式尝试提取序列号
【关键】不做任何额外API调用,只从现有对象里挖,完全不影响速度
"""
invalid_serials = {"", " ", "default string", "unknown", "n/a", "na", "无", "none", "null",
"default", "0000000000", "0", "none", "undefined", "not specified",
"to be filled by o.e.m.", "system serial number"}
def clean_serial(s):
"""清洗序列号"""
if not s:
return None
s = str(s).strip()
if not s or s.lower() in invalid_serials:
return None
return s.upper()
# ============== 第1梯队:最可能有正确序列号的字段 ==============
# 1. systemInfo.serialNumber(首选)
if system_info and hasattr(system_info, 'serialNumber'):
serial = clean_serial(system_info.serialNumber)
if serial:
return serial
# 2. biosInfo.serialNumber(第二首选)
if bios_info and hasattr(bios_info, 'serialNumber'):
serial = clean_serial(bios_info.serialNumber)
if serial:
return serial
# ============== 第2梯队:从systemInfo的其他字段挖 ==============
if system_info:
# 3. systemInfo.otherIdentifyingInfo(深度遍历)
if hasattr(system_info, 'otherIdentifyingInfo') and system_info.otherIdentifyingInfo:
for info in system_info.otherIdentifyingInfo:
if hasattr(info, 'identifierValue'):
serial = clean_serial(info.identifierValue)
if serial:
return serial
# 尝试identifierType
if hasattr(info, 'identifierType') and hasattr(info.identifierType, 'key'):
key = info.identifierType.key.lower() if info.identifierType.key else ''
if 'serial' in key or 'asset' in key:
if hasattr(info, 'identifierValue'):
serial = clean_serial(info.identifierValue)
if serial:
return serial
# 4. 尝试systemInfo的其他可能字段(不同品牌服务器字段不同)
for attr_name in ['serviceTag', 'assetTag', 'serial', 'serialNumberAlt', 'chassisSerialNumber']:
if hasattr(system_info, attr_name):
serial = clean_serial(getattr(system_info, attr_name))
if serial:
return serial
# ============== 第3梯队:从biosInfo的其他字段挖 ==============
if bios_info:
# 5. biosInfo的其他可能字段
for attr_name in ['biosSerialNumber', 'systemSerial', 'serviceTag', 'assetTag']:
if hasattr(bios_info, attr_name):
serial = clean_serial(getattr(bios_info, attr_name))
if serial:
return serial
# ============== 最终兜底 ==============
return "未知"
def get_host_status(overall_status, power_state):
"""获取主机状态"""
status_map = {
'green': '正常',
'yellow': '警告',
'red': '严重',
'gray': '未知'
}
if power_state != 'poweredOn':
return '已关机'
return status_map.get(overall_status, '未知')
def get_vmk_adapters(network_config, network_cache):
"""获取VMkernel适配器信息,100%兼容分布式端口组"""
vmk_info = []
try:
if not network_config or not hasattr(network_config, 'vnic'):
return ''
for vnic in network_config.vnic:
try:
ip = ''
portgroup_name = ''
# 获取IP
if hasattr(vnic, 'spec') and hasattr(vnic.spec, 'ip'):
ip = vnic.spec.ip.ipAddress
if not ip:
continue
# 优先处理分布式端口组
if hasattr(vnic.spec, 'distributedVirtualPort') and vnic.spec.distributedVirtualPort:
dvs_connection = vnic.spec.distributedVirtualPort
if hasattr(dvs_connection, 'portgroupKey') and dvs_connection.portgroupKey:
pg_key = dvs_connection.portgroupKey
if network_cache and pg_key in network_cache:
portgroup_name = network_cache[pg_key]
else:
portgroup_name = f"分布式端口组({pg_key[:8]})"
# 兜底标准端口组
if not portgroup_name:
if hasattr(vnic, 'portgroup') and vnic.portgroup:
pg_name = vnic.portgroup
portgroup_name = network_cache.get(pg_name, pg_name)
elif hasattr(vnic.spec, 'portgroup') and vnic.spec.portgroup:
pg_name = vnic.spec.portgroup
portgroup_name = network_cache.get(pg_name, pg_name)
# 拼接结果
if portgroup_name:
vmk_info.append(f"{ip}({portgroup_name})")
else:
vmk_info.append(f"{ip}(未知端口组)")
except Exception:
continue
return '\n'.join(vmk_info) if vmk_info else ''
except Exception:
return ''
# ================= 【核心修复】NVMe盘误识别修复版 =================
def get_disk_details_batch(storage_device, host_name, vc_ip):
"""
NVMe盘误识别修复版:
1. 【新增】NVMe盘优先判断:在RAID判断前先识别NVMe盘,直接判定为本地物理盘
2. 【优化】RAID盘NAA前缀判断:更精确,避免误判
3. 保留原有能力:远程盘过滤、ATA厂商修复、RAID盘型号强制规范
4. 零额外开销:全内存字符串处理,完全不影响巡检速度
"""
disk_details = []
try:
if not storage_device or not hasattr(storage_device, 'scsiLun'):
return disk_details
# ================= 规则配置区(内存内常量,无性能损耗) =================
# 1. 远程盘过滤规则
remote_transport_types = {'iscsi', 'fc', 'fcoe', 'nfs', 'nvmetcp', 'rdma'}
remote_keywords = {'iscsi', 'vsan', 'vml', 'fc', 'nfs', 'netapp', 'emc', '3par', 'huawei', 'remote'}
# 2. 【新增】NVMe盘专属前缀(优先判断,避免误识别为RAID)
nvme_disk_prefixes = {'t10.nvme', 'eui.', 'naa.5'}
# 3. RAID盘识别关键词(全覆盖你提到的场景)
raid_disk_keywords = {
'logical volume', 'virtual disk', 'raid', 'vd ', ' vd', 'megaraid',
'perc', 'smart array', 'lsi', 'broadcom', 'hp ', 'dell ', 'lenovo ',
'raid volume', 'virtual drive', 'logical drive', 'ld ', ' ld'
}
# 【优化】RAID盘专属NAA前缀(更精确,避免误判NVMe)
raid_naa_prefixes = {'naa.600', 'naa.614', 'naa.624', 'naa.630', 'naa.648', 'naa.678', 'naa.690'}
# 4. 真实物理硬盘厂商映射(仅用于提取真实厂商,RAID卡厂商不算)
real_disk_vendor_map = {
'intel': 'Intel',
'samsung': 'Samsung',
'wdc': 'Western Digital',
'western digital': 'Western Digital',
'seagate': 'Seagate',
'st ': 'Seagate',
'toshiba': 'Toshiba',
'hgst': 'HGST',
'micron': 'Micron',
'crucial': 'Crucial',
'kingston': 'Kingston',
'sandisk': 'SanDisk',
'sk hynix': 'SK Hynix',
'kioxia': 'Kioxia',
'hitachi': 'Hitachi',
'dell': 'Dell',
'pm9a3': 'Samsung', # 你例子中的PM9A3是三星的
'pm9': 'Samsung'
}
# 无效厂商列表
invalid_vendors = {'ata', 'unknown', '', ' ', 'null', 'none', 'lsi', 'perc', 'hp', 'dell', 'broadcom', 'lenovo',
'huawei'}
disk_idx = 1
# 遍历所有SCSI LUN
for lun in storage_device.scsiLun:
# 只保留磁盘类型,过滤光驱/磁带等
if getattr(lun, 'deviceType', '') != 'disk':
continue
# ================= 第一步:彻底过滤远程盘 =================
is_remote = False
# 优先用传输协议精准过滤
if hasattr(lun, 'transport') and hasattr(lun.transport, 'type'):
transport_type = str(lun.transport.type).lower()
if transport_type in remote_transport_types:
is_remote = True
# 关键词兜底过滤
canonical = getattr(lun, 'canonicalName', '').lower()
display_name = getattr(lun, 'displayName', '').lower()
model = getattr(lun, 'model', '').lower()
vendor = getattr(lun, 'vendor', '').lower()
if any(kw in canonical for kw in remote_keywords) or \
any(kw in display_name for kw in remote_keywords) or \
any(kw in model for kw in remote_keywords):
is_remote = True
# 远程盘直接跳过
if is_remote:
continue
# ================= 【核心修复】第二步:优先判断是否为NVMe盘 =================
is_nvme_disk = False
# 通过标识符前缀判断NVMe盘(你例子中的t10.NVMe和eui.都会匹配到)
if any(canonical.startswith(prefix) for prefix in nvme_disk_prefixes):
is_nvme_disk = True
# 关键词兜底判断NVMe
elif 'nvme' in model or 'nvme' in display_name or 'nvme' in canonical:
is_nvme_disk = True
# ================= 第三步:判断是否为RAID盘(NVMe盘直接跳过) =================
is_raid_disk = False
if not is_nvme_disk:
# 1. 先判断NAA前缀(更精确的RAID卡专属前缀)
if any(canonical.startswith(prefix) for prefix in raid_naa_prefixes):
is_raid_disk = True
# 2. 关键词兜底判断(覆盖Logical Volume等场景)
elif any(kw in model for kw in raid_disk_keywords) or \
any(kw in display_name for kw in raid_disk_keywords) or \
any(kw in vendor for kw in raid_disk_keywords):
is_raid_disk = True
# ================= 第四步:提取厂商和型号 =================
final_vendor = ''
final_model = ''
raw_vendor = (getattr(lun, 'vendor', '') or "").strip()
raw_model = (getattr(lun, 'model', '') or "").strip()
full_disk_info = f"{raw_vendor} {raw_model}".lower()
# ---------------- 场景1:RAID盘处理(严格按你的要求) ----------------
if is_raid_disk:
# 型号强制固定为「RAID硬盘」,不管原来是什么值
final_model = "RAID硬盘"
# 厂商:仅提取真实物理硬盘厂商,RAID卡厂商不算,找不到就留空
for vendor_key, vendor_name in real_disk_vendor_map.items():
if vendor_key in full_disk_info:
final_vendor = vendor_name
break
# 找不到真实厂商就留空,绝不填RAID控制器信息
# ---------------- 场景2:NVMe盘/本地物理盘处理 ----------------
else:
final_model = raw_model
# 修复ATA厂商异常
if raw_vendor.lower() in invalid_vendors:
# 从型号中提取真实厂商
for vendor_key, vendor_name in real_disk_vendor_map.items():
if vendor_key in full_disk_info:
final_vendor = vendor_name
# 移除型号中的厂商前缀,只保留纯型号
final_model = raw_model.lower().replace(vendor_key, '').strip().upper()
break
# 还是提取不到就留空
if not final_vendor:
final_vendor = ''
else:
final_vendor = raw_vendor
# ================= 第五步:容量计算 =================
capacity = 0.0
if hasattr(lun, 'capacity') and lun.capacity:
capacity = round(lun.capacity.block * lun.capacity.blockSize / (1024 ** 3), 2)
# ================= 第六步:填充结果 =================
disk_details.append({
"vCenter地址": vc_ip,
"ESXi主机名称": host_name,
"硬盘序号": disk_idx,
"硬盘厂商": final_vendor,
"硬盘型号": final_model,
"硬盘容量(GB)": capacity
})
disk_idx += 1
return disk_details
except Exception as e:
return []
def parse_single_host(props, host_mor, vc_ip, cluster_map, network_cache, cert_result):
"""单台主机数据解析,用于并发处理"""
try:
# 基础信息
host_name = props.get('name', '')
host_moid = host_mor._moId
cluster_name = cluster_map.get(host_moid, '') # 直接从缓存拿,无API调用
power_state = props.get('runtime.powerState', '')
overall_status = props.get('overallStatus', '')
status = get_host_status(overall_status, power_state)
# ESXi版本
esxi_version = ''
product = props.get('config.product')
if product:
esxi_version = product.version if hasattr(product, 'version') else '未知'
# 硬件信息
vendor = ''
model = ''
system_info = props.get('hardware.systemInfo')
if system_info:
vendor = system_info.vendor if hasattr(system_info, 'vendor') else ''
model = system_info.model if hasattr(system_info, 'model') else ''
# 序列号获取(深度优化)
bios_info = props.get('hardware.biosInfo')
serial_number = get_host_serial_number(system_info, bios_info)
# CPU信息
cpu_model = ''
cpu_hz = 0
num_cpu_pkgs = 0
num_cpu_cores = 0
cpu_info = props.get('hardware.cpuInfo')
if cpu_info:
cpu_hz = cpu_info.hz if hasattr(cpu_info, 'hz') else 0
num_cpu_pkgs = cpu_info.numCpuPackages if hasattr(cpu_info, 'numCpuPackages') else 0
num_cpu_cores = cpu_info.numCpuCores if hasattr(cpu_info, 'numCpuCores') else 0
cpu_pkgs = props.get('hardware.cpuPkg')
if cpu_pkgs and len(cpu_pkgs) > 0:
cpu_model = cpu_pkgs[0].description if hasattr(cpu_pkgs[0], 'description') else ''
cpu_ghz = round(cpu_hz / 1e9, 2) if cpu_hz else 0
# 内存信息
memory_size = props.get('hardware.memorySize', 0)
memory_gb = round(memory_size / (1024 ** 3), 2) if memory_size else 0
# 性能统计
cpu_usage = 0.0
memory_usage = 0.0
uptime_days = None
if power_state == 'poweredOn':
quick_stats = props.get('summary.quickStats')
if quick_stats:
# 运行天数
if hasattr(quick_stats, 'uptime'):
uptime_seconds = quick_stats.uptime
if uptime_seconds > 0:
uptime_days = round(uptime_seconds / (24 * 3600), 1)
# CPU使用率
if hasattr(quick_stats, 'overallCpuUsage') and cpu_info:
used_cpu = quick_stats.overallCpuUsage
total_cpu = num_cpu_cores * (cpu_hz / 1000000)
cpu_usage = round(used_cpu / total_cpu * 100, 2) if total_cpu > 0 else 0.0
# 内存使用率
if hasattr(quick_stats, 'overallMemoryUsage'):
used_mem_mb = quick_stats.overallMemoryUsage
total_mem_mb = memory_size / (1024 * 1024)
memory_usage = round(used_mem_mb / total_mem_mb * 100, 2) if total_mem_mb > 0 else 0.0
# 获取主机管理IP
host_ip = ''
network_config = props.get('config.network')
if network_config and hasattr(network_config, 'vnic'):
for vnic in network_config.vnic:
if hasattr(vnic, 'spec') and hasattr(vnic.spec, 'ip') and vnic.spec.ip.ipAddress:
host_ip = vnic.spec.ip.ipAddress
break
if not host_ip:
host_ip = host_name
# 证书过期时间(从批量获取的缓存里拿,无额外耗时)
cert_expiry = cert_result.get(host_ip, None)
# VMkernel适配器
vmk_adapters = get_vmk_adapters(network_config, network_cache)
# 硬盘信息获取(NVMe修复版)
storage_device = props.get('config.storageDevice')
disk_details = get_disk_details_batch(storage_device, host_name, vc_ip)
# 构建主机信息
host_info = {
'vCenter地址': vc_ip,
'ESXi主机名称': host_name,
'集群': cluster_name,
'状况': status,
'ESXi版本': esxi_version,
'服务器厂家': vendor,
'服务器型号': model,
'服务器序列号': serial_number,
'CPU型号': cpu_model,
'CPU主频(GHz)': cpu_ghz,
'物理CPU数量': num_cpu_pkgs,
'CPU核心总数': num_cpu_cores,
'内存总大小(GB)': memory_gb,
'运行天数': uptime_days,
'CPU使用率(%)': cpu_usage,
'内存使用率(%)': memory_usage,
'VMkernel适配器': vmk_adapters,
'证书过期时间': cert_expiry
}
return host_info, disk_details
except Exception as e:
return None, []
def process_single_vc(vc_ip, vc_cred):
"""单VC处理全流程优化"""
print(f"正在连接 vCenter: {vc_ip} ...")
si = None
context = ssl._create_unverified_context()
all_host_info = []
all_disk_info = []
try:
# 连接VC
start_time = datetime.datetime.now()
si = SmartConnect(
host=vc_ip,
user=vc_cred['user'],
pwd=vc_cred['pwd'],
port=443,
sslContext=context
)
content = si.content
connect_cost = (datetime.datetime.now() - start_time).total_seconds()
print(f"✅ 成功连接 vCenter: {vc_ip},耗时 {connect_cost:.2f} 秒")
# 【第一步:预构建所有缓存,全量一次性拉取】
print(f"===== 预构建缓存 =====")
# 1. 集群-主机映射缓存
cluster_map = build_cluster_host_map(content)
# 2. 端口组缓存
network_cache = build_network_cache(content)
# 【第二步:一次性批量拉取所有主机的全量属性】
print(f"===== 批量拉取主机属性 =====")
container = content.viewManager.CreateContainerView(
content.rootFolder, [vim.HostSystem], True
)
# 【保持不变】只保留最基础、兼容性最好的属性
properties = [
'name',
'runtime.powerState',
'overallStatus',
'config.product',
'config.network',
'config.storageDevice',
'hardware.systemInfo',
'hardware.biosInfo',
'hardware.cpuInfo',
'hardware.cpuPkg',
'hardware.memorySize',
'summary.quickStats'
]
filter_spec = create_property_filter_spec(container, vim.HostSystem, properties)
pc = content.propertyCollector
fetch_start = datetime.datetime.now()
result = pc.RetrieveProperties(specSet=[filter_spec])
container.Destroy()
fetch_cost = (datetime.datetime.now() - fetch_start).total_seconds()
print(f"📊 批量拉取完成,共 {len(result)} 台主机,耗时 {fetch_cost:.2f} 秒")
# 【第三步:预收集所有主机IP,批量并发获取证书】
print(f"===== 批量获取证书 =====")
host_ip_list = []
host_props_map = {} # 主机moId -> 属性映射
host_mor_map = {} # 主机moId -> 主机对象
for obj_content in result:
props = {p.name: p.val for p in obj_content.propSet}
host_mor = obj_content.obj
host_moid = host_mor._moId
host_props_map[host_moid] = props
host_mor_map[host_moid] = host_mor
# 提取管理IP
host_ip = ''
network_config = props.get('config.network')
if network_config and hasattr(network_config, 'vnic'):
for vnic in network_config.vnic:
if hasattr(vnic, 'spec') and hasattr(vnic.spec, 'ip') and vnic.spec.ip.ipAddress:
host_ip = vnic.spec.ip.ipAddress
break
if not host_ip:
host_ip = props.get('name', '')
if host_ip:
host_ip_list.append(host_ip)
# 批量并发获取证书
cert_start = datetime.datetime.now()
cert_result = get_esxi_certificate_expiry_batch(
host_ip_list,
max_workers=CERT_FETCH_CONCURRENT,
timeout=CERT_TIMEOUT
)
cert_cost = (datetime.datetime.now() - cert_start).total_seconds()
print(
f"🔐 证书获取完成,共 {len(host_ip_list)} 台,成功 {len([v for v in cert_result.values() if v])} 台,耗时 {cert_cost:.2f} 秒")
# 【第四步:并发解析主机数据】
print(f"===== 解析主机数据 =====")
parse_start = datetime.datetime.now()
with concurrent.futures.ThreadPoolExecutor(max_workers=PARSE_CONCURRENT) as executor:
future_to_moid = {
executor.submit(
parse_single_host,
host_props_map[moid],
host_mor_map[moid],
vc_ip,
cluster_map,
network_cache,
cert_result
): moid for moid in host_props_map
}
for future in concurrent.futures.as_completed(future_to_moid):
host_info, disk_details = future.result()
if host_info:
all_host_info.append(host_info)
if disk_details:
all_disk_info.extend(disk_details)
parse_cost = (datetime.datetime.now() - parse_start).total_seconds()
total_cost = (datetime.datetime.now() - start_time).total_seconds()
print(f"✅ {vc_ip} 处理完成,总耗时 {total_cost:.2f} 秒")
print(f"📊 主机数: {len(all_host_info)} 台,硬盘数: {len(all_disk_info)} 块")
except Exception as e:
print(f"❌ 处理 vCenter {vc_ip} 失败: {str(e)}")
import traceback
traceback.print_exc()
finally:
if si:
Disconnect(si)
print(f"已断开 vCenter {vc_ip} 连接\n")
return all_host_info, all_disk_info
def main():
print("=" * 60)
print("🚀 ESXi主机巡检工具 (NVMe盘误识别修复版)")
print("=" * 60)
total_start = datetime.datetime.now()
total_host_list = []
total_disk_list = []
# 多VC并发处理
print(f"\n开始处理 {len(VC_CONFIGS)} 个 vCenter...")
with concurrent.futures.ThreadPoolExecutor(max_workers=len(VC_CONFIGS)) as executor:
future_to_vc = {
executor.submit(process_single_vc, vc_ip, vc_cred): vc_ip
for vc_ip, vc_cred in VC_CONFIGS.items()
}
for future in concurrent.futures.as_completed(future_to_vc):
vc_ip = future_to_vc[future]
try:
vc_host_list, vc_disk_list = future.result()
total_host_list.extend(vc_host_list)
total_disk_list.extend(vc_disk_list)
except Exception as e:
print(f"❌ vCenter {vc_ip} 处理异常: {str(e)}")
# 导出到Excel
if total_host_list or total_disk_list:
print(f"\n📝 正在导出到 {OUTPUT_FILE} ...")
export_start = datetime.datetime.now()
with pd.ExcelWriter(OUTPUT_FILE, engine='openpyxl') as writer:
# Sheet 1: 主机信息
if total_host_list:
df_host = pd.DataFrame(total_host_list)
host_columns = [
'vCenter地址', 'ESXi主机名称', '集群', '状况', 'ESXi版本',
'服务器厂家', '服务器型号', '服务器序列号', 'CPU型号',
'CPU主频(GHz)', '物理CPU数量', 'CPU核心总数', '内存总大小(GB)',
'运行天数', 'CPU使用率(%)', '内存使用率(%)', 'VMkernel适配器', '证书过期时间'
]
df_host = df_host[host_columns]
df_host.to_excel(writer, index=False, sheet_name='主机信息')
# 调整列宽
worksheet = writer.sheets['主机信息']
host_col_widths = {
'A': 15, 'B': 30, 'C': 20, 'D': 10, 'E': 12,
'F': 20, 'G': 30, 'H': 25, 'I': 40, 'J': 14,
'K': 14, 'L': 14, 'M': 16, 'N': 12, 'O': 12,
'P': 14, 'Q': 14, 'R': 35, 'S': 22
}
for col, width in host_col_widths.items():
worksheet.column_dimensions[col].width = width
# Sheet 2: 硬盘明细
if total_disk_list:
df_disk = pd.DataFrame(total_disk_list)
disk_columns = [
'vCenter地址', 'ESXi主机名称', '硬盘序号', '硬盘厂商', '硬盘型号', '硬盘容量(GB)'
]
df_disk = df_disk[disk_columns]
df_disk.to_excel(writer, index=False, sheet_name='硬盘明细')
worksheet = writer.sheets['硬盘明细']
disk_col_widths = {
'A': 15, 'B': 30, 'C': 10, 'D': 20, 'E': 30, 'F': 16
}
for col, width in disk_col_widths.items():
worksheet.column_dimensions[col].width = width
export_cost = (datetime.datetime.now() - export_start).total_seconds()
total_cost = (datetime.datetime.now() - total_start).total_seconds()
print(f"✅ 导出完成!文件: {OUTPUT_FILE},导出耗时 {export_cost:.2f} 秒")
print(f"⏱️ 总耗时: {total_cost:.2f} 秒")
print(f"📊 主机数: {len(total_host_list)} 台")
print(f"📊 硬盘数: {len(total_disk_list)} 块")
else:
print("⚠️ 未收集到任何信息")
if __name__ == "__main__":
main()
使用方法
在代码内填入vc的有关信息,然后运行脚本即可得到esxi主机信息的xlsx文件
获取的信息包括vCenter地址,ESXi主机名称,集群,状况,ESXi版本,服务器厂家,服务器型号,服务器序列号,CPU型号,CPU主频(GHz),物理CPU数量,CPU核心总数,内存总大小(GB),运行天数,CPU使用率(%),内存使用率(%),VMkernel适配器,证书过期时间等列;除此之外还有硬盘信息(vCenter地址,ESXi主机名称,硬盘序号,硬盘厂商,硬盘型号,硬盘容量(GB))。硬盘信息可能因为做了raid而获取不全,最好的办法还是通过redfish获取效果更好。
脚本为ai编写,使用前请使用测试环境进行测试
更多推荐
所有评论(0)