第 12 章 开发相关

12.1. 使用内嵌Perl解释器

12.1.1. 介绍

Nagios编译时可以选择支持内嵌Perl解释器。这使得Nagios可以用更高效率来执行Perl所写插件,因而如果严重依赖于Perl写的插件的话可能是个好消息。没有内嵌Perl解释器,Nagios将通过象外部命令一样用派生和执行的方法利用Perl所写的插件。当编译中选择了支持内嵌Perl解释器时,Nagios可以象调用库一样来执行Perl插件。

提示

嵌入式Perl解释器可以让Nagios执行各种Perl脚本-不仅仅是插件。本文档只讨论涉及到Perl解释里执行对主机与服务检测的插件,但它同样也支持相似的Perl脚本以用于其他类型命令(例如通知脚本、事件处理脚本等)。

Stephen Davies在几年前发布了最初的嵌入式Perl解释器,Stanley Hopcroft是主要的帮助嵌入式Perl解释器改进提升的人并且应用它的对优劣性做了批注。他同时给出了有关如何更好地实现用嵌入式Perl来实现Perl插件的方法。必须注意本文档里用的"ePN",它指示Nagios用Perl,或是指示了Nagios要用嵌入式Perl解释器来编译执行它。

12.1.2. 优点

使用ePN(Nagios编译有嵌入式Perl解释器)的好处有:

  1. Nagios在运行Perl插件时将付出更少时间因为它不需要派生进程来执行插件(每次执行要调入Perl解释器)。嵌入式Perl解释器可以象调用库函数一样来执行插件;
  2. 它会大大降低运行Perl插件的系统开销并且(或者同时)可以同时运行更多的Perl插件检测。也就是说,可能使用其他语言并没有这些好处,语言象C/C++、Expect/TCL,用这些语言开发插件公认地比使用Perl语言开发插件要慢一个数量级(虽然在最终运行时间上会更快,TCL语言是个例外);
  3. 如果不是C程序员,仍旧可以用Perl来做背负那些繁重的工作而不至于拖慢Nagios的运行。但是要注意,ePN并不能加速插件本身(还要除去内嵌Perl解释器的加载时间)。如果要加速插件本身的执行,可以考虑使用Perl XSUB包(XS)或是C,这么做的前提是你已经确信插件Perl程序足够优化并且保存了合理的算法(用那个Benchmark.pm包来比较Perl程序模块的性能的方法是没有意义的);
  4. 用ePN是一个学习Perl语言最好的机会。

12.1.3. 缺点

使用ePN(Nagios嵌入式Perl解释器)比之纯粹Nagios程序的缺点相近的,是在运行Apache带有mod_perl(Apache也是使用嵌入式解释器)和运行纯粹Apache程序的两种情形对比,情况也是这样:

  1. 一个Perl程序可能在使用纯粹Nagios程序时运转良好但可能在使用ePN时却可能不正常,要修改Perl插件程序以使之运转;
  2. 在使用ePN时调试Perl插件要比使用纯粹Nagios调试插件要困难一些;
  3. 使用ePN的插件比纯粹Nagios情况下要更大一些(内存占用);
  4. 想用的一些Perl结构体可能不能用或是用起来很困难;
  5. 不得不要关注'有多个进程在使用'和选择一种方式看起来更少交换或更少明显交互(注意用解释器执行是并发的--译者注);
  6. 要有更多的Perl功底(但不需要过多Perl技巧或素材-除非是使用XSUBS的插件)。

12.1.4. 使用嵌入式Perl解释器

如果要使用嵌入式Perl解释器来运行Perl插件和Perl脚本,下面这些是需要做的:

  1. 带有嵌入式Perl解释器支持选项来编译Nagios(见下面的指令);
  2. 打开主配置文件里的enable_embedded_perl选项开关;
  3. 设置use_embedded_perl_implicitly选项以符合要求。该选项决定默认情况下是否要使用Perl解释器来运行个别Perl插件和脚本;
  4. 偶尔要对某些Perl插件或脚本要设置或打开或关闭使用嵌入式Perl解释器。这对于部分因带有嵌入式Perl解释的Nagios在执行Perl插件时存在问题时很有用,见下面指令是如何完成的。

12.1.5. 编译一个Nagios带嵌入式Perl解释器

如果要用嵌入式Perl解释器,首先需要编译Nagios支持它。只需要运行源程序配置脚本时带上--enable-embedded-perl参数即可。如果要嵌入式解释器缓存编译后的脚本,还要带上--with-perlcache参数,例子:

./configure --enable-embedded-perl --with-perlcache otheroptions...

一旦用新选项重新运行配置脚本,一定要重编译Nagios。

12.1.6. 指定插件使用Perl解释器

自Nagios的第三版开始,可以在Perl插件或脚本中指定是否需要在嵌入式Perl解释器里运行。这对于运行那些在Perl解释器里运行有问题的Perl脚本的处理将非常有帮助。

明确地指定Nagios是否要使用嵌入式Perl解释器来运行某个Perl脚本,把下面的东西加到你的Perl插件或脚本里...

如果需要Nagios使用嵌入式Perl解释器来运行Perl脚本,在Perl程序里加入下面一行:

# nagios: +epn

如果不用嵌入式解释器,在Perl程序里加入下面这一行:

# nagios: -epn

上面的行必须出现有Perl脚本的前10行里以让Nagios检测Perl程序是否要用解释器。

提示

如果没有明确地用上述方式指出该插件是否要在Perl解释器里运行,Nagios将自主决定。它取决于use_embedded_perl_implicitly变量设置。如果该变量值是1,全部的Perl插件与脚本(那些没明确指定的)将在Perl解释器里运行,如果值是0,那些没明确指定运行方式的插件和脚本将不会在解释器里运行。

12.1.7. 开发嵌入式Perl解释器可运行的Perl插件

更多有关开发嵌入式Perl解释器里可运行Perl插件的信息可查阅这篇文档

12.2. 开发使用内嵌式Perl解释器的Nagios插件

12.2.1. 介绍

Stanley Hopcroft在嵌入式Perl解释器的工作机制方面卓有成效并且给出了利用嵌入式Perl解释器方面的优点和缺点的注释,他同时还给出了如何让Perl插件更好地在Perl解释器里运行的一些提示与帮助,下面内容多来自于他的注解。

需要注意本文档里用的"ePN",它指定是带有嵌入式Perl解释器的Nagios版本,如果不是,Nagios需要重新编译以带有嵌入式Perl解释器的支持。

12.2.2. 目标受众

  1. 中级Perl开发者-那些受Perl语言强大能力的熏陶却不会利用内在语言特性或是对那些特性掌握不多的开发员;
  2. 实用主义者而不是深入研究Perl语言特性的;
  3. 如果对Perl对象、命名空间管理、数据结构和调试等并不很有兴趣,或许也就够了;

12.2.3. 开发Perl插件必备知识

不管是否用ePN与否,下面的内容要注意:

  1. 总是要生成一些输出内容;
  2. 加上引用'use utils'并引用些通用模块来输出($TIMEOUT %ERRORS &print_revision &支持等);
  3. 总是知道一些Perl插件的标准习惯,如:
    1. 退出时总是exit带着$ERRORS{CRITICAL}、$ERRORS{OK}等;
    2. 使用getopt函数来处理命令行;
    3. 程序处理超时问题;
    4. 当没有命令参数时要给出可调用print_usage;
    5. 使用标准的命令行选项开关(象-H 'host'、-V 'version'等)。

12.2.4. 开发ePN下的Perl插件必做内容

  • 1、<DATA>不能使用,用下面方式替换:

    my $data = <<DATA; portmapper 100000 portmap 100000 sunrpc 100000 rpcbind 100000 rstatd 100001 rstat 100001 rup 100001 .. DATA %prognum = map { my($a, $b) = split; ($a, $b) } split(/\n/, $data) ;

  • 2、BEGIN块并不会象你想像的执行,尽可能避免用它;
  • 3、确保编译时非常干净,象
    • use strict
    • use perl -w (其他开关特别是[-T]将不能用)
    • use perl -c
  • 4、避免使用全程块里的变量语句(my)来传递到子程序里作为变量数据。如果子程序在插件时被多次调用这将是个致命性错误。这样的子程序块起到一个"终止"作用,使得全程块里的变量被赋予第一个值而带入到后序子程序调用过程中导致死锁。但如果全程变量是只读的(比如是个可并发的结构体)就不是问题,在Bekman的"推荐替代方式"里是这样来做的:
    • 让子程序调用没有副作用方式的参考:

      表 12.1. 源程序转换比对

      转换前转换后

      my $x = 1 ; sub a { .. Process $x ... } . . a ; $x = 2 ; a ; # anon closures __always__ rebind the current lexical value

      my $x = 1 ; $a_cr = sub { ... Process $x ... } ; . . &$a_cr ; $x = 2 ; &$a_cr ;


    • 把全程语句变量送到子程序块里(象个对象或模块那样来处理);
    • 将信息以引用或别名方式送入子程序里(\$lex_var或$_[n]);
    • 用包内全程替换语句并用'use vars qw(global1 global2 ..)'从'use strict'分离出来。
  • 5、注意哪里可以得到更多信息。一般信息是要值得怀疑的(O'Reilly出版的书籍,加上Damien Conways著的"Object Oriented Perl"),但真正有用的是从Stas Bekman在http://perl.apache.org/guide/里所写的mod_perl指南,这才是好的起点。那本书非常适用,虽然它里面没有一点关于Nagios的东西,全是如果来写在Apache里怎么开发利用嵌入式Perl解释器来写Perl程序的内容。提倡多使用perlembed的在线手册(manpage)。其基础是知道Lincoln Stein和Doug MacEachern写的有关Perl及嵌入Perl的一些东西,他们所著的'Writing Apache Modules with Perl and C'一书绝对值得一读。
  • 6、注意在ePN里插件有可能返回一些奇怪的值,这很有可能是由于前面4里所提到的问题所导致的;
  • 7、调试前做点准备:

    • 对ePN做个检查;
    • 插件里加些打印语句输出到标准错误设备STDERR(不能用标准输出设备STDOUT)来显示变量值;
    • 在p1.pl里加些打印语句来显示ePN有要执行插件时是如何看待插件的;
    • 在前台模式里运行ePN(可能要结合前面的一些建议);
    • 插件里用'Deparse'模块来看一下解析命令选项在嵌入解释器实际得到的内容(见Sean M. Burke所写的'Constants in Perl',在Perl Journal里,写于2001年);

    perl -MO::Deparse <your_program>
  • 8、还要当心ePN把插件转换成什么了,并且如果还有错误可以调试一下转换后的内容。正如下面看到的,p1.pl把插件当作一个子程序调用,子程序是在一个名为'Embed::<something_related_to_your_plugin_file_name>'的包里,名字是'hndlr'。插件可能需要命令参数放在@ARGV里,因而pl.pl会把@_赋予@ARGV。这会按次序从'eval'取出值并且如果eval抛出一个错误(任意一个解析错误或运行错误)都会使插件中断运行。下面打印出了在ePN尝试运行之前由它转换好的check_rpc插件的真正内容,大部分来自于插件的代码没有给出,感兴趣的只是由ePN对插件的转换结果。为更清楚起见,转换出的部分用下划线标识了一下:

    package main; use subs 'CORE::GLOBAL::exit'; sub CORE::GLOBAL::exit { die "ExitTrap: $_[0] (Embed::check_5frpc)"; } package Embed::check_5frpc; sub hndlr { shift(@_); @ARGV=@_; #! /usr/bin/perl -w # # check_rpc plugin for Nagios # # usage: # check_rpc host service # # Check if an rpc serice is registered and running # using rpcinfo - $proto $host $prognum 2>&1 |"; # # Use these hosts.cfg entries as examples # # command[check_nfs]=/some/path/libexec/check_rpc $HOSTADDRESS$ nfs # service[check_nfs]=NFS;24x7;3;5;5;unix-admin;60;24x7;1;1;1;;check_rpc # # initial version: 3 May 2000 by Truongchinh Nguyen and Karl DeBisschop # current status: $Revision: 1.17 $ # # Copyright Notice: GPL # ... rest of plugin code goes here (it was removed for brevity) ... }

  • 9、在插件里不用要'use diagnostics'来运行ePN方式的程序,这会使得全部插件都返回一个"紧急"状态;
  • 10、考虑用一个最小的C嵌入Perl的程序来检测插件。虽然它还不能担保在它里面运行正常的话也会在ePN里运行也能正常,但它可以找到很多ePN里肯定也会有的错误。[一个小的ePN程序已经在Nagios的源程序包里,放在contrib/目录下,可用于测试ePN式的Perl插件,切换目录到contrib/下并敲入'make mini_epn'来编译它。它必须与p1.pl程序放在同一个目录里运行,p1.pl也放进了Nagios源程序包里了。]

12.3. Nagios插件API

12.3.1. 其他资源

如果想给Nagios增加一个自己的插件,请访问如下资源:

12.3.2. 插件概览

作为Nagios插件的脚本或执行程序必须(至少)要做两件事:

  1. 退出时给出几种可能的返回值中的一个;
  2. 至少要给出一条输出内容到标准输出设备(STDOUT)。

对Nagios来说,插件里面做什么并不重要。自制插件可以是做TCP端口状态检测,运行某个数据库查询,检查磁盘空闲空间,或其他需要检测的内容。这取决于你想检测什么东西,这完全由你自己决定。

12.3.3. 返回值

Nagios用插件的返回值来生成主机或服务的状态。下表里列出了合法的返回值以及对应的服务或主机状态。

表 12.2. 

插件返回值服务状态主机状态
0正常(OK)运行(UP)
1告警(WARNING)运行(UP)或宕机(DOWN)/不可达(UNREACHABLE)*
2紧急(CRITICAL)宕机(DOWN)/不可达(UNREACHABLE)
3未知(UNKNOWN)宕机(DOWN)/不可达(UNREACHABLE)

注意

如果使能use_aggressive_host_checking选项,返回值1将使主机状态要么是宕机(DOWN)要么是不可达(UNREACHABLE)。其他情况下,返回值1将使主机状态是运行(UP)。Nagios将判定是宕机(DOWN)还是不可达(UNREACHABLE)状态的讨论在这篇文档里有。

12.3.4. 特定插件输出

最小情况下,插件要返回一行文本输出。自Nagios 3版本起,插件可以返回多行输出文本。插件可以返回性能数据以让外部应用来做后序处理。输出文本的基本格式如下:

TEXT OUTPUT | OPTIONAL PERFDATA LONG TEXT LINE 1 LONG TEXT LINE 2 ... LONG TEXT LINE N | PERFDATA LINE 2 PERFDATA LINE 3 ... PERFDATA LINE N

性能数据(用下划线示意的部分)是可选的,如果插件输出文本里有性能数据,必须用管道符(|)把性能数据与其他数据分开,额外的大段输出行(用文字删除符示意的部分)同样也是可选的。

12.3.5. 插件输出样例

下面看一下插件输出的样例...

案例1:只有一行文本输出(不带性能数据)

假定插件的输出文本是这样:

DISK OK - free space: / 3326 MB (56%);

如果插件执行的是一个服务检测,整行输出都会保存在$SERVICEOUTPUT$宏里。

案例2:一行输出带性能数据

插件的输出文本中带有性能数据可给外部应用来处理。性能数据要用管道符(|)分隔开,象是这样:

DISK OK - free space: / 3326 MB (56%); | /=2643MB;5948;5958;0;5968

如果插件执行的是一个服务检测,分隔符左侧的部分将保存在$SERVICEOUTPUT$宏里并且右侧(用下划线示意)的部分将保存在$SERVICEPERFDATA$宏里面。

案例3:多行输出(正文和性能数据都有)

插件可以输出多行文本,并且带有正文输出和性能数据,象是这样:

DISK OK - free space: / 3326 MB (56%); | /=2643MB;5948;5958;0;5968

/ 15272 MB (77%);

/boot 68 MB (69%);

/home 69357 MB (27%);

/var/log 819 MB (84%); | /boot=68MB;88;93;0;98

/home=69357MB;253404;253409;0;253414

/var/log=818MB;970;975;0;980

如果插件执行的是一个服务检测,第一行分隔符左侧的部分将保存在$SERVICEOUTPUT$宏里,带有下划线标识的部分(带空格)将保存在$SERVICEPERFDATA$宏里,带删除符标识的部分(不带换行符)的部分将保存在$LONGSERVICEOUTPUT$宏里(以上的下划线和删除符只是为标记文本段而用的,实际文本中不带有符号格式--译者注)。

每个宏的最终结果是这样的:

表 12.3. 

内容
$SERVICEOUTPUT$DISK OK - free space: / 3326 MB (56%);
$SERVICEPERFDATA$/=2643MB;5948;5958;0;5968./boot=68MB;88;93;0;98./home=69357MB;253404;253409;0;253414./var/log=818MB;970;975;0;980
$LONGSERVICEOUTPUT$/ 15272 MB (77%);\n/boot 68 MB (69%);\n/var/log 819 MB (84%);

利用多行输出结果的机制,可以采取多种方式来返回性能数据:

  1. 无论什么情况都没有性能数据;
  2. 只返回一行性能数据;
  3. 只是在后序的行内返回性能数据(第一行不用的管道分隔符右侧不填内容);
  4. 利用全部的输出位置来带出性能数据。

(看起来第一行右侧部分有点"多余",真的可以不用,但其实这是作者为软件向下兼容低版本使用的插件而特意这么做的,很有必要这么做,看一下源程序就明白了。--译者注)

12.3.6. 插件输出长度限制

Nagios只处理插件返回的前4KB数据内容。这样是为了防止插件返回一个上兆或上千兆的数据块给Nagios处理。这个4K的限制很容易修改,如果你想改,可以编辑一下源代码里的MAX_PLUGIN_OUTPUT_LENGTH宏定义,在源程序包的include/nagios.h.in文件里,重编译一下Nagios就可以了,其他地方不用动!

12.3.7. 例子

如果想找点例子来学习开发插件,推荐去下载Nagios插件项目官方的软件包,插件代码使用多种语言(象C、Perl和SHELL脚本等)写成插件。取得Nagios插件的官方代码包的方法可以看这篇文档

12.3.8. Perl插件

Nagios可以选择支持嵌入式Perl解释器可以提高执行Perl插件的速度。更多有关使用嵌入式Perl解释器来开发Perl插件的信息可以查阅这篇文档