运维监控,如何获取数据?

运维如果想做自动化高效化,则少不了搭建监控系统。目前市面上已经有大量成熟、开源的监控平台可供挑选。但如果想实现一个监控系统,或了解监控系统的原理,则可参见本文。

1. 常见运维监控系统划分

常见运维监控系统可按有/无Agent,使用Pull/Push获取数据进行简单划分。

有/无Agent互斥,是单选题;Pull/Push可并存,是多选题。使用这两种划分,共有C(1,2)*( C(1 ,2)+C(2,2) )=6 种情况。

监控实际上发生在监控主机和被监控主机的进程之间。监控主机内运行主动拉取、被动接收进程,分别实现Pull、Push能力;被监控主机开启通用功能(SNMP/SSH/Telnet/HTTP)进程,运行Agent进程,实现向外提供metric数据的能力。

1.1 什么是Agent?

先看定义,“在分布计算领域,人们通常把在分布式系统中持续自主发挥作用的、具有自主性、交互性、反应性、主动性的活着的计算实体称为Agent”。在各式各样的开源项目里,大家都喜欢把那些为了实现某些功能,需要额外部署在客户机上的轻量级程序,称为Agent。

1.2 什么时候需要Agent?

 “需要agent” 的潜台词是“目标需要额外装软件才支持新功能”,类似于打印机需要安装专门的驱动程序才能使用。 “不需要agent” 则意味着 “目标现在已经支持这个功能”,类似于现在无线网卡已经可以免驱动随插随用。需要目标没有的功能或自定义功能,就需要使用到Agent。

1.3 Pull和Push如何选择?

Pull明显会产生更多的流量,Push则流量相对较少,但是否Push就比Pull优秀呢?不是的。Pull在拉取过程中,可以顺便检测被监控机活跃状态,而Push,节点如果没有发送metric,没办法确定是节点ping不通/metric包被丢包/节点本身出现问题,这种情况下Pull会比Push更好定位问题原因。两者还有许多其他的区别,建议成年人不做选择题,Pull/Push全都要,都实现后确定其中某一项为主方式即可。两者的更多区别可以参考此文章

2. 不使用Agent时的数据获取

2.1 SNMP

SNMP是最适合做小流量监控的协议,一般服务器/网络设备/存储设备都会实现。但此协议需要手动配置开启,简要的开启和测试过程如下。

  • //centos启动SNMP服务(本机可以提供SNMP服务)
  • [root@localhost ~]# service snmpstart
  • //centos安装snmp工具包(本机可以拉取SNMP信息)
  • [root@localhost ~]# yum -y install net-snmp-utils
  • //已经安装好的SNMP工具命令
  • [root@localhost ~]# snmp
  • snmpbulkget snmpconf snmpdelta snmpget snmpinform snmpset snmptable snmptls snmptrap snmpusm snmpwalk
  • snmpbulkwalk snmpd snmpdf snmpgetnext snmpnetstat snmpstatus snmptest snmptranslate snmptrapd snmpvacm
  • //SNMP常用的两种拉取,get和walk示例
  • [root@localhost ~]# snmpget -v 2c -c public 10.6.16.128 1.3.6.1.4.1.2021.11.9.0
  • UCD-SNMP-MIB::ssCpuUser.0 = INTEGER: 0
  • [root@localhost ~]# snmpwalk -v 2c -c public 10.6.16.128 1.3.6.1.2.1.3.1.1
  • SNMPv2-SMI::mib-2.3.1.1.1.2.1.10.6.16.250 = INTEGER: 2
  • SNMPv2-SMI::mib-2.3.1.1.1.2.1.10.6.16.253 = INTEGER: 2
  • SNMPv2-SMI::mib-2.3.1.1.1.2.1.10.6.16.254 = INTEGER: 2
  • SNMPv2-SMI::mib-2.3.1.1.2.2.1.10.6.16.250 = Hex-STRING: 00 50 56 F6 1F 49
  • SNMPv2-SMI::mib-2.3.1.1.2.2.1.10.6.16.253 = Hex-STRING: 00 50 56 C0 00 08
  • SNMPv2-SMI::mib-2.3.1.1.2.2.1.10.6.16.254 = Hex-STRING: 00 50 56 FA 04 59
  • SNMPv2-SMI::mib-2.3.1.1.3.2.1.10.6.16.250 = IpAddress: 10.6.16.250
  • SNMPv2-SMI::mib-2.3.1.1.3.2.1.10.6.16.253 = IpAddress: 10.6.16.253
  • //juniper交换机配置SNMP服务
  • set snmp community linux authorization read-only
  • set snmp community linux client-list-name snmp5
  • set snmp community linux_xxx authorization read-write
  • set snmp community linux_xxx client-list-name snmp5
  • //cisco n9k交换机配置SNMP服务
  • snmp-server community linux group network-operator
  • snmp-server community linux_xxx group network-operator
  • snmp-server community linux use-ipv4acl 50
  • snmp-server community linux_xxx use-ipv4acl 50
展开

SNMP功能开启后,类似于开放了一个key-value数据库,我们需要使用一种名为OID的key去获取对应的value。OID对应的含义可查询此链接,笔者整理的通用OID如下。

  • #encoding=utf-8
  • #collect by paul hu 2021/04/05
  • #available for python 2.x/3.x
  • #归类到通用的oid
  • OidsInternet = {
  • "ipNetToMediaEntry":{
  • # 用于拉取物理地址,ip地址,ifindex三者之间的映射(linux服务器适用,网络设备尚未测试)
  • # 除了增加了映射类型,表项与下面的arptable基本一致,但要注意如果是在大二层架构下,arp表只有核心可以查询到,cam是每台机器都可以查到
  • # 1.3.6.1.2.1 = iso.identified-organization.dod.internet.mgmt.mib-2
  • # .4.22.1 = .ip.ipNetToMediaTable.ipNetToMediaEntry
  • 'ipNetToMediaIfIndex' : '1.3.6.1.2.1.4.22.1.1', #唯一标识->ifindex
  • 'ipNetToMediaPhysAddress' : '1.3.6.1.2.1.4.22.1.2', #唯一标识->物理地址
  • 'ipNetToMediaNetAddress' : '1.3.6.1.2.1.4.22.1.3', #唯一标识->实际对应的ip地址
  • 'ipNetToMediaType' : '1.3.6.1.2.1.4.22.1.4', #唯一标识->映射类型
  • },
  • "atEntry":{
  • # 用于拉取Arp表数据(linux服务器/网络设备通用) at= arp table
  • # 1.3.6.1.2.1.3 = iso.org.dod.internet.mgmt.mib-2.at
  • # .1.1.2 = .atTable.atEntry.atPhysAddress
  • 'atIfIndex' : '1.3.6.1.2.1.3.1.1.1', # 唯一标识->ifindex
  • 'atPhysAddress' : '1.3.6.1.2.1.3.1.1.2', # 唯一标识->物理地址
  • 'atNetAddress' : '1.3.6.1.2.1.3.1.1.3', # 唯一标识->ip地址
  • },
  • "ifEntry":{
  • # 接口的各种相关信息,包括类型,速率,状态等(linux服务器/网络设备通用)
  • # 1.3.6.1.2.1 = iso.org.dod.internet.mgmt.mib-2
  • # 2.2.1 = interfaces.ifTable.ifEntry
  • 'ifIndex' : '1.3.6.1.2.1.2.2.1.1', #ifindex
  • 'ifDescr' : '1.3.6.1.2.1.2.2.1.2', #描述
  • 'ifType' : '1.3.6.1.2.1.2.2.1.3', #类型
  • 'ifSpeed' : '1.3.6.1.2.1.2.2.1.5', #速率/带宽
  • 'ifPhysAddress' : '1.3.6.1.2.1.2.2.1.6', #物理地址
  • 'ifAdminStatus' : '1.3.6.1.2.1.2.2.1.7', #管理状态
  • 'ifOperStatus' : '1.3.6.1.2.1.2.2.1.8', #操作状态
  • 'ifLastChange' : '1.3.6.1.2.1.2.2.1.9', #上次变更时间
  • 'IfInOctet' : '1.3.6.1.2.1.2.2.1.10',#接口接收的字节数
  • 'IfInUcastPkts' : '1.3.6.1.2.1.2.2.1.11',#接口接收的数据包数
  • 'IfOutOctet' : '1.3.6.1.2.1.2.2.1.16',#接口发送的字节数
  • 'IfOutUcastPkts': '1.3.6.1.2.1.2.2.1.17',#接口发送的数据包数
  • },
  • "ifXEntry":{
  • # 接口附加信息
  • # 1.3.6.1.2.1 = iso.identified-organization.dod.internet.mgmt.mib-2
  • # .31.1.1.1 = .ifMIB.fMIBObjects.ifXTable.ifXEntry
  • 'ifName': '1.3.6.1.2.1.31.1.1.1.1', # 接口名
  • 'ifHighSpeed': '1.3.6.1.2.1.31.1.1.1.15', # 接口当前带宽的估计值
  • 'ifAlias': '1.3.6.1.2.1.31.1.1.1.18', # 接口别名
  • 'ifStackStatus': '1.3.6.1.2.1.31.1.2.1.3',
  • },
  • "system":{
  • # 系统信息(linux服务器/网络设备通用)
  • # 1.3.6.1.2.1 = iso.org.dod.internet.mgmt.mib-2
  • 'sysDescr': '1.3.6.1.2.1.1.1.0', # 系统描述
  • 'sysObjectID': '1.3.6.1.2.1.1.2.0', # 系统oid
  • 'sysUpTime': '1.3.6.1.2.1.1.3.0', # 系统运行时间
  • 'sysContact': '1.3.6.1.2.1.1.4.0', # 系统联系人
  • 'sysName': '1.3.6.1.2.1.1.5.0', # 系统名
  • 'SysLocation': '1.3.6.1.2.1.1.6.0', # 系统位置
  • 'SysService': '1.3.6.1.2.1.1.7.0', # 系统提供服务
  • },
  • "hrSWRunEntry":{
  • # 运行的进程信息(linux服务器适用,网络设备待测试)
  • # 1.3.6.1.2.1 = identified-organization.dod.internet.mgmt.mib-2
  • # .25.4.2.1 = host.hrSWRun.hrSWRunTable
  • 'hrSWRunIndex': '1.3.6.1.2.1.25.4.2.1.1', # 进程index
  • 'hrSWRunName': '1.3.6.1.2.1.25.4.2.1.2', # 进程名
  • 'hrSWRunID': '1.3.6.1.2.1.25.4.2.1.3', # 进程id
  • 'hrSWRunPath': '1.3.6.1.2.1.25.4.2.1.4', # 进程运行路径
  • 'hrSWRunParameters': '1.3.6.1.2.1.25.4.2.1.5', # 进程运行参数
  • 'hrSWRunType': '1.3.6.1.2.1.25.4.2.1.6', # 进程运行类型
  • 'hrSWRunStatus': '1.3.6.1.2.1.25.4.2.1.7', # 进程运行状态
  • 'hrSWRunPriority': '1.3.6.1.2.1.25.4.2.1.8', # 进程运行优先级
  • },
  • "hrSWInstalledEntry":{
  • # 安装的软件列表(linux服务器适用,网络设备待测试)
  • # 1.3.6.1.2.1 = identified-organization.dod.internet.mgmt.mib-2
  • # .25.6.3.1 = host.hrSWInstalled.hrSWInstalledTable.hrSWInstalledEntry
  • 'hrSWInstalledIndex': '1.3.6.1.2.1.25.6.3.1.1', # 安装index
  • 'hrSWInstalledName': '1.3.6.1.2.1.25.6.3.1.2', # 安装软件名
  • 'hrSWInstalledID': '1.3.6.1.2.1.25.6.3.1.3', # 安装id
  • 'hrSWInstalledType': '1.3.6.1.2.1.25.6.3.1.4', # 安装类型
  • 'hrSWInstalledDate': '1.3.6.1.2.1.25.6.3.1.5', # 安装时间
  • 'hrSWInstalledDescription': '1.3.6.1.2.1.25.6.3.1.6', # 安装描述
  • 'hrSWInstalledVersion': '1.3.6.1.2.1.25.6.3.1.7', # 安装版本
  • },
  • "ipCidrRouteEntry":{
  • # 路由表相关(linux服务器/开启了三层功能的网络设备)
  • # 1.3.6.1.2.1 = iso.org.dod.internet.mgmt.mib-2
  • # 4.24.4.1 = ip.ipForward.ipCidrRouteTable.ipCidrRouteEntry
  • # 更详细的路由表内容请自行搜索添加,此处仅添加前4项
  • 'ipCidrRouteDest': '1.3.6.1.2.1.4.24.4.1.1', # 目标网段
  • 'ipCidrRouteMask': '1.3.6.1.2.1.4.24.4.1.2', # 目标网段掩码
  • 'ipCidrRouteTos': '1.3.6.1.2.1.4.24.4.1.3', # TYPE OF SERVICE
  • 'ipCidrRouteNextHop': '1.3.6.1.2.1.4.24.4.1.4', # 下一跳地址
  • },
  • "hrStorage": {
  • # 存储相关
  • "hrStorageTypes": "1.3.6.1.2.1.25.2.1", # 获取存储类型
  • "hrMemorySize": "1.3.6.1.2.1.25.2.2", # 获取内存大小
  • "hrStorageIndex": "1.3.6.1.2.1.25.2.3.1.1", # 存储设备编号
  • "hrStorageType": "1.3.6.1.2.1.25.2.3.1.2", # 存储设备类型
  • "hrStorageDescr": "1.3.6.1.2.1.25.2.3.1.3", # 存储设备描述
  • "hrStorageAllocationUnits": "1.3.6.1.2.1.25.2.3.1.4", # 簇的大小
  • "hrStorageSize": "1.3.6.1.2.1.25.2.3.1.5", # 簇的的数目
  • "hrStorageUsed": "1.3.6.1.2.1.25.2.3.1.6", # 使用多少,跟总容量相除就是占用率
  • },
  • "extra":{
  • # 其他未分类oid
  • # 二层相关拉取(一般仅交换机,具体使用请结合品牌进行拉取测试)
  • # 1.3.6.1.2.1 = iso.org.dod.internet.mgmt.mib-2
  • # 17.1.4.1 = dot1dBridge.dot1dBase.dot1dBasePortTable.dot1dBasePortEntry
  • 'jnxdot1qTpFdbPort': '1.3.6.1.2.1.17.7.1.2.2.1.2',
  • 'dot1dBasePortIfIndex': '1.3.6.1.2.1.17.1.4.1.2',
  • 'dot1dTpFdbPort': '1.3.6.1.2.1.17.4.3.1.2',
  • 'dot1dTpFdbAddress': '1.3.6.1.2.1.17.4.3.1.1',
  • 'dot1qNumVlans': '1.3.6.1.2.1.17.7.1.1.4',
  • 'dot1qTpFdbTable': '1.3.6.1.2.1.17.7.1.2.2',
  • 'dot1qPvid': '1.3.6.1.2.1.17.7.1.4.5.1.1', # portid->vlan
  • },
  • "hrProcessorTable":{
  • # 处理器表
  • "hrProcessorLoad": "1.3.6.1.2.1.25.3.3.1.2", # CPU的当前负载,N个核就有N个负载
  • },
  • }
  • #归类到私有的oid
  • OidsPrivate = {
  • "systemStats": {
  • # 系统状态,负载cpu等(linux服务器适用,网络设备需要测试)
  • "ssCpuUser": "1.3.6.1.4.1.2021.11.9.0", # 用户CPU百分比
  • "ssCpuSystem": "1.3.6.1.4.1.2021.11.10.0", # 系统CPU百分比
  • "ssCpuIdle": "1.3.6.1.4.1.2021.11.11.0", # 空闲CPU百分比
  • "ssCpuRawUser": "1.3.6.1.4.1.2021.11.50.0", # 原始用户CPU使用时间
  • "ssCpuRawNice": "1.3.6.1.4.1.2021.11.51.0", # 原始nice占用时间
  • "ssCpuRawSystem": "1.3.6.1.4.1.2021.11.52.0", # 原始系统CPU使用时间
  • "ssCpuRawIdle": "1.3.6.1.4.1.2021.11.53.0", # 原始CPU空闲时间
  • "ssSwapIn": "1.3.6.1.4.1.2021.11.3.0",
  • "SsSwapOut": "1.3.6.1.4.1.2021.11.4.0",
  • "ssIOSent": "1.3.6.1.4.1.2021.11.5.0",
  • "ssIOReceive": "1.3.6.1.4.1.2021.11.6.0",
  • "ssSysInterrupts": "1.3.6.1.4.1.2021.11.7.0",
  • "ssSysContext": "1.3.6.1.4.1.2021.11.8.0",
  • "ssCpuRawWait": "1.3.6.1.4.1.2021.11.54.0",
  • "ssCpuRawInterrupt": "1.3.6.1.4.1.2021.11.56.0",
  • "ssIORawSent": "1.3.6.1.4.1.2021.11.57.0",
  • "ssIORawReceived": "1.3.6.1.4.1.2021.11.58.0",
  • "ssRawInterrupts": "1.3.6.1.4.1.2021.11.59.0",
  • "ssRawContexts": "1.3.6.1.4.1.2021.11.60.0",
  • "ssCpuRawSoftIRQ": "1.3.6.1.4.1.2021.11.61.0",
  • "ssRawSwapIn": "1.3.6.1.4.1.2021.11.62.0",
  • "ssRawSwapOut": "1.3.6.1.4.1.2021.11.63.0",
  • "Load5": "1.3.6.1.4.1.2021.10.1.3.1",
  • "Load10": "1.3.6.1.4.1.2021.10.1.3.2",
  • },
  • "memTotalSwap": {
  • # 交换内存相关(linux服务器适用,网络设备需要测试)
  • "memTotalSwap": "1.3.6.1.4.1.2021.4.3.0", # Total Swap Size(虚拟内存)
  • "memAvailSwap": "1.3.6.1.4.1.2021.4.4.0", # Available Swap Space
  • "memTotalReal": "1.3.6.1.4.1.2021.4.5.0", # Total RAM in machine
  • "memAvailReal": "1.3.6.1.4.1.2021.4.6.0", # Total RAM used
  • "memTotalFree": "1.3.6.1.4.1.2021.4.11.0", # Total RAM Free
  • "memShared": "1.3.6.1.4.1.2021.4.13.0", # Total RAM Shared
  • "memBuffer": "1.3.6.1.4.1.2021.4.14.0", # Total RAM Buffered
  • "memCached": "1.3.6.1.4.1.2021.4.15.0", # Total Cached Memory
  • },
  • "dskEntry": {
  • # 磁盘相关(linux服务器适用,网络设备需要测试)
  • "dskPath": "1.3.6.1.4.1.2021.9.1.2", # Path where the disk is mounted
  • "dskDevice": "1.3.6.1.4.1.2021.9.1.3", # Path of the device for the partition
  • "dskTotal": "1.3.6.1.4.1.2021.9.1.6", # Total size of the disk/partion (kBytes)
  • "dskAvail": "1.3.6.1.4.1.2021.9.1.7", # Available space on the disk
  • "dskUsed": "1.3.6.1.4.1.2021.9.1.8", # Used space on the disk
  • "dskPercent": "1.3.6.1.4.1.2021.9.1.9", # Percentage of space used on disk
  • "dskPercentNode": "1.3.6.1.4.1.2021.9.1.10", # Percentage of inodes used on disk
  • },
  • "chassisGrp":{
  • # CISCO私有,集群相关
  • # 1.3.6.1.4.1 = iso.org.dod.internet.private.enterprises
  • # 9.5.1.2.16 = cisco.wkgrpProducts.stack.chassisGrp.chassisModel
  • 'chassisModel': '1.3.6.1.4.1.9.5.1.2.16.0',
  • },
  • "vmVoiceVlanEntry":{
  • # CISCO私有,vmVoice相关
  • # 1.3.6.1.4.1.9 = iso.org.dod.internet.private.enterprises.cisco
  • # 9.68.1 = ciscoMgmt.ciscoVlanMembershipMIB.ciscoVlanMembershipMIBObjects
  • # 5.1.1.1 = vmVoiceVlan.vmVoiceVlanTable.vmVoiceVlanEntry,vmVoiceVlanId
  • 'vmVoiceVlanId' : '1.3.6.1.4.1.9.9.68.1.5.1.1.1',
  • 'vmVlan' : '1.3.6.1.4.1.9.9.68.1.2.2.1.2',
  • },
  • "c2900PortEntry":{
  • # CISCO私有
  • # 1.3.6.1.4.1.9 = iso.org.dod.internet.private.enterprises.cisco
  • # 9.87.1.4 = ciscoMgmt.ciscoC2900MIB.c2900MIBObjects.c2900Port
  • # 1.1.32 = c2900PortTable.c2900PortEntry.c2900PortDuplexStatus
  • 'c2900PortLinkbeatStatus' : '1.3.6.1.4.1.9.9.87.1.4.1.1.18',
  • 'c2900PortDuplexState' : '1.3.6.1.4.1.9.9.87.1.4.1.1.31',
  • 'c2900PortDuplexStatus' : '1.3.6.1.4.1.9.9.87.1.4.1.1.32',
  • 'c2900PortVoiceVlanId' : '1.3.6.1.4.1.9.9.87.1.4.1.1.37',
  • },
  • "moduleEntry":{
  • # CISCO私有
  • # 1.3.6.1.4.1 iso.org.dod.internet.private.enterprises
  • # 9.5.1.3 cisco.wkgrpProducts.stack.moduleGrp
  • # 1.1.2 moduleTable.moduleEntry.moduleType
  • 'moduleType': '1.3.6.1.4.1.9.5.1.3.1.1.2',
  • 'moduleSerialNumber': '1.3.6.1.4.1.9.5.1.3.1.1.3',
  • 'moduleName': '1.3.6.1.4.1.9.5.1.3.1.1.13',
  • 'moduleModel': '1.3.6.1.4.1.9.5.1.3.1.1.17',
  • 'moduleHwVersion': '1.3.6.1.4.1.9.5.1.3.1.1.18',
  • 'moduleFwVersion': '1.3.6.1.4.1.9.5.1.3.1.1.19',
  • 'moduleSwVersion': '1.3.6.1.4.1.9.5.1.3.1.1.20',
  • 'moduleSerialNumberString': '1.3.6.1.4.1.9.5.1.3.1.1.26',
  • },
  • "vlanPortEntry": {
  • # CISCO私有
  • # 1.3.6.1.4.1.9 = iso.org.dod.internet.private.enterprises.cisco
  • # 5.1.9.3.1.3 = wkgrpProducts.stack.vlanGrp.vlanPortTable.vlanPortEntry.vlanPortVlan
  • 'vlanPortVlan' : '1.3.6.1.4.1.9.5.1.9.3.1.3',
  • 'vlanPortIslAdminStatus': '1.3.6.1.4.1.9.5.1.9.3.1.7',
  • 'vlanPortIslOperStatus' : '1.3.6.1.4.1.9.5.1.9.3.1.8',
  • },
  • "cdpCacheEntry": {
  • # CISCO私有,CDP协议相关
  • # 1.3.6.1.4.1.9 = iso.org.dod.internet.private.enterprises.cisco
  • # 9.23.1.2 = ciscoMgmt.ciscoCdpMIB.ciscoCdpMIBObjects.cdpCache
  • # 1.1.8 = cdpCacheTable.cdpCacheEntry.cdpCachePlatform
  • 'cdpCacheDeviceId' : '1.3.6.1.4.1.9.9.23.1.2.1.1.6',
  • 'cdpCacheDevicePort': '1.3.6.1.4.1.9.9.23.1.2.1.1.7',
  • 'cdpCachePlatform' : '1.3.6.1.4.1.9.9.23.1.2.1.1.8',
  • },
  • "vtpVlanEntry":{
  • # CISCO私有,VTP相关
  • # 1.3.6.1.4.1.9 = iso.org.dod.internet.private.enterprises.cisco
  • # 9.46.1.6.1.1 = ciscoMgmt.ciscoVtpMIB.vtpMIBObjects.vlanTrunkPorts.vlanTrunkPortTable.vlanTrunkPortEntry
  • 'vtpVlanState' : '1.3.6.1.4.1.9.9.46.1.3.1.1.2',
  • 'vlanTrunkPortVlansEnabled' : '1.3.6.1.4.1.9.9.46.1.6.1.1.4',
  • 'vlanTrunkPortNativeVlan' : '1.3.6.1.4.1.9.9.46.1.6.1.1.5',
  • 'vlanTrunkPortDynamicState' : '1.3.6.1.4.1.9.9.46.1.6.1.1.13',
  • 'vlanTrunkPortDynamicStatus' : '1.3.6.1.4.1.9.9.46.1.6.1.1.14',
  • 'vlanTrunkPortVlansEnabled2k': '1.3.6.1.4.1.9.9.46.1.6.1.1.17',
  • 'vlanTrunkPortVlansEnabled3k': '1.3.6.1.4.1.9.9.46.1.6.1.1.18',
  • 'vlanTrunkPortVlansEnabled4k': '1.3.6.1.4.1.9.9.46.1.6.1.1.19',
  • },
  • "hwL2IfEntry":{
  • # 华为私有,二层表相关,需要其他的信息可以参考下面的oid自行添加
  • "hwVlanTrunkPortDynamicStatus" : "1.3.6.1.4.1.2011.5.25.42.1.1.1.3.1.3",
  • },
  • "hh3cifXXEntry":{
  • # h3c私有,二层表相关,需要其他的信息可以参考下面的oid自行添加
  • "h3cVlanTrunkPortDynamicStatus": "1.3.6.1.4.1.25506.8.35.1.1.1.5",
  • },
  • "myVlanIfStateEntry":{
  • # 锐捷私有,二层表相关,需要其他的信息可以参考下面的oid自行添加
  • "RuijieVlanTrunkPortDynamicStatus": "1.3.6.1.4.1.4881.1.1.10.2.9.1.6.1.2",
  • },
  • "jnxExVlanPortGroupEntry":{
  • # JUNIPER私有
  • # 1.3.6.1.2.1 = iso.org.dod.internet.private.enterprise
  • # 2636.3.40.1 = 2636.jnxMibs.jnxExMibRoot.jnxExSwitching
  • # 5.1.7 = jnxExVlan.jnxVlanMIBObjects.jnxExVlanPortGroupTable
  • # 1.5 = jnxExVlanPortGroupEntry.jnxExVlanPortAccessMode
  • 'jnxExVlanPortAccessMode' : '1.3.6.1.4.1.2636.3.40.1.5.1.7.1.5',
  • },
  • }
展开

OID实际为树状结构,比如1.3.6.1.4.1和1.3.6.1.4.2分别是1.3.6.1.4下面的两个树分支。SNMP获取数据一般为GET和WALK两种,GET需精确到树状结构的叶子节点级别,适合拉取CPU使用率这样的值;WALK则会对整个树状结构进行遍历,适合拉取整个ARP表或接口表。至于如何实现SNMP拉取,调用不同语言的SNMP包即可,比如GO的"github.com/soniah/gosnmp" 包、PYTHON的pysnmp包,不展开。

2.2 SSH

SSH用于远程管理,一般服务器/网络设备/存储设备都会实现。相信运维/开发对此协议都很熟悉,用于监控时,它可以直接输入系统命令从而获得监控数据输出。优点是一次就能获取大量的信息,缺点是交互不好控制和获取到的输出往往需要清洗处理。SSH示例如下。

  • //go version go1.14.6 windows/amd64
  • package main
  • import (
  • "bytes"
  • "fmt"
  • "golang.org/x/crypto/ssh"
  • "net"
  • "time"
  • )
  • //建立ssh client
  • func InitClient(user, password, host string, port int)(*ssh.Client, error){
  • var (
  • auth []ssh.AuthMethod
  • addr string
  • clientConfig *ssh.ClientConfig
  • client *ssh.Client
  • err error
  • )
  • // 获取认证method
  • auth = make([]ssh.AuthMethod, 0)
  • auth = append(auth, ssh.Password(password))
  • clientConfig = &ssh.ClientConfig{
  • User: user,
  • Auth: auth,
  • Timeout: 30 * time.Second,
  • //需要验证服务端,不做验证返回nil就可以,编辑器点HostKeyCallback看源码
  • HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
  • return nil
  • },
  • }
  • addr = fmt.Sprintf("%s:%d", host, port)
  • // 三次tcp dial防止失败
  • for i:=0; i<3 ;i++{
  • if client, err = ssh.Dial("tcp", addr, clientConfig); err == nil {
  • return client, nil
  • }
  • }
  • return nil,err
  • }
  • //建立SSH会话
  • func InitSession(client *ssh.Client) (*ssh.Session, error) {
  • var (
  • session *ssh.Session
  • err error
  • )
  • // 三次建立ssh连接防止失败
  • for i:=0; i<3 ;i++{
  • if session, err = client.NewSession(); err == nil {
  • return session, nil
  • }
  • }
  • return nil, err
  • }
  • //初始化SSH命令执行函数
  • func InitExcutor(client *ssh.Client,session *ssh.Session)( func(string) error, func() (string,string,error), error ){
  • var err error
  • printer :=func(err error){ //打印函数
  • fmt.Printf("error in InitExcutor:%s",err)
  • }
  • if session==nil { //如果没有传入session,重新建立session
  • session, err=InitSession(client)
  • if err != nil{
  • printer(err)
  • return nil,nil,err
  • }
  • }
  • stdinBuf, err := session.StdinPipe() //开启一个名为stdinBuf的stdin pipe,以stdinBuf.Write()模拟输入
  • if err != nil{
  • printer(err)
  • return nil,nil,err
  • }
  • var outbt, errbt bytes.Buffer //创建buffer
  • session.Stdout = &outbt //buffer地址给stdout,即输出到outbt
  • session.Stderr = &errbt
  • err = session.Shell() //开启shell,不然一个session只能执行一条命令
  • if err != nil{
  • printer(err)
  • return nil,nil,err
  • }
  • excuteFunc:=func(cmd string) error{ //执行函数
  • fmt.Printf("发送命令:%s\\n",cmd)
  • _,err=stdinBuf.Write([]byte(cmd+"\\n"))
  • return err
  • }
  • readRes:= func() (string,string,error) { //读取退出函数
  • _,err=stdinBuf.Write([]byte("exit\\n")) //发送exit以退出
  • if err!=nil{return "","",err}
  • //session.Wait()
  • err=session.Wait() //等到退出信号,一般要先发送exit才会收到这个信号,但发送exit后往往会自动关闭session,所以下面的session关闭仅为保证关闭
  • if err!=nil{
  • fmt.Printf("error happens when session wait :%s",err)
  • }
  • err=session.Close()
  • if err!=nil{
  • fmt.Printf("error happens when session close :%s",err)
  • }
  • return outbt.String(),errbt.String(),nil
  • }
  • return excuteFunc,readRes,nil
  • }
  • func main(){
  • usr :="root"
  • pswd:="Justjokeforyou"
  • host:="120.139.213.44"
  • port:= 22
  • client,_ := InitClient(usr,pswd,host,port)
  • sn,_ := InitSession(client)
  • excute,exRead,_:=InitExcutor(client,sn)
  • excute("ps -axu")
  • out,_,_:=exRead()
  • fmt.Printf("%v",out)
  • }
展开

2.3 Telnet

类似于SSH,一般服务器/网络设备/存储设备都会实现。此处不展开。

2.4 HTTP/HTTPS

HTTP用于提供所谓API接口数据,以前的网络/存储设备很少有自带HTTP功能,但现在基本上都已经有HTTP功能可选。只需在设备上开启此功能,然后参看接口文档,调用对应接口即可取到相应的数据。但服务器安装centos,默认是没有内置HTTP功能的,需要自己挂个HTTP服务或者运行agent,才能提供HTTP服务。下面为仅列出使用HTTP如何构造Header,以及常用认证方式,具体如何取数据见API文档。

  • #常用header
  • commonHeaders = {
  • 'Accept' : "text/plain, text/ html", #可接受的响应内容类型
  • 'Accept-Encoding' : "gzip, deflate, sdch", #可接受的响应内容的编码方式
  • 'Accept-Language':"zh_CN,en", #可接受的语言
  • 'Accept-Charset' : "utf-8", #可接受的字符集
  • 'Authorization' : "", #用于表示HTTP协议中需要认证资源的认证信息
  • 'Cache-Control' : "no-cache", #用来指定当前的请求/回复中的,是否使用缓存机制
  • 'Connection' : "keep-alive", #keepalive/Upgrade
  • 'Content-Type' : "application/json; charset=utf-8", #请求体类型
  • 'Cookie': "", #由之前服务器通过Set-Cookie设置的一个HTTP协议Cookie
  • 'Content-Length' : "348", #以8进制表示的请求体的长度
  • 'Date' : "Tue, 15 Nov 2010 08:12:31 GMT", #发送该消息的日期和时间
  • 'Expect': "100-continue", #表示客户端要求服务器做出特定的行为
  • 'From': "user@itbilu.com", #发起此请求的用户的邮件地址
  • 'Host' : "{}:{}", #表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略
  • 'Origin' : "http://www.baidu.com", #发起一个针对跨域资源共享的请求
  • 'Pragma' : "no-cache", #与具体的实现相关,这些字段可能在请求/回应链中的任何时候产生
  • 'Proxy-Authorization' : "", #用于向代理进行认证的认证信息
  • 'Range' : "bytes=500-999", #表示请求某个实体的一部分,字节偏移以0开始
  • 'Referer' : "http://www.baidu.com", #表示浏览器所访问的前一个页面,可以认为是之前访问页面的链接将浏览器带到了当前页面。
  • 'User-Agent' : "Mozilla/5.0(Windows NT 6.1)AppleWebKit/537.36(KHTML, like Gecko)Chrome/38.0.2125.111Safari/537.36", #浏览器的身份标识字符串
  • 'X-Requested-With' : "XMLHttpRequest", #非标,通常在值为“XMLHttpRequest”时使用
  • }
  • def getHeaders(selectors=[], filters=[], **kwargs):
  • """获取一个定制的header"""
  • if not selectors:
  • selectors = [i for i in commonHeaders]
  • if selectors and filters:
  • diff = set(selectors).difference(set(filters))
  • selectors = list(diff)
  • tmpHeaders = {}
  • for s in selectors:
  • tmpHeaders[s] = commonHeaders[s]
  • keys = kwargs.keys()
  • if keys:
  • for key in keys:
  • tmpHeaders[key] = kwargs.get(key) or ""
  • return tmpHeaders
  • def getNewHeaders(host=""):
  • """获取一个新的headers"""
  • selectors = [ 'Accept', 'Accept-Encoding', 'Accept-Language', 'Cache-Control', 'Connection',
  • 'Content-Type', 'Host', 'User-Agent', 'X-Requested-With']
  • tmpHeaders = getHeaders(selectors, [])
  • if host != "":
  • tmpHeaders["host"] = host
  • return tmpHeaders
  • # HTTP basic auth,无状态,在每个请求里带user和password,类似下面,部分国外厂商默认用这个
  • import requests
  • usr,pwd = "root","root123"
  • requests.get(url=url,auth=(usr,pwd))
  • # 返回session的auth,一般认证信息放入data,大部分认证都类似这个
  • usr,pwd = "root","root123"
  • authurl = "www.xxx.com/login"
  • requests.post(url=authurl,data={'usr':usr,'pwd':pwd})
展开

2.5 Syslog

Syslog用于传递日志信息,一般服务器/网络/存储设备都会具备此功能。Syslog有发送方和接收方,网络/存储设备一般为发送方,服务器一般为接受方。但服务器也可以配置成发送方,如centos一般都自带了rsyslog功能,可以根据需求配置成接收方/接受方,然后使用“service rsyslog restart”命令启动。

  • //centos配置syslog发送
  • [root@hecs-197747 etc]# cat -n rsyslog.conf
  • ...
  • 89 # remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
  • 90 #*.* @@remote-host:514
  • 91 # ### end of the forwarding rule ###
  • 主要就是将90行的“#”去掉,然后将“*.* @@remote-host:514”换成日志服务器的ip,如“ *.*@@10.22.11.185:514
  • //centos配置syslog接收
  • [root@hecs-197747 etc]# cat -n rsyslog.conf
  • 6 #### MODULES ####
  • 7
  • 8 # The imjournal module bellow is now used as a message source instead of imuxsock.
  • 9 $ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
  • 10 $ModLoad imjournal # provides access to the systemd journal
  • 11 #$ModLoad imklog # reads kernel messages (the same are read from journald)
  • 12 #$ModLoad immark # provides --MARK-- message capability
  • ...
  • 14 # Provides UDP syslog reception
  • 15 #$ModLoad imudp
  • 16 #$UDPServerRun 514
  • ...
  • 18 # Provides TCP syslog reception
  • 19 #$ModLoad imtcp
  • 20 #$InputTCPServerRun 514
  • ...
  • 主要就是将上面的“#”去掉,其他如日志等级,日志到哪个文件夹可以后面再调整。
  • //cisco n9k配置syslog发送
  • logging server 111.99.36.82 4
  • logging server 111.99.36.86 4
  • logging server 19.1.9.212 16
  • logging source-interface Vlan2004
  • logging timestamp milliseconds
  • logging level daemon 2
  • //juniper配置syslog发送
  • set system syslog user * any emergency
  • set system syslog host 19.20.81.12 any info
  • set system syslog host 19.20.81.12 match "!LBCM-L2,pfe_bcm_l2_mac_add"
  • set system syslog host 19.20.81.12 log-prefix DCC-ITB-SW-14
  • set system syslog host 19.20.81.12 source-address 10.2.92.23
  • set system syslog host 19.21.131.134 any info
  • set system syslog host 19.21.131.134 match "!LBCM-L2,pfe_bcm_l2_mac_add"
  • set system syslog host 19.21.131.134 log-prefix DCC-ITB-SW-14
  • set system syslog host 19.21.131.134 source-address 10.2.92.23
展开

3. 使用Agent时的数据获取

不使用Agent时,不必了解数据如何被收集。需要了解的是SNMP、SSH等协议的内容,而不需要了解这些协议的进程在被监控机上是如何从OS处收集数据的。但如果使用Agent获取数据,在动手写一个Agent之前,需了解Agent一般是怎么去从OS处收集数据的。通常地,Agent从OS收集数据有文件读取、命令行获取、其他系统调用三种方式。监控程序和Agent之间的沟通,可以自行使用任意协议,但一般地,会选用HTTP/HTTPS进行通信。

3.1 文件读取

读取的文件分为两种,系统文件和应用数据文件。系统文件读取的系统的运行数据,应用数据文件读取的是应用的运行数据。仅以系统文件举例,例如Linux系统的监控,大多可以靠读取/proc/目录下的文件实现。/proc/下文件对应的用途和含义详见笔者的另一篇文章《Linux Procfs (一) /proc/* 文件实例解析》

  • //下面的代码截取自open-falcon的agent实现,用的都是读取文件的方法获取数据。
  • //因为是部分截取函数用于说明,缺少引用部分,虽是源码但并不能直接运行。但相信读者读完可以自己写出类似的代码。
  • //centos返回Cpu的频率
  • func CpuMHz() (mhz string, err error) {
  • f := "/proc/cpuinfo" //被访问的文件路径,本质就是读取/proc/cpuinfo文件,然后做二次加工
  • var bs []byte
  • bs, err = ioutil.ReadFile(f)
  • if err != nil {
  • return
  • }
  • reader := bufio.NewReader(bytes.NewBuffer(bs))
  • for {
  • var lineBytes []byte
  • lineBytes, err = file.ReadLine(reader)
  • if err == io.EOF {
  • return
  • }
  • line := string(lineBytes)
  • if !strings.Contains(line, "MHz") {
  • continue
  • }
  • arr := strings.Split(line, ":")
  • if len(arr) != 2 {
  • return "", fmt.Errorf("%s content format error", f)
  • }
  • return strings.TrimSpace(arr[1]), nil
  • }
  • return "", fmt.Errorf("no MHz in %s", f)
  • }
  • //centos返回内核最大的文件句柄数
  • func KernelMaxFiles() (uint64, error) {
  • return file.ToUint64(Root() + "/proc/sys/fs/file-max") //被访问的文件路径
  • }
  • //centos返回内核已分配的文件句柄数
  • func KernelAllocateFiles() (ret uint64, err error) {
  • var content string
  • file_nr := Root() + "/proc/sys/fs/file-nr" //被访问的文件路径
  • content, err = file.ToTrimString(file_nr)
  • if err != nil {
  • return
  • }
  • arr := strings.Fields(content)
  • if len(arr) != 3 {
  • err = fmt.Errorf("%s format error", file_nr)
  • return
  • }
  • return strconv.ParseUint(arr[0], 10, 64)
  • }
  • //centos返回最大的进程号
  • func KernelMaxProc() (uint64, error) {
  • return file.ToUint64(Root() + "/proc/sys/kernel/pid_max") //被访问的文件路径
  • }
  • //centos返回平均负载情况
  • func LoadAvg() (*Loadavg, error) {
  • loadAvg := Loadavg{}
  • data, err := file.ToTrimString(Root() + "/proc/loadavg") //被访问的文件路径
  • if err != nil {
  • return nil, err
  • }
  • L := strings.Fields(data)
  • if loadAvg.Avg1min, err = strconv.ParseFloat(L[0], 64); err != nil {
  • return nil, err
  • }
  • if loadAvg.Avg5min, err = strconv.ParseFloat(L[1], 64); err != nil {
  • return nil, err
  • }
  • if loadAvg.Avg15min, err = strconv.ParseFloat(L[2], 64); err != nil {
  • return nil, err
  • }
  • processes := strings.SplitN(L[3], "/", 2)
  • if len(processes) != 2 {
  • return nil, errors.New("invalid loadavg " + data)
  • }
  • if loadAvg.RunningProcesses, err = strconv.ParseInt(processes[0], 10, 64); err != nil {
  • return nil, err
  • }
  • if loadAvg.TotalProcesses, err = strconv.ParseInt(processes[1], 10, 64); err != nil {
  • return nil, err
  • }
  • return &loadAvg, nil
  • }
展开

3.2 命令行获取

类UINX系统一般都有着类似的命令行,用于和系统进行直接交互。使用3.1节读取系统文件的方式,如读取上面/proc目录下的文件,如非对文件内容非常熟悉,往往不知道具体的数值含义,此时我们可以用平时常用的命令去取到易读性很高的内容。例如在centos中,我们可以调用某个成熟的包,利用"netstat -antup"命令快速获取所有连接信息;而在系统之上的应用层,这种方式也会有大量使用场景,比如调用成熟的包,利用“show status”,“show variables”这种命令获取MYSQL监控整体运行的各种指标和变量。

  • //centos获取socket信息
  • func SocketStatSummary() (m map[string]uint64, err error) {
  • m = make(map[string]uint64)
  • var bs []byte
  • bs, err = sys.CmdOutBytes("sh", "-c", "ss -s") //调用命令行,相当于输入“sh -c ss -s”命令
  • if err != nil {
  • return
  • }
  • reader := bufio.NewReader(bytes.NewBuffer(bs))
  • // ignore the first line
  • line, e := file.ReadLine(reader)
  • if e != nil {
  • return m, e
  • }
  • for {
  • line, err = file.ReadLine(reader)
  • if err != nil {
  • return
  • }
  • lineStr := string(line)
  • if strings.HasPrefix(lineStr, "TCP") {
  • left := strings.Index(lineStr, "(")
  • right := strings.Index(lineStr, ")")
  • if left < 0 || right < 0 {
  • continue
  • }
  • content := lineStr[left+1 : right]
  • arr := strings.Split(content, ", ")
  • for _, val := range arr {
  • fields := strings.Fields(val)
  • if fields[0] == "timewait" {
  • timewait_arr := strings.Split(fields[1], "/")
  • m["timewait"], _ = strconv.ParseUint(timewait_arr[0], 10, 64)
  • if len(timewait_arr) > 1 {
  • m["slabinfo.timewait"], _ = strconv.ParseUint(timewait_arr[1], 10, 64)
  • } else {
  • m["slabinfo.timewait"] = 0
  • }
  • continue
  • }
  • m[fields[0]], _ = strconv.ParseUint(fields[1], 10, 64)
  • }
  • return
  • }
  • }
  • return
  • }
  • ----------------------------分割线--------------------------
  • //MySQL获取status值、variable值
  • package main
  • import (
  • "fmt"
  • _ "github.com/go-sql-driver/mysql"
  • "github.com/jmoiron/sqlx"
  • )
  • var (
  • userName string = "root"
  • password string = "test123"
  • ipAddrees string = "12.16.0.11"
  • port int = 3306
  • dbName string = "test"
  • charset string = "utf8"
  • )
  • func connectMysql() (*sqlx.DB) {
  • dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
  • Db, err := sqlx.Open("mysql", dsn)
  • if err != nil {
  • fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
  • }
  • return Db
  • }
  • func main() {
  • var Db *sqlx.DB = connectMysql()
  • defer Db.Close()
  • result, _ := Db.Exec("show varibles")
  • fmt.Printf("%v \\n %v",result)
  • result, _ := Db.Exec("show varibles")
  • fmt.Printf("%v \\n %v",result)
  • }
展开

MySQL的show variables命令可以返回>500个变量的值,show status可以返回>400个状态值。这两个命令再配合对/proc/下MySQL进程所在文件夹的文件读取,即可完成80%以上的MySQL监控。

3.3 其他系统调用

本质上,3.1的读取文件、3.2的利用命令行也算系统调用。操作系统提供的其他调用可以在某些文档上查询到,还劳请读者自己去发现了。很多语言在实现的时候都有"os"这个包,里面封装了许多系统调用,我们可以利用这些封装的函数获取到很多系统信息,实现各层级的监控。

  • //centos获取系统变量
  • package nux
  • import (
  • "os"
  • "strings"
  • )
  • const nuxRootFs = "NUX_ROOTFS"
  • // Root 获取系统变量
  • func Root() string {
  • root := os.Getenv(nuxRootFs)
  • if !strings.HasPrefix(root, string(os.PathSeparator)) {
  • return ""
  • }
  • root = strings.TrimSuffix(root, string(os.PathSeparator))
  • if pathExists(root) {
  • return root
  • }
  • return ""
  • }
  • func pathExists(path string) bool {
  • fi, err := os.Stat(path)
  • if err == nil {
  • return fi.IsDir()
  • }
  • return false
  • }

4. 小结

  • 运维监控系统可按“有/无agent”、“使用pull/push获取数据”划分成6类。
  • Agent实际是一个轻量程序,用于提供系统无法直接提供的数据。
  • Pull相对复杂,Push相对简单,如果想从最基础的搭建起,选用push这种方式即可。
  • SNMP、SSH、HTTP、Syslog是常见的无agent获取数据方式,需要针对协议进行编程。
  • 使用Agent获取数据时,如果想自行编写Agent时,可以利用读取文件、命令行、其他系统调用来实现。
本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>