使用 pyVmomi 采集 vSphere 监控指标

image.png

前言

VMware 在中小型的企业/单位里几乎是标配了。毕竟规模不大的情况下,性价比还是相当好的。对于 VMware 平台上各项指标,我们当然也要去做监控了。

我们使用了 pyVmomi 去连接 vCenter ,获取 ESXi 主机,datastore,VM 等性能指标,并推送给 Open-Falcon

需求

我们需要监控的内容包含:

  1. ESXi —— 主机的状态当然是要监控的
  2. datastore —— 所有挂载的存储,要监控他们的使用情况,剩余空间什么的
  3. 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
FolderchildEntity 是一个 list,里面可能是 Folder, ClusterComputeResourceComputeResource
ClusterComputeResourceComputeResource 下的 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 拿到所有的 computeResourceClusterComputeResource

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.memorySizehost.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

几个参数说明

  1. content —— 就是连接 vCenter后返回的那个 content
  2. vchtime —— 当前的时间,用来计算查询的时间范围,可以通过 si.CurrentTime() 去拿 vCenter 的当前时间,规避时间同步问题
  3. counterId —— 需要查询的 counter 所对应的 ID
  4. instance —— 需要返回的实例,某些指标会有多个实例。比如网络指标就对应有多张网卡。* 则表示全部返回
  5. entity —— 查询的对象,比如 ESXi 主机或者某个 虚拟机
  6. 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 对应了不同的网卡,如vmnic0vmnic1 等,其中 instance = '' 表示这台主机所有的网卡流量总和,也就是这台机器总体的网络流量。

获取 VM 信息

类似的 , vm 的数据都在 Datacenter.vmFolder 下面,当然也会碰到 Folder 嵌套的问题。不过我们之所以遍历 Folder 去获取主机信息,主要是为了能够在遍历过程中,拿到主机上级的 DatacenterCluster 等信息,作为数据的 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.averagenet.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 主机监控


image.png

VM 运行监控


image.png

参考文档

pyvmomi-community-samples
pyvmomi
在 VMware vSphere Client 6.0 中查看虚拟机网络的实时性能图表时显示错误:未指定衡量指标 (2125021)

以上

转载授权

CC BY-SA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容