前言
VMware 在中小型的企业/单位里几乎是标配了。毕竟规模不大的情况下,性价比还是相当好的。对于 VMware 平台上各项指标,我们当然也要去做监控了。
我们使用了 pyVmomi 去连接 vCenter ,获取 ESXi 主机,datastore,VM 等性能指标,并推送给 Open-Falcon
需求
我们需要监控的内容包含:
- ESXi —— 主机的状态当然是要监控的
- datastore —— 所有挂载的存储,要监控他们的使用情况,剩余空间什么的
- vm —— 在提供租户服务时,在租户的服务器里直接塞 agent 肯定是不合适的。得通过 vSphere 来获取租户服务器的相关指标。有些虚拟机,比如思科语音的 CUCM 这种,也没法给他装 agent,只能通过 vSphere 来获取监控指标。
这些数据其实 vCenter 里全部都有,我们在 vCenter 内都能看得到。但是如果我们希望能够统一监控的平台,要把这些监控数据从 vCenter 里取出来,怎么做呢?
pyVmomi 是 VMware vSphere API 的一个 python sdk,我们可以利用它来与 vCenter 交互,获取我们需要的信息.
使用 pyVmomi 连接 vCenter
pyVmomi 说老实话,蛮复杂的。好在 VMware 官方提供了一大堆 example——pyvmomi-community-samples 可以参考。
安装 pyVmomi
也非常容易,pip install pyVmomi
就好了。
首先我们来连接一下 vCenter,与 example 不同的是,我们使用 SmartConnectNoSSL
来连接 vCenter,这样不会由于证书原因报错。
#!/usr/bin/env python
#coding=utf-8
import atexit
from pyVmomi import vim, vmodl
from pyVim.connect import SmartConnectNoSSL, Disconnect
def run(host,user,pwd,port):
try:
si = SmartConnectNoSSL(host=host, user=user, pwd=pwd, port=port)
atexit.register(Disconnect, si)
content = si.RetrieveContent()
print "Hellow World!"
except vmodl.MethodFault as error:
print "Caught vmodl fault : " + error.msg
return False, error.msg
return True, "ok"
if __name__ == "__main__":
host = "vcenter.host"
user = "administrator@vpshere.local"
pwd = "password"
port = 443
print run(host,user,pwd,port)
Hello World !
Hellow World!
(True, 'ok')
获取 Datastore 信息
在连接上 vCenter
之后,我们就可以开始获取各项指标了。我们从 content
下的根目录逐级开始遍历,他的第一个 childEntity
就是我们的 datacenter
print content.rootFolder.childEntity
(ManagedObject) [
'vim.Datacenter:datacenter-2'
]
datacenter
下面的 datastore
属性,即存储的相关信息
for datacenter in content.rootFolder.childEntity:
print "datacenter =",datacenter.name
print datacenter.datastore
datastores = datacenter.datastore
for ds in datastores:
print ds.summary
我们可以通过 datacenter.name
,获取 datacenter
的名字,在组织数据上报的时候,可以作为 tag
打在 datastore
上,可以区分 datastore
来自哪个 datacenter
。
datastore
的容量,类型等数据,则都在 datastore.summary
之中
datacenter = Datacenter
(ManagedObject) [
'vim.Datastore:datastore-52',
'vim.Datastore:datastore-63',
'vim.Datastore:datastore-53',
'vim.Datastore:datastore-64',
'vim.Datastore:datastore-10'
]
(vim.Datastore.Summary) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
datastore = 'vim.Datastore:datastore-52',
name = 'datastore1',
url = 'ds:///vmfs/volumes/56c67e14-26d12426-719b-e02f6dbbf15a/',
capacity = 1189705940992L,
freeSpace = 1025420296192L,
uncommitted = 1005186974305L,
accessible = true,
multipleHostAccess = false,
type = 'VMFS',
maintenanceMode = 'normal'
}
(vim.Datastore.Summary) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
datastore = 'vim.Datastore:datastore-63',
name = 'datastore1 (1)',
url = 'ds:///vmfs/volumes/56c6891b-a7873bd4-c83e-e86549552d90/',
capacity = 1189705940992L,
freeSpace = 1188680433664L,
uncommitted = <unset>,
accessible = true,
multipleHostAccess = false,
type = 'VMFS',
maintenanceMode = 'normal'
}
(vim.Datastore.Summary) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
datastore = 'vim.Datastore:datastore-53',
name = 'datastore2',
url = 'ds:///vmfs/volumes/56c6a783-08a37b38-cd79-e02f6dbbf15a/',
capacity = 1197490569216L,
freeSpace = 302907392000L,
uncommitted = 589L,
accessible = true,
multipleHostAccess = false,
type = 'VMFS',
maintenanceMode = 'normal'
}
(vim.Datastore.Summary) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
datastore = 'vim.Datastore:datastore-64',
name = 'datastore2 (1)',
url = 'ds:///vmfs/volumes/56c6a879-8b734ca2-dddd-e86549552d90/',
capacity = 1197490569216L,
freeSpace = 24554504192L,
uncommitted = 487L,
accessible = true,
multipleHostAccess = false,
type = 'VMFS',
maintenanceMode = 'normal'
}
(vim.Datastore.Summary) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
datastore = 'vim.Datastore:datastore-10',
name = 'datastore1 (32)',
url = 'ds:///vmfs/volumes/59c3b795-2762392c-3cb9-3440b5d64c30/',
capacity = 590021132288L,
freeSpace = 232418967552L,
uncommitted = 2355216338L,
accessible = true,
multipleHostAccess = false,
type = 'VMFS',
maintenanceMode = 'normal'
}
获取 ESXi 信息
ESXi
, 即我们 vSphere
集群中的主机信息,他在 pyVmomi
的结构是这样的:
Datacenter.hostFolder -> Folder
hostFolder.childEntity -> list(Folder or ClusterComputeResource or ComputeResource)
ClusterComputeResource.host -> list(HostSystem)
ComputeResource.host -> list(HostSystem)
Folder
即我们建立的文件夹,ClusterComputeResource
是主机集群,如果没有建立集群,Datacenter
下直接建立主机的话,则是 ComputeResource
Folder
的 childEntity
是一个 list
,里面可能是 Folder
, ClusterComputeResource
或 ComputeResource
ClusterComputeResource
和 ComputeResource
下的 host
也是一个 list
,里面是主机的列表。
这里我们面临一个问题,因为 Folder
下面还可能嵌套 Folder
,所以我们得写个递归来遍历。
def getComputeResource(Folder,computeResourceList):
if hasattr(Folder, 'childEntity'):
for computeResource in Folder.childEntity:
getComputeResource(computeResource,computeResourceList)
else:
computeResourceList.append(Folder)
return computeResourceList
这样,我们就可以遍历所有的 Folder
拿到所有的 computeResource
和 ClusterComputeResource
了
for datacenter in content.rootFolder.childEntity:
print "datacenter =",datacenter.name
if hasattr(datacenter.hostFolder, 'childEntity'):
hostFolder = datacenter.hostFolder
computeResourceList = []
computeResourceList = getComputeResource(hostFolder,computeResourceList)
for computeResource in computeResourceList:
print computeResource.name
print computeResource.host
类似的,主机的相关基本信息都在 host.summary 下面。其中 summary.quickStats 有一些基本的监控信息。cpu,mem 的总量则需要去 host.hardware 中去找。以下是某台主机的host.name
host.hardware.cpuInfo
, host.hardware.memorySize
和 host.summary.quickStats
esxi = 192.168.179.120
(vim.host.CpuInfo) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
numCpuPackages = 2,
numCpuCores = 8,
numCpuThreads = 16,
hz = 2666761067L
}
memorySize = 34347229184
(vim.host.Summary.QuickStats) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
overallCpuUsage = 209,
overallMemoryUsage = 13881,
distributedCpuFairness = 10000,
distributedMemoryFairness = 574,
uptime = 2018927
}
perfManager
现在我们还有主机的网络数据没有拿到,这些在 quickStats 中是没有的。此时就需要通过 perfManager
来获取性能数据了。vSphere
中内置了大量的性能指标,可以从perfManager.perfCounter
中获取
perf_dict = {}
perfList = content.perfManager.perfCounter
for counter in perfList:
counter_full = "{}.{}.{}".format(counter.groupInfo.key, counter.nameInfo.key, counter.rollupType)
perf_dict[counter_full] = counter.key
print perf_dict
通过 content.perfManager.perfCounter
我们可以拿到所有的指标 counter
和对应的 counter key
,后面我们查询的时候会用到这个 counter key
我们先构造一个性能查询的函数
from datetime import timedelta
def BuildQuery(content, vchtime, counterId, instance, entity, interval):
perfManager = content.perfManager
metricId = vim.PerformanceManager.MetricId(counterId=counterId, instance=instance)
startTime = vchtime - timedelta(seconds=(interval + 60))
endTime = vchtime - timedelta(seconds=60)
query = vim.PerformanceManager.QuerySpec(intervalId=20, entity=entity, metricId=[metricId], startTime=startTime,
endTime=endTime)
perfResults = perfManager.QueryPerf(querySpec=[query])
if perfResults:
return perfResults
else:
return False
几个参数说明
-
content
—— 就是连接vCenter
后返回的那个content
-
vchtime
—— 当前的时间,用来计算查询的时间范围,可以通过si.CurrentTime()
去拿vCenter
的当前时间,规避时间同步问题 -
counterId
—— 需要查询的counter
所对应的ID
号 -
instance
—— 需要返回的实例,某些指标会有多个实例。比如网络指标就对应有多张网卡。*
则表示全部返回 -
entity
—— 查询的对象,比如 ESXi 主机或者某个 虚拟机 -
interval
—— 查询的间隔,用来产生查询的时间范围。和我们上报监控的 step 保持一致。
使用 perfManger
查询性能时,需要给出查询的时间范围和查询的颗粒度。我这里使用的颗粒度是 intervalId = 20
,也就是 20 秒一个点。提供的时间范围提前了 1 分钟,避免太接近当前时间的点上查不到数据。
一个返回的示例
(vim.PerformanceManager.EntityMetricBase) [
(vim.PerformanceManager.EntityMetric) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
entity = 'vim.HostSystem:host-9',
sampleInfo = (vim.PerformanceManager.SampleInfo) [
(vim.PerformanceManager.SampleInfo) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
timestamp = 2017-10-20T15:37:20Z,
interval = 20
},
(vim.PerformanceManager.SampleInfo) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
timestamp = 2017-10-20T15:37:40Z,
interval = 20
},
(vim.PerformanceManager.SampleInfo) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
timestamp = 2017-10-20T15:38:00Z,
interval = 20
}
],
value = (vim.PerformanceManager.MetricSeries) [
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = ''
},
value = (long) [
10L,
4L,
5L
]
},
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = 'vmnic3'
},
value = (long) [
0L,
0L,
0L
]
},
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = 'vmnic2'
},
value = (long) [
0L,
0L,
0L
]
},
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = 'vmnic1'
},
value = (long) [
10L,
4L,
5L
]
},
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = 'vusb0'
},
value = (long) [
0L,
0L,
0L
]
},
(vim.PerformanceManager.IntSeries) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
id = (vim.PerformanceManager.MetricId) {
dynamicType = <unset>,
dynamicProperty = (vmodl.DynamicProperty) [],
counterId = 148,
instance = 'vmnic0'
},
value = (long) [
0L,
0L,
0L
]
}
]
}
]
起始的 sampleInfo
表示的是性能数据所在的时间点。我们查的 interval
是 60 秒,颗粒度是 20秒。因此返回了 3 个点。value
是每个点上的值,instance
对应了不同的网卡,如vmnic0
,vmnic1
等,其中 instance = ''
表示这台主机所有的网卡流量总和,也就是这台机器总体的网络流量。
获取 VM 信息
类似的 , vm
的数据都在 Datacenter.vmFolder
下面,当然也会碰到 Folder
嵌套的问题。不过我们之所以遍历 Folder
去获取主机信息,主要是为了能够在遍历过程中,拿到主机上级的 Datacenter
和 Cluster
等信息,作为数据的 tag
一同报送给监控系统。
这些 tag
对于经常飘来飘去的 vm
而言就没太大的意义了,而且对于 Open-Falcon 这样的监控系统而言, tag
的变动意味着 counter
发生变更,数据会落入新的 rrd
当中,无疑是给自己找麻烦。
我们可以通过更简单的办法拿到所有的 vm
清单
obj = content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True)
for vm in obj.view
print vm.summary.quickStats
与主机类似的,vm
的基本状态信息也在 summary.quickStats
内。而诸如网络流量,磁盘 IO 等信息也需要通过 perfManager
来获取。对应的 counter ID
也依然可以从 perfManager.perfCounter
中获取。
此时可能会发现部分 vm
是拿不到网络信息,也就是net.transmitted.average
和 net.received.average
这两个 counter
的性能数据。我们可以打开 vCenter
内虚拟机的性能图表,会发现网络图表也会显示 未指定衡量指标
或者 No Metric Specified
。
这是 VMware 的一个已知 bug,在 vSphere 6.0 Update 1
以上版本被修复。详见官方 Knowledge —— 在 VMware vSphere Client 6.0 中查看虚拟机网络的实时性能图表时显示错误:未指定衡量指标 (2125021)
轮子
如果你使用的监控系统是 Open-Falcon 的话,那么轮子已经造好了。可以直接使用 vsphere-monitor 进行监控。
vsphere-monitor 需要 python2.7
以上版本
安装
git clone https://github.com/freedomkk-qfeng/vsphere-monitor.git
yum install -y python-virtualenv
cd vsphere-monitor
virtualenv ./env
./env/bin/pip install -r requirement.txt
配置
配置文件是 config.py
,修改它就可以了
# falcon
endpoint = "vcenter" # 上报给 open-falcon 的 endpoint
push_api = "http://127.0.0.1:6060/api/push" # 上报的 http api 接口
interval = 60 # 上报的 step 间隔
# vcenter
host = "vcenter.host" # vcenter 的地址
user = "administrator@vsphere.local" # vcenter 的用户名
pwd = "password" # vcenter 的密码
port = 443 # vcenter 的端口
# esxi
esxi_names = [] # 需要采集的 esxi ,留空则全部采集
# datastore
datastore_names = [] # 需要采集的 datastore ,留空则全部采集
# vm
vm_enable = True # 是否要采集虚拟机信息
vm_names = [ # 需要采集的虚拟机,留空则全部采集
"vm1",
"vm2",
"vm3"
]
运行
先尝试跑一下,假定 vsphere-monitor
放在 /opt
下
/opt/vsphere-monitor/env/bin/python /opt/vsphere-monitor/vsphere-monitor.py
没有问题的话,将他放入定时任务
crontab -e
0-59/1 * * * * /opt/vsphere-monitor/env/bin/python /opt/vsphere-monitor/vsphere-monitor.py
运行
ESXi 主机监控
VM 运行监控
参考文档
pyvmomi-community-samples
pyvmomi
在 VMware vSphere Client 6.0 中查看虚拟机网络的实时性能图表时显示错误:未指定衡量指标 (2125021)