分类: LINUX
2008-07-02 09:29:29
存在许多可以监视 UNIX 服务器的方法。有关一些可用的监视类型的示例,请参见参考资料。监视简单的服务器并不是个问题,但是跨许多服务器监视相同信息可能带来问题。如果您负责的其中一个服务器的磁盘空间耗尽,您希望在该情况影响用户和客户端之前了解该情况。
以这种方式监视多个服务器,尤其是在那些服务器使用各种不同操作系统的情况下,可能是个问题。命令行工具、输出格式、值和其他信息之间的差异全都会使原本应该简单的过程变得复杂化。在这种情况下,所需要的是一个提供通用接口的解决方案,并且无论您在使用什么 UNIX 变体,该解决方案都应该有效。
简单网络管理协议 (SNMP) 提供了一种管理不同系统的信息的方法。代理运行在每个系统上,并使用 SNMP 向不同的管理系统报告信息。
SNMP 通常是诸如路由器和交换机等网络设备的内置组件,并且是可用于远程检索统计信息和状态信息的唯一方法。在大多数主机上,您需要显式运行 SNMP 软件,以通过 SNMP 协议公开有关该主机的信息。
可以通过使用 GET 请求来请求信息,从而显式地从代理检索信息,或者代理可以使用 TRAP 或 INFORM 消息将信息广播到管理系统。此外,管理系统可以设置代理上的信息和参数,但是这通常仅用于更改网络配置。
可共享的信息类型可能千差万别。这些信息可以是从网络接口的网络设置、统计信息和度量数据到监视 CPU 负载和磁盘空间的所有内容。
SNMP 标准没有定义代理返回什么信息;相反,可用的信息由管理信息库(Management Information Base,MIB)定义。MIB 定义了返回信息的结构,并使用对象标识符(object identifier,OID)组织为层次结构。可以通过使用 MIB 结构中的特定位置来请求数据,从而访问代理中的信息。
例如,清单 1 显示了一些较常见的 ID。
sysDescr.0 1.3.6.1.2.1.1.1.0 sysObjectId.0 1.3.6.1.2.1.1.2.0 sysUpTime.0 1.3.6.1.2.1.1.3.0 sysContact.0 1.3.6.1.2.1.1.4.0 sysName.0 1.3.6.1.2.1.1.5.0 sysLocation.0 1.3.6.1.2.1.1.6.0 sysServices.0 1.3.6.1.2.1.1.7.0 ifNumber.0 1.3.6.1.2.1.2.1.0 |
从此列表中可以看到,MIB 是用数字表示的,并且实际上是顺次排列的。在获得信息时,您可以使用 GET 请求来获得某个特定值,或者使用 GETNEXT 来获得上次读取的属性的下一个属性。还可以使用名称。上面显示的名称全都是系统树的一部分,因此可以使用 OID“system.sysUpTime.0”来通过 GET 请求读取值。
所读取的值还具有特定的类型。您可以读取被定义为“标量”的整型、浮点型和字符串值。这些对象中的内容是使用特定有效位来标识的类型。例如,时间间隔值以“时间单位数”(或百分之一秒数)的形式来报告。在显示这些值之前,需要将其转换为更便于阅读的形式。还存在返回表型数据的 MIB 对象。这是通过返回附加的 OID 实例来处理的,可以将这些实例分组在一起以形成 SNMP 表格。
从安全的角度看,可以将 SNMP 代理与特定社区相关联,并且管理系统使用社区作为验证其代理访问权限的方法来访问信息。在 SNMP 标准的版本 1 中,社区字符串是保护或限制访问的唯一方法。对于 SNMP 标准的版本 2,安全性得到了改进,但是处理起来可能非常复杂。对于版本 3(自从 2004 年以来的最新版本),该标准已通过显式身份验证和访问控制系统进行了改进。
存在着许多从 SNMP 系统获得信息的方法,包括使用专业的管理工具、编程接口和命令行工具。
对于其中的最后一种方法,也许最著名和最容易的是使用 snmpwalk 命令,此命令是 SNMP 工具套件的一部分,该套工具允许您直接从命令行获得 SNMP 代理中的信息。此命令将遍历某个给定的管理值的整个子树,并返回在该子树中获得的有关系统的所有信息。
例如,清单 2 显示了在查询某个本地系统中的“system”树中的所有信息时的输出。
$ snmpwalk -Os -c MCSLP -v 1 localhost system sysDescr.0 = STRING: Linux tweedledum 2.6.23-gentoo-r8 #1 SMP Tue Feb 12 16:32:14 GMT 2008 x86_64 sysObjectID.0 = OID: netSnmpAgentOIDs.10 sysUpTimeInstance = Timeticks: (34145553) 3 days, 22:50:55.53 sysContact.0 = STRING: root@Unknown sysName.0 = STRING: tweedledum sysLocation.0 = STRING: serverroom sysORLastChange.0 = Timeticks: (0) 0:00:00.00 sysORID.1 = OID: snmpFrameworkMIBCompliance sysORID.2 = OID: snmpMPDCompliance sysORID.3 = OID: usmMIBCompliance sysORID.4 = OID: snmpMIB sysORID.5 = OID: tcpMIB sysORID.6 = OID: ip sysORID.7 = OID: udpMIB sysORID.8 = OID: vacmBasicGroup sysORDescr.1 = STRING: The SNMP Management Architecture MIB. sysORDescr.2 = STRING: The MIB for Message Processing and Dispatching. sysORDescr.3 = STRING: The management information definitions for the SNMP User-based Security Model. sysORDescr.4 = STRING: The MIB module for SNMPv2 entities sysORDescr.5 = STRING: The MIB module for managing TCP implementations sysORDescr.6 = STRING: The MIB module for managing IP and ICMP implementations sysORDescr.7 = STRING: The MIB module for managing UDP implementations sysORDescr.8 = STRING: View-based Access Control Model for SNMP. sysORUpTime.1 = Timeticks: (0) 0:00:00.00 sysORUpTime.2 = Timeticks: (0) 0:00:00.00 sysORUpTime.3 = Timeticks: (0) 0:00:00.00 sysORUpTime.4 = Timeticks: (0) 0:00:00.00 sysORUpTime.5 = Timeticks: (0) 0:00:00.00 sysORUpTime.6 = Timeticks: (0) 0:00:00.00 sysORUpTime.7 = Timeticks: (0) 0:00:00.00 sysORUpTime.8 = Timeticks: (0) 0:00:00.00 |
从这里可以看到一系列有关该主机的信息,包括操作系统(在 sysDescr.0
中)、系统已正常运行的时间(sysUpTimeInstance
),以及该计算机的位置。这里同时以原始值 (Timeticks) 和转换后的可读值“小时:分钟:秒”的形式显示了间隔时间。
计算机的正常运行时间或可用性是非常常见的 SNMP 用途,并提供了也许是用于确定计算机是否在正常运行并处理请求的最方便和最高效的方法。在本系列以前的各个部分中描述的其他解决方案包括 ping 或使用 rwho 和 ruptime。后两个解决方案占用非常多的 CPU 和网络带宽,并且在资源利用方面不是非常友好。
但是请注意,这里描述的正常运行时间(即 SNMP 代理的正常运行时间中显示的信息)不是整个计算机的正常运行时间。两者在大多数情况下是相同的,尤其是对于内置了 SNMP 监视的设备,例如网络路由器和交换机。对于通过 SNMP 公开其状态的计算机,系统与 SNMP 代理正常运行时间之间可能存在偏差。
可以使用 snmpstatus 来通过 SNMP 更快速地了解计算机的状态。这将从指定的 SNMP 代理获得许多数据点,包括 IP 地址、描述、正常运行时间和网络统计数据(已发送/接收的数据包和已发送/接收的 IP 数据包)。例如,如果我们在 Solaris 主机上,则可以看到简化的信息,如清单 3 所示。
$ snmpstatus -v1 -c public t1000 [192.168.0.26]=>[SunOS t1000 5.11 snv_81 sun4v] Up: 2:12:10.20 Interfaces: 4, Recv/Trans packets: 643/160 | IP: 456/60 2 interfaces are down! |
该计算机最近已重新启动过(因而具有很低的正常运行时间和数据包统计数据)。snmpstatus 命令还确定该计算机上的其中两个接口(该计算机具有四个以太网端口)已关闭。这是 SNMP 所能提供的警告信息种类的很好示例,这些信息帮助通知您需要进一步调查的问题。
若要获得某个特定的信息部分,您可以使用 snmpget 命令,该命令直接读取一个或多个 OID,并报告它们的值。对于特殊类型,该命令还将其转换为可读的格式。例如,要获取系统描述和正常运行时间,可以使用以下命令(在清单 4 中)。
$ snmpget -v1 -c public t1000 system.sysUpTime.0 system.sysContact.0 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (867411) 2:24:34.11 SNMPv2-MIB::sysContact.0 = STRING: "System administrator" |
孤立地看,所有这些方法都是非常有用的,但实际上,您需要监视和跟踪多台计算机和多个 OID,以了解全面的情况。这可以通过使用多个可编程的 SNMP 接口之一来实现。
用于 Perl 的 Net::SNMP
模块使用 SNMP 从一个或多个代理获得信息。类似地,其他语言可以使用其他接口,包括 Python、Ruby 和 PHP(请参见参考资料)。接口的工作方式是创建一个与所需主机上的 SNMP 代理通信(并在必要时进行身份验证)的会话。一旦拥有了活动和有效的会话,您就可以直接从代理请求一个或多个 OID 的数据。信息以绑定 OID 与对应值的信息散列的形式返回。
清单 5 显示了一个非常简单的脚本,此脚本将获得命令行上提供的每个主机的系统正常运行时间。
#! /usr/local/bin/perl use strict; use Net::SNMP; my $uptimeOID = '1.3.6.1.2.1.1.3.0'; foreach my $host (@ARGV) { my ($session, $error) = Net::SNMP->session( -hostname => $host, -community => 'public', -port => 161 ); warn ("ERROR for $host: $error\n") unless (defined($session)); my $result = $session->get_request( -varbindlist => [$uptimeOID] ); if (!defined($result)) { warn ("ERROR: " . $session->error . "\n"); } else { printf("Uptime for %s: %s\n",$host, $result->{$uptimeOID}); } $session->close; } |
在该脚本中,我们提供了系统的完整数字 OID、sysUpTime 属性。在使用 get_request()
方法作为对数组的引用时,您必须提供要获得的 OID 的列表,然后从返回的散列中提取信息。在清单 5 中,我们在调用期间动态地构建了数组引用,然后在打印结果时使用 OID 作为散列键。
使用该脚本,我们可以获取命令行上提供的每个主机的正常运行时间列表(请参见清单 6)。
$ perl uptime.pl tweedledum t1000 Uptime for tweedledum: 4 minutes, 52.52 Uptime for t1000: 6 minutes, 26.12 |
当然,人工观察此信息是非常低效的。
一次查看某个 SNMP OID 属性的单个实例并不非常具有实际意义。通常,您希望监视某些属性(例如可用性)在一段时间内的情况,或者希望监视特定值的变化情况。一个很好的示例就是磁盘空间。可以配置 SNMP 以记录所有类型的信息,其中磁盘空间是您希望监视的常见系统,以便不仅能够确定磁盘空间何时达到某个特定的级别,而且还可以确定何时磁盘空间存在重大变化(这可能指示某个问题)。
例如,清单 7 显示了一个基于回调的解决方案,以连续监视磁盘空间。在该脚本中,我们输出了累计总数,但是可以配置该脚本仅输出存在磁盘空间减少时触发的警告消息。
#! /usr/local/bin/perl use strict; use warnings; use Net::SNMP qw(snmp_dispatcher); my $diskspaceOID = '1.3.6.1.4.1.2021.9.1.7.1'; foreach my $host (@ARGV) { my ($session, $error) = Net::SNMP->session( -hostname => $host, -nonblocking => 0x1, ); if (!defined($session)) { warn "ERROR: $host produced $error - not monitoring\n" } else { my ($last_poll) = (0); $session->get_request( -varbindlist => [$diskspaceOID], -callback => [ \&diskspace_cb, \$last_poll ] ); } } snmp_dispatcher(); exit 0; sub diskspace_cb { my ($session, $last_poll) = @_; if (!defined($session->var_bind_list)) { printf("%-15s ERROR: %s\n", $session->hostname, $session->error); } else { my $space = $session->var_bind_list->{$diskspaceOID}; if ($space < ${$last_poll}) { my $diff = ((${$last_poll}-$space)/${$last_poll})*100; printf("WARNING: %s has lost %0.2f%% diskspace)\n", $session->hostname,$diff); } printf("%-15s Ok (%s)\n", $session->hostname, $space ); ${$last_poll} = $space; } $session->get_request( -delay => 60, -varbindlist => [$diskspaceOID] ); } |
该脚本分为两个部分,并使用了 Net::SNMP
模块(此模块允许您在从主机获得 SNMP 值时调用一个函数)中的一些功能,同时结合了在简单但高效的循环中连续地监视主机和 SNMP 对象的方法。
第一部分设置每个主机以监视信息。我们仅监视其中的一个信息部分,但是可以作为该解决方案的一部分来监视其他信息部分。该对象被配置为“非阻塞的”,以便脚本将不会在无法访问主机时等待,而是简单地继续到下一个主机。最后,在对 get_request()
的调用中,我们提交了回调信息。这里的第一个参数是要在从代理接收到响应时调用的函数的名称。第二个参数是将在调用该函数时应用于该函数的参数。
我们使用此参数是为了能够记录和跟踪由 SNMP 调用返回的前一个值。在回调函数中,我们将新返回的值与以前一个值进行了比较。如果存在减少,则计算减少百分比,然后报告一个警告。
该回调的最后一部分是指定应该进行另一次检索,并在这里指定下一次检索应该延迟 60 秒。现有的回调信息将保留。实际上,该脚本从 SNMP 代理获得值,调用回调函数,然后回调函数把将来的另一次检索排入队列。由于已经定义了同一个回调,该过程将在一个无限循环中重复。
顺便提一下,该脚本使用 dskAvail OID 值,并基于上一个值和新的值来计算差异百分比。此属性所在的 dskTable 树实际上有一个磁盘百分比属性,我们原本应该查询此属性而不是手动对其进行计算。然而,返回的值也许没有足够细的粒度,从而不适用。
通过使用 snmpwalk 来输出 dskTable 树(此树本身是 UCD MIB 的一部分),您可以看到此属性和当前值(请参见清单 8)。
$ snmpwalk -v 1 localhost -c public UCD-SNMP-MIB::dskTable UCD-SNMP-MIB::dskIndex.1 = INTEGER: 1 UCD-SNMP-MIB::dskPath.1 = STRING: / UCD-SNMP-MIB::dskDevice.1 = STRING: /dev/sda3 UCD-SNMP-MIB::dskMinimum.1 = INTEGER: 100000 UCD-SNMP-MIB::dskMinPercent.1 = INTEGER: -1 UCD-SNMP-MIB::dskTotal.1 = INTEGER: 72793272 UCD-SNMP-MIB::dskAvail.1 = INTEGER: 62024000 UCD-SNMP-MIB::dskUsed.1 = INTEGER: 7071512 UCD-SNMP-MIB::dskPercent.1 = INTEGER: 10 UCD-SNMP-MIB::dskPercentNode.1 = INTEGER: 3 UCD-SNMP-MIB::dskErrorFlag.1 = INTEGER: noError(0) UCD-SNMP-MIB::dskErrorMsg.1 = STRING: |
要在第一时间找到该属性,您可以使用 snmptranslate 来转储所有已知的属性。通过使用 grep 筛选转储结果,我们可以看到以下所需的信息: $ snmptranslate -Ts |grep dsk
。
要获取数字值,可以使用 snmptranslate 并使用 –On 选项来提供名称(请参见清单 9)。
$ snmptranslate -On UCD-SNMP-MIB::dskAvail .1.3.6.1.4.1.2021.9.1.7 |
运行该脚本,我们将获取指定主机上的磁盘空间使用累计情况(以及警告)。请参见清单 10。
$ perl diskspace-auto.pl tweedledum tweedledum Ok (50319024) WARNING: tweedledum has lost 2.67% diskspace) tweedledum Ok (48976392) WARNING: tweedledum has lost 1.65% diskspace) tweedledum Ok (48166292) tweedledum Ok (48166292) tweedledum Ok (48166292) tweedledum Ok (48166292) |
从这个输出中可以看到,指定主机上的该磁盘损失了大量的可用空间。要监视更多主机,只需在命令行上添加更多主机名称。
SNMP 软件包包括一个守护进程 snmpd,可以配置此守护进程使用 SNMP 协议来公开各种各样的信息。要公开的信息的配置使用 /etc/snmpd.conf 文件进行控制。
例如,清单 11 显示了在本文前面的示例中使用的主机上的 snmpd.conf 文件。
syslocation serverroom proc imapd 20 10 disk / 100000 load 5 10 10 |
此文件中的每一行填充了不同的信息。在此示例中,我们设置了计算机的位置,然后配置了一些要监视的特定项目。
proc 部分监视某个特定进程,这里显示的是用于邮件服务的 IMAP 守护进程的监视器。该选项后面的数字指定允许运行的最大进程数量,以及应该运行的最小进程数量。可以使用这个部分来确保某个特定的服务正在运行,并且没有超出可能指示错误的容量。当进程计数超过 MAX 值时,则会生成 SNMP 消息。
对于磁盘,您将指定要监视的目录的路径,以及磁盘应该具有的最小空闲空间大小(以 KB 为单位)。同样,如果磁盘空间降至此值以下,则会触发 SNMP 消息的生成。
最后,负载信息显示了应该报告的 1、5 和 15 分钟中的 CPU 的最大负载。这等效于 uptime 命令的输出,它显示了在这些时间间隔内的进程负载。与其他已配置的限制一样,超过这些限制时会引发 SNMP 消息。
手动设置此信息并不是非常困难,但也不是非常理想。如果需要设置用于该配置的更简单方法,可以使用基于菜单的简单解决方案 snmpconf。
在集群节点上用下面的命令检查MIB:
#snmpwalk localhost -v 1 -c public |
这个命令应产生一个很长的报告显示集群节点上的MIB,象征本地SNMP代理(snmpd)响应了查询。
我们用这个命令查询一些有用的信息:
#snmpwalk -v 1 localhost -c public .1.3.6.1.4.1.2021.9 |
注意:在这个命令中,我们使用了一串数值代替符号名定位SNMP管理信息库(MIB)中的信息,为了查找这个数字对象标识符代表的符号名或文本,使用:
#snmptranslate .1.3.6.1.4.1.2021.9 |
如果你象配置文件示例那样设置了磁盘警报参数的话,你会看到:
enterprises.ucdavis.dskTable.dskEntry.dskIndex.1 = 1 |
注意:在这个磁盘空间SNMP MIB报告中你会看到加州大学的名字,因为这就是它们开发的。
特别要注意dskEntry.dskErrorFlag 行(已经加粗显示),这些行指出磁盘是低于(值0)或高于(值1)你在snmp.conf中指定的阀值,花点时间修改阀值(以M为单位)比磁盘分区的剩余空间(使用df -m命令检查)还小的值,然后输入:
#service snmpd restart |
或
#/etc/init.d/snmpd restart |
注意:kill HUP snmpd守护进程的PID能达到同样的目的。
然后再次输入相同的snmpwalk命令:
#snmpwalk -v 1 localhost -c public .1.3.6.1.4.1.2021.9 |
你应该看到错误标志指示磁盘分区已经用光了磁盘空间。