利用脚本扩展snmp收集信息

来源:互联网 发布:淘宝优惠券发放软件 编辑:程序博客网 时间:2024/06/09 13:55
最近需要开发一个适用于自己集群的监控软件,在调研了ganlia等监控工具后,决定采用snmp+rrd+MySQL的方式来完成节点数据的采集和存储工作。以前没有接触过snmp,简单记录一下开发过程,下文主要会涉及:
  • snmpd.conf简单配置
  • 通过脚本方式扩展agent采集自身所需要的信息
  • 一个基于snmp4j的snmp manager实现
其实snmp还有一个很重要的trap功能,个人理解是可以完成主动汇报(报警)工作,但暂时没有用到。

有关snmp的介绍不想多说,它本身只是一个协议,在各操作系统平台下都有自己的实现。由于集群都是Linux系统,所以选择了最流行的net-snmp,各个Linux版本的软件库中应该都会包含(其实windows下面也有)。软件本身也可分为agent(snmpd包)和manager(snmp包)两部分。顾名思义,snmpd作为deamon,应该部署到被监控的节点上;而manager则包含了一系列snmpget、snmpset、snmpwalk等工具,应该被安装在监控服务器上。由于我们会用snmp4j来进行信息收集和一些配置工作(它本身就能提供snmpget、snmpset等功能),因此从理论上来讲,snmp这个包可有可无,但为了方便调试,一般还是会装上。

在安装完snmpd之后,首要任务是修改/etc/snmp/snmpd.conf配置。默认的配置文件会给出很多的注释和参考示例,很清晰。总结来说需要修改或添加以下项:

agentAddress udp:161,udp6:[::1]:161 ####### 为了能在非本地访问,监听所有161端口view   all❘-included   .1 ####### 添加一个名为all的可以访问.1入口下所有MIB库的组rwcommunity test default -V all ####### 添加一个对于组all具有读写权限的用户名testextend-sh disk_info_sda1 /bin/bash /tmp/script/disk_info.sh sda1 ####### 扩展一个名为disk_info_sda1的项,执行所给地址的脚本,并以sda1为参数,结果为脚本的输出值

注意修改完后要重启一下snmpd服务。snmp协议的3个版本,只有v3才提供了安全方面的功能,也可以通过这个文件配置,这里我们先略去不谈。

snmp以MIB库的形式来管理数据。就像是一棵树,其每个叶子节点所对应的OID都可以用来访问某个特定的数据项。可以利用snmpget命令来获取某个OID对应的值,也可以用snmpwalk来遍历以给定节点为根的子树,输出其包含的所有数据项。snmp本身为我们提供了很多可以获取的有用的数据,可以通过http://www.oidview.com/mibs/detail.html这个网站进行查询,但有时候,这些数据格式或内容可能与我们想要的不一致,这时候就要用到agent扩展。net-snmp本身提供了多种扩展方式,我们仅用到了最简单的脚本方式(上述已经配置过),新添加的扩展都会被添加到NET-SNMP-EXTEND-MIB(1.3.6.1.4.1.8072.1.3.2)这个入口之下。我们通过遍历命令,可以得到如下结果(假设只有一个disk_info_sda1扩展项):

snmpwalk -v 2c -c  test  localhost 1.3.6.1.4.1.8072.1.3.2NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 1NET-SNMP-EXTEND-MIB::nsExtendCommand."disk_info_sda1" = STRING: /bin/bashNET-SNMP-EXTEND-MIB::nsExtendArgs."disk_info_sda1" = STRING: /tmp/script/disk_info.sh sda1NET-SNMP-EXTEND-MIB::nsExtendInput."disk_info_sda1" = STRING: NET-SNMP-EXTEND-MIB::nsExtendCacheTime."disk_info_sda1" = INTEGER: 5NET-SNMP-EXTEND-MIB::nsExtendExecType."disk_info_sda1" = INTEGER: shell(2)NET-SNMP-EXTEND-MIB::nsExtendRunType."disk_info_sda1" = INTEGER: run-on-read(1)NET-SNMP-EXTEND-MIB::nsExtendStorage."disk_info_sda1" = INTEGER: permanent(4)NET-SNMP-EXTEND-MIB::nsExtendStatus."disk_info_sda1" = INTEGER: active(1)NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."disk_info_sda1" = STRING: 32,169NET-SNMP-EXTEND-MIB::nsExtendOutputFull."disk_info_sda1" = STRING: 32,169NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."disk_info_sda1" = INTEGER: 1NET-SNMP-EXTEND-MIB::nsExtendResult."disk_info_sda1" = INTEGER: 0NET-SNMP-EXTEND-MIB::nsExtendOutLine."disk_info_sda1".1 = STRING: 32,169

由于本机在/usr/share/mibs/netsnmp下有MIB定义文件NET-SNMP-EXTEND-MIB的存在,walk工具会自动将那一连串难记的数字转换为可读的名称。当然,也可以通过-O n参数显示原始的数字:

 snmpwalk -v 2c -c test -O n  localhost 1.3.6.1.4.1.8072.1.3.2.1.3.6.1.4.1.8072.1.3.2.1.0 = INTEGER: 1.1.3.6.1.4.1.8072.1.3.2.2.1.2.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = STRING: /bin/bash.1.3.6.1.4.1.8072.1.3.2.2.1.3.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = STRING: /tmp/script/disk_info.sh sda1.1.3.6.1.4.1.8072.1.3.2.2.1.4.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = STRING: .1.3.6.1.4.1.8072.1.3.2.2.1.5.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: 5.1.3.6.1.4.1.8072.1.3.2.2.1.6.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: shell(2).1.3.6.1.4.1.8072.1.3.2.2.1.7.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: run-on-read(1).1.3.6.1.4.1.8072.1.3.2.2.1.20.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: permanent(4).1.3.6.1.4.1.8072.1.3.2.2.1.21.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: active(1).1.3.6.1.4.1.8072.1.3.2.3.1.1.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = STRING: 32,169.1.3.6.1.4.1.8072.1.3.2.3.1.2.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = STRING: 32,169.1.3.6.1.4.1.8072.1.3.2.3.1.3.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: 1.1.3.6.1.4.1.8072.1.3.2.3.1.4.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 = INTEGER: 0.1.3.6.1.4.1.8072.1.3.2.4.1.2.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49.1 = STRING: 32,169

名称和数字有些类似于域名和IP地址的关系。这里顺便给出disk_info.sh的脚本如下:

#!/bin/bashif [ $# -lt 1 ]; then    echo "Usage: disk_info [disk name]";    exit 1;fidiskName=$1disk_info=`df -m | grep $diskName' '|awk '{print $1" "$2" "$3" "$4" "$5" "$6" "}'`used=`echo $disk_info|awk '{print $3}'`available=`echo $disk_info|awk '{print $4}'`if [ -z "$used" ]then    used='0'fiif [ -z "$available" ]then    available='0'fiecho $used","$available

很简单,它的作用就是输出给定磁盘的已用空间和剩余空间,以MB为单位,中间用逗号隔开。上文中NET-SNMP-EXTEND-MIB::nsExtendOutputFull."disk_info_sda1" = STRING: 32,169 就是示例输出。换句话说,我们如果想随时获取节点sda1磁盘的空间情况,只需要执行snmpget -v 2c -c test localhost 1.3.6.1.4.1.8072.1.3.2.3.1.1.14.100.105.115.107.95.105.110.102.111.95.115.100.97.49 即可。

由于开发语言选用的Java,所以找到了一个名为snmp4j(http://www.snmp4j.org/)的snmp的另一个实现,它提供了很多API可供我们使用。与net-snmp类似,snmp4j也有agent和manager一说,这里我们将只用到它的manager。给出一个对snmp4j的API进行封装后的SNMPManager实现如下:

import java.io.IOException;import java.util.LinkedList;import java.util.List;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.snmp4j.CommunityTarget;import org.snmp4j.PDU;import org.snmp4j.Snmp;import org.snmp4j.Target;import org.snmp4j.TransportMapping;import org.snmp4j.event.ResponseEvent;import org.snmp4j.mp.SnmpConstants;import org.snmp4j.smi.Address;import org.snmp4j.smi.GenericAddress;import org.snmp4j.smi.Integer32;import org.snmp4j.smi.OID;import org.snmp4j.smi.OctetString;import org.snmp4j.smi.UdpAddress;import org.snmp4j.smi.Variable;import org.snmp4j.smi.VariableBinding;import org.snmp4j.transport.DefaultUdpTransportMapping;import org.snmp4j.util.DefaultPDUFactory;import org.snmp4j.util.TreeEvent;import org.snmp4j.util.TreeUtils;import util.OIDUtil;/** * A snmp4j based SNMP Manager *  * @author grandland.xccui *  */public class SNMPManager {private static final Logger LOG = LoggerFactory.getLogger(SNMPManager.class);private Snmp snmp;private CommunityTarget target;private Address address;private String community;/** *  * @param addrStr *            the agent address * @param port *            the agent port * @param community *            the community name for access */public SNMPManager(String addrStr, int port, String community) {this.community = community;this.address = GenericAddress.parse(addrStr + "/" + port);this.target = null;}/** * Start the SNMP manager *  * @throws IOException */public void start() throws IOException {TransportMapping<UdpAddress> transportMapping = new DefaultUdpTransportMapping();snmp = new Snmp(transportMapping);snmp.listen();}/** * Walk MIB tree with the given oid as the root *  * @param oid * @return * @throws IOException */public List<VariableBinding> walkAsList(String oid) throws IOException {TreeUtils treeUtils = new TreeUtils(snmp, new DefaultPDUFactory());List<TreeEvent> events = treeUtils.getSubtree(getTarget(), new OID(oid));LinkedList<VariableBinding> resultList = new LinkedList<VariableBinding>();for (TreeEvent event : events) {if (event != null) {if (event.isError()) {LOG.warn("oid [" + oid + "] " + event.getErrorMessage());}VariableBinding[] varBindings = event.getVariableBindings();if (varBindings != null && varBindings.length > 0) {for (VariableBinding vb : varBindings) {resultList.add(vb);}}}}return resultList;}/** * Get values for each oid in the given array *  * @param oids * @return * @throws IOException */public List<VariableBinding> getAsList(String... oids) throws IOException {List<VariableBinding> resultList = new LinkedList<VariableBinding>();PDU pdu = new PDU();pdu.setType(PDU.GET);for (String oid : oids) {pdu.add(new VariableBinding(new OID(oid)));}ResponseEvent event = snmp.get(pdu, getTarget());if (null != event) {PDU response = event.getResponse();if (null != response) {int length = response.size();for (int i = 0; i < length; i++) {resultList.add(response.get(i));}}LOG.debug(resultList.toString());}return resultList;}/** * Set integer value for the given oid *  * @param oid * @param value * @return */public boolean setInteger(String oid, int value) {try {return set(oid, new Integer32(value));} catch (IOException e) {e.printStackTrace();return false;}}/** * Set string value for the given oid *  * @param oid * @param value * @return */public boolean setString(String oid, String value) {try {return set(oid, new OctetString(value));} catch (IOException e) {e.printStackTrace();return false;}}/** * Close the snmp manager *  * @throws IOException */public void close() throws IOException {snmp.close();}private boolean set(String oid, Variable variable) throws IOException {VariableBinding binding = new VariableBinding(new OID(oid), variable);PDU pdu = new PDU();pdu.setType(PDU.SET);pdu.add(binding);ResponseEvent event = snmp.set(pdu, getTarget());if (event.getResponse().getErrorStatus() != PDU.noError) {LOG.warn("oid [" + oid + "] variable [" + variable.toString()+ "] " + event.getResponse().getErrorStatusText());return false;}return true;}private synchronized Target getTarget() {if (null != target) {return target;}target = new CommunityTarget();target.setCommunity(new OctetString(community));target.setAddress(address);target.setRetries(2);target.setTimeout(1500);target.setVersion(SnmpConstants.version2c);return target;}}

其中还用到了一个OIDUtil工具:

public class OIDUtil {public static final String NET_SNMP_EXTEND_BASE = "1.3.6.1.4.1.8072.1.3.2.";public static final String NET_SNMP_EXTEND_nsExtendCacheTime_BASE = NET_SNMP_EXTEND_BASE+ "2.1.5.";public static final String NET_SNMP_EXTEND_nsExtendOutputFull_BASE = NET_SNMP_EXTEND_BASE+ "3.1.2.";public static String toOIDSeq(String str) {StringBuilder sb = new StringBuilder();int length = str.length();for (int i = 0; i < length; i++) {sb.append("." + (int) str.charAt(i));}return sb.toString();}public static String genCacheTimeOID(String name) {return NET_SNMP_EXTEND_nsExtendCacheTime_BASE+ name.length()+ toOIDSeq(name);}public static String genOutputFullOID(String name) {return NET_SNMP_EXTEND_nsExtendOutputFull_BASE + name.length()+ toOIDSeq(name);}}

这个工具类的作用是根据给定的扩展名称,也就是上文中的disk_info_sda1,来生成其对应的缓存时间项和输出结果项的OID,免去了指定那一长串数字的麻烦(OID的生成方式是一段base+名称长度+名称每一位对应的ASCII值)。当然,这里没有进行再深入的研究,snmp4j应该也能支持直接通过MIB表内的名称进行访问。

为什么还需要缓存时间这一项呢?实际中发现,为了提高效率,net-snmp的agent不会每次都去执行脚本,而是会将上次结果缓存某个时间,默认是5秒。在这段时间之内无论你访问多少次,结果都是一样的。一些对实时性要求比较高的信息,如CPU使用率等,如果访问间隔小于缓存时间,得到的结果就会很难看。所以每次在开始定期收集数据之前,需要利用SNMPManager里的setInteger方法,来设置一个小于采集间隔的缓存时间。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宝宝九个月了不爱吃辅食怎么办 八个月宝宝不喜欢吃辅食怎么办 小孩米粉吃多了怎么办 宝宝四个月了奶水不足怎么办 4个月奶水不足怎么办 孩子不吃奶粉母乳又不够怎么办 宝宝吃母乳上火了怎么办 5个月宝宝厌奶期怎么办 九个月宝宝不吃奶粉怎么办 第5个月奶不够吃怎么办 九个月的宝宝不吃奶粉怎么办 9个月宝宝不肯吃怎么办 11个月不吃辅食怎么办 4个月母乳不足怎么办 宝宝四个月奶不够怎么办 四个月宝宝奶不够吃怎么办 宝宝吃母乳偏瘦怎么办 宝宝吃母乳很瘦怎么办 8个月宝宝流汗太多怎么办 奶水多乳房胀疼怎么办 乳房胀奶奶水减少怎么办 宝宝五个月奶水不够吃怎么办 梦见鬼在梦里怎么办 宝宝晚上奶水不够吃怎么办 十个月晚上奶水不够吃怎么办 产妇晚上奶水不够吃怎么办 刚出生的宝宝不吃母乳怎么办 宝宝六个月奶不够吃怎么办 六个月奶不够吃怎么办 刚出生奶不够吃怎么办 做梦醒了看见鬼怎么办 宝宝到陌生地方哭闹怎么办 大人生病住院小孩没人带怎么办 孕妇被小猫抓了怎么办 怀孕了家里有猫怎么办 厕所被湿纸巾堵了怎么办 5天新生儿不拉屎怎么办 4月宝宝不拉屎怎么办 两岁宝宝晚上睡觉哭闹怎么办 2月婴儿吐奶很多怎么办 心情不好回奶了怎么办