Munin—渡鸦报告
长期监控工具 Munin 在挪威开发,其名称源自北欧神话。向奥丁神报告世界消息的两只渡鸦之一被称为 Munin,另一只名为 Hugin。Munin 是“记忆”,而 Hugin 是“思想”。
Munin 长期监控套件的酷之处在于,它不局限于仅监控典型的系统和网络参数。例如,您可以轻松地帮市场部门的同事一个忙,为他们监控产品销售额。您只需用您选择的语言编写一个脚本或程序,以以下形式返回被监控参数的当前值<parameter>.value <value>(以换行符结尾)在标准输出上。
要理解 Munin 的实际工作原理有点不太容易。这是因为较早的官方文档(包括用 Perl 编写的源代码)声称 Munin 是一个客户端-服务器应用程序,这引起了很多困惑。最近,Munin 开发人员将其称为具有主节点架构,这更为恰当。
要使用监控软件,您需要一台托管 Web 服务器并安装了 Tobias Oetiker 的 RRDtool 的机器。在这台计算机上,您运行 Munin 中心,即 Munin 主节点。Debian 和 Ubuntu 用户可以在 munin 软件包中找到它。它由一组 Perl 脚本组成,这些脚本由 cron 守护进程以五分钟的间隔运行。这些脚本从多个 Munin 节点收集数据,将它们存档到循环数据库 (RRD) 中,生成图表并更新呈现它们的网页。其中一个名为 munin-limits 的脚本还会警告违反限制的值(如果已相应配置)。
RRD 的优点是尺寸永远不会改变。随着时间的推移,较旧的数据会被压缩并以较少的细节存储,直到最终完全淘汰。Munin 使用 RRD 存储过去 48 小时的数据,分辨率为五分钟。过去十天的平均值以半小时的分辨率存储;过去 46 天的平均值以两小时的分辨率存储,过去 449 天的平均值以一天的分辨率存储。
Munin 主节点读取通常放置在 /etc/ 中的 munin.conf 配置文件(在 Debian/Ubuntu 下,为 /etc/munin/munin.conf),以找出从哪里请求数据以及它应该以哪个(主机)名出现在 Web 界面中。一个典型的配置条目如下所示
[Airport;localhost.localdomain] address 127.0.0.1 use_node_name yes
在方括号中,您放入要监控的机器的名称(此处为 localhost.localdomain)。如果您使用完全限定域名 (FQDN),Munin 会自动将此机器呈现为以给定域名部分命名的组的成员。如果您更喜欢使用自己的组,请在方括号中的机器名称前添加相关的组名称(此处为 Airport;结果如图 1 所示)。
确保您使用分号作为分隔符,前后没有任何空格,并在开始监控之前决定组名。这将使您的生活更轻松,因为 Munin 使用组名作为包含循环数据库和图表的目录的名称(在我们的示例中,在 Ubuntu 系统上,为 /var/www/munin/Airport/)。
数据库和图表的文件名包含方括号中给出的主机名。如果您之后更改方括号的内容,请确保相应地更改文件和目录名称(并在下一个五分钟间隔结束之前);否则,Munin 将使用新的空 RRD,您可能会丢失数据。
使用 address 参数指定 Munin 主节点从中获取相关数据的 Internet 地址。这允许您使用中间从属机器从实际目标机器收集数据,而不会在 Munin Web 界面中给出误导性信息。
乍一看似乎使事情复杂化的是一个非常有用的功能,因为它允许您将 Munin 协议的纯文本通信限制为受信任的机器。此外,您并非总是有机会在实际目标机器上安装 Munin 节点软件。例如,如果 Munin 节点通过 SNMP 收集数据,则就是这种情况。在这种情况下,您必须将 use_node_name 参数设置为 no。
在每个作为 address 参数的值给出的机器上,您都需要安装 Munin 节点软件;否则,Munin 主节点生成的网页将保持为空。在 Debian/Ubuntu 系统上,相关的软件包称为 munin-node。
Munin 节点由一个守护进程组成,该守护进程在收到 Munin 主节点的请求时,会启动负责收集其特定类型数据的插件。它的配置文件 munin-node.conf 存储在与 munin.conf 相同的目录中;如果您的 Munin 主节点在也充当节点的机器上运行,请不要混淆这两个文件。munin-node.conf 定义了日志文件和日志级别、要使用的端口(通常为 4949),以及最重要的,允许连接到守护进程的机器
allow ^127\.0\.0\.1$
在这种情况下,正则表达式^127\.0\.0\.1$将访问限制为在同一机器 localhost 上运行的 Munin 主节点。
从发行版软件包安装 Munin 节点通常会激活一系列可以自动配置的插件。在这种情况下,Telnet 连接到节点机器的端口 4949 将为您提供概述
$ telnet localhost 4949 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. $ munin node at extrablatt.trish.de help $ Unknown command. Try list, nodes, config, fetch, version or quit list open_inodes if_err_eth0 irqstats entropy processes postfix_mailqueue if_eth0 df netstat interrupts swap load cpu df_inode if_eth1 if_err_eth1 postfix_mailvolume forks iostat open_files memory vmstat fetch open_inodes used.value 67839 max.value 68094 . Connection closed by foreign host.
list 命令返回此特定节点上所有已激活插件的名称。fetch 命令,以您要运行的插件的名称作为参数,返回插件监控的参数的值(在本例中为 used 和 max)。只要您不更改 munin-node.conf 中的 timeout 指令(例如,更改为 20 秒timeout 20),您必须快速输入命令,因为守护进程将在默认的十秒后关闭连接。
如果 list 命令未列出任何插件(这可能是源代码安装后出现的情况),您需要先激活它们。为此,将它们符号链接到节点机器上的插件目录(Debian/Ubuntu 上为 /etc/munin/plugins),并使用其 init 脚本重新启动守护进程。命令munin-node-configure --shell将显示发行版提供的一些插件的链接命令。
插件有两种类型。对于独立于附加参数的插件,链接名称等于插件名称。但是,有时一个插件可以监控同一类型的多个项目,例如,多个网络接口,如 eth0 和 eth1。
在这种情况下,将接口名称硬编码到插件中是愚蠢的。相反,您在符号链接的名称中提供此信息。能够做到这一点的插件称为通配符插件,它们的名称以一个下划线结尾。例如,如果您想使用通配符插件 if_ 监控 eth0 接口,则指向 if_ 插件的链接将是 if_eth0
$ ls -al /etc/munin/plugins/if_eth* lrwxrwxrwx 1 root root 28 2008-06-27 23:53 ↪/etc/munin/plugins/if_eth0 -> /usr/share/munin/plugins/if_ lrwxrwxrwx 1 root root 28 2008-06-27 23:53 ↪/etc/munin/plugins/if_eth1 -> /usr/share/munin/plugins/if_
Munin 发行版提供的大多数插件都属于 auto 插件系列,并且可以使用 autoconf 参数运行。在这种情况下,它们会检查它们是否能够提供有意义的结果。例如,仅当您运行 Exim 邮件服务器时,运行 Exim MTA 的监控插件才有意义。如果您的系统不满足运行特定启用 autoconf 的插件的先决条件,它将为您提供有意义的提示
$ /usr/share/munin/plugins/exim_mailqueue autoconf no (exim not found)
要概述所有预安装的实现 autoconf 方法的插件,只需运行
# munin-node-configure --suggest Plugin | Used | Suggestions ------ | ---- | ----------- [...] exim_mailqueue | no | [exim not found] [...] if_ | yes | [...]
如果您想与他人分享您自己的插件,例如,在 Munin Exchange 平台上,我们建议您查看官方提供的插件,了解 autoconf 方法是如何实现的。但是,要开始使用您自己的插件,请不要不必要地使您的生活复杂化。
作为一个例子,让我们以五分钟的间隔监控慕尼黑机场的出发情况,因为这是 Munin cron 作业的默认设置。出发时刻表可以从网页上获得,我们可以使用 shell 脚本和 Links 文本浏览器将其转储到临时文件中
#!/bin/sh SITE=http://www.munich-airport.de DEP_URL=$SITE/en/consumer/fluginfo/abflug/index.jsp?viewType=t TMP_FILE=/tmp/.muc_flights links -dump $DEP_URL > $TMP_FILE
此文件现在包含如下行
[...] [ LH 3464 ] [ Budapest ] [ 21:30 ] [ ] [ T2 ] [ departed ] [ LH 726 ] [ Shanghai ] [ 21:30 ] [ ] [ T2 ] [ boarding ] [...]
该网页列出了五种航班状态:呼叫中、登机中、已出发、计划中(表示延误)和已取消。我们将计算当前时间间隔内的航班状态,并在标准输出上返回它们,如下所示
calling.value 0 boarding.value 1 departed.value 1 planned.value 0 cancelled.value 0
要确定当前时间,我们可以使用以下 date 命令
$ date +%H:%M 21:30
并在“links”转储中 grep 此字符串。不幸的是,事情有点复杂。机场的出发时刻表列出了五分钟时段内的所有航班。但是,即使 Munin 主节点上的 cron 作业配置为在每小时的零分、五分、十分(等等)运行时,我们也不能确定它会准时运行。这就是为什么我们的插件使用模运算(由 bc 执行)相应地向下舍入当前分钟,并在 case 结构中组合小时和分钟
TIME=$(date +%H) MIN=$(echo "($(date +%M)/5)*5"|bc) case $MIN in 0) TIME=$TIME:00 ;; 5) TIME=$TIME:05 ;; *) TIME=$TIME:$MIN ;; esac
现在,TIME 变量仅包含分钟为五的倍数的小时:分钟对。
还有另一个复杂情况——当 Munin 主节点在与计划时间略有偏差的时间点将从插件检索到的值存储在相关的 RRD 中时,RRDtool 会对它们进行插值。这样,数据库很少包含我们的插件返回的整数值,而是略有偏差的浮点值。
好消息是 RRDtool 接受时间值对。在这种情况下,它将避免插值。时间必须以自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数作为值的前缀给出。冒号 (:) 用作分隔符,如以下示例插件输出所示
calling.value 1230841800:0 boarding.value 1230841800:1 departed.value 1230841800:1 planned.value 1230841800:0 cancelled.value 1230841800:0
(1230841800 等于 2009 年 1 月 1 日,21:30。)请注意,1.3.4 之前的 Munin 版本无法处理使用此扩展格式的插件输出。这意味着以下插件代码与旧版本的 Munin 不兼容
links -dump $DEP_URL | grep $TIME > $TMP_FILE UNIXTIME=$(date -d$TIME:00 +%s) echo "calling.value $UNIXTIME:$(grep calling $TMP_FILE | wc -l)" echo "boarding.value $UNIXTIME:$(grep boarding $TMP_FILE | wc -l)" echo "departed.value $UNIXTIME:$(grep departed $TMP_FILE | wc -l)" echo "planned.value $UNIXTIME:$(grep planned $TMP_FILE | wc -l)" echo "cancelled.value $UNIXTIME:$(grep cancelled $TMP_FILE | wc -l)"
除了在不带其他参数运行插件时要生成的输出之外,所有插件都必须实现 config 方法,该方法在插件使用 config 字符串作为参数运行时执行。如果我们将我们的脚本命名为 muc(慕尼黑机场的缩写)并从它所在的目录启动它,它可能会产生以下输出
$ ./muc config graph_title Departures Munich Airport graph_vlabel Number graph_args --base 1000 --lower-limit 0 graph_category Departures calling.label Calling calling.draw AREA boarding.label Boarding boarding.draw STACK departed.label Departed departed.draw STACK planned.label Late planned.draw LINE2 cancelled.label Cancelled cancelled.draw LINE2
每次 Munin 主节点要求 Munin 节点守护进程使用 fetch 命令运行插件时(如上面的 Telnet 会话中所示),它还会执行 config 方法,以了解如何在图表中显示数据。在此示例中,图表的标题应为“慕尼黑机场出发”(图 2),y 轴应标记为“数量”。
graph_args 变量允许插件将参数转发到 RRDtool 图形例程(请参阅 rrdgraph 手册页)。转发选项--base 1000,muc 插件确保图中显示的 k(千)单位前缀等于 1000,而不是 1024。-lower-limit 0影响 RRDtool 的自动缩放。它确保显示的 y 轴始终至少从 0 开始。
graph_category 告诉 Munin 主节点在哪个类别(图 1)中显示相关图表。这允许您以逻辑方式对图表进行分组。未指定 graph_category 变量的插件的图表可以在“其他”类别中找到。muc 数据将在我们自己的新类别“出发”中呈现。
除了这些与整个图表相关的变量外,您还可以为插件监控的每个参数指定详细信息,因此在 muc 插件的情况下,为 calling、boarding、departed、planned 和 cancelled。变量<parameter>.label设置<parameter>.
<parameter>.draw指定<parameter>数据要呈现为的图表类型。AREA要求 Munin 主节点绘制相关曲线,并用颜色填充 x 轴和数据点之间的整个区域。绘图类型为 STACK 的参数数据将堆叠在此基本区域之上。这样,我们就汇总了在当前五分钟间隔内标记为呼叫中、登机中和已出发的所有航班。机场时刻表稍后不会更正其出发时间;它们都在此时计为已发送。
标记为计划中和已取消的航班的行为有所不同。对于延误的航班,慕尼黑机场当局稍后将发布新的出发时间。这样,插件将看到计划中的航班两次:一次是在其计划时间段内作为计划中的航班,另一次是在其实际出发时间间隔作为呼叫中、登机中或已出发的航班。这就是为什么我们将延误的航班以类型为 LINE2 的单独线条绘制。数字表示线条的粗细,单位为像素。LINE1 线条为一个像素粗;LINE2 线条为两个像素,LINE3 线条为三个像素。取消的航班不会在时间表中重新出现,但由于它们永远不会出发,因此我们也将其绘制为两条像素粗的单独线条。
所有这些插件输出都写入标准输出。我们的 muc 脚本的最终版本如列表 1 所示。
列表 1. muc Munin 插件脚本
SITE=http://www.munich-airport.de DEP_URL=$SITE/en/consumer/fluginfo/abflug/index.jsp?viewType=t TMP_FILE=/tmp/.muc_flights if test "$1" = "config"; then echo graph_title Departures Munich Airport echo graph_vlabel Number echo graph_args --base 1000 --lower-limit 0 echo graph_category Departures echo calling.label Calling echo calling.draw AREA echo boarding.label Boarding echo boarding.draw STACK echo departed.label Departed echo departed.draw STACK echo planned.label Late echo planned.draw LINE2 echo cancelled.label Cancelled echo cancelled.draw LINE2 else TIME=$(date +%H) MIN=$(echo "($(date +%M)/5)*5"|bc) case $MIN in 0) TIME=$TIME:00 ;; 5) TIME=$TIME:05 ;; *) TIME=$TIME:$MIN ;; esac TMP_FILE=$TMP_FILE:$TIME links -dump $DEP_URL | grep $TIME > $TMP_FILE UNIXTIME=$(date -d$TIME:00 +%s) echo "calling.value $UNIXTIME:$(grep calling $TMP_FILE | wc -l)" echo "boarding.value $UNIXTIME:$(grep boarding $TMP_FILE | wc -l)" echo "departed.value $UNIXTIME:$(grep departed $TMP_FILE | wc -l)" echo "planned.value $UNIXTIME:$(grep planned $TMP_FILE | wc -l)" echo \ "cancelled.value $UNIXTIME:$(grep cancelled $TMP_FILE | wc -l)" rm $TMP_FILE fi
要激活 muc 插件,我们只需在插件目录中创建一个符号链接并重新启动 Munin 守护进程
# ln -s <path/to/>muc /etc/munin/plugins # /etc/init.d/munin-node restart
通过从 Munin 主节点机器 Telnet 连接到我们已激活 muc 插件的节点机器的端口 4949,我们可以检查一切是否正常。让我们看看我们的 config 方法是否有效
$ telnet localhost 4949 [...] config muc graph_title Departures Munich Airport graph_vlabel Number [...] cancelled.label Cancelled cancelled.draw LINE2
如果我们手动可以做到这一点,Munin 主节点应该生成一些漂亮的小图表,并通过 Web 呈现它们,如图 3 所示。
编写和集成自定义插件的简单方法是 Munin 的巨大优势之一——即使是更复杂的通配符插件也不是什么大问题。
不幸的是,简单有时也意味着过于简单。尽管可以将 Munin 生成的图表包含在自定义网页中,但 Munin 不提供任何功能来自定义 Munin 主节点生成的网页。尤其是在具有许多主机和插件要监控的站点上,将所有每日和每周图表组合在一个页面中的简单方法会导致概述页面加载速度极慢。
Munin 架构中僵化方法的另一个例子是数据的固定分辨率。并非所有数据的变化都足够快,以至于五分钟的间隔是合适的。
不幸的是,单个插件的配置选项仍然是愿望清单上的一个项目。另一方面,Munin 允许通过在 /etc/munin/plugin-conf.d/ 目录中设置环境变量来对插件进行非常强大的配置。
由于文档可能不够详尽,并且代码的注释也不够完善,因此英语和德语用户的邮件列表仍然是有用的资源。
资源
Munin: munin.projects.linpro.no
与 Nagios 集成: munin.projects.linpro.no/wiki/HowToContactNagios
RRDtool: oss.oetiker.ch/rrdtool
Munin Exchange: muninexchange.projects.linpro.no
rrdgraph 手册页: oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html
Patricia Jung (trish+lj@trish.de) 是一位自由技术作家、编辑、社区经理和系统管理员, специализируется на темах с открытым исходным кодом из Мюнхена/Германии.