第五章:事件调度
欢迎回到Nagios Core!
在上一章第四章:配置加载中,我们了解了Nagios如何读取配置文件以知晓需要监控的对象,比如我们的朋友"Web Server 1"。此时Nagios内存中已构建完整的基础设施拓扑图。
但仅仅知道存在什么是不够的。Nagios需要主动执行操作 ,而且必须在正确的时间执行。何时检查"Web Server 1"的可达性?何时检查HTTP服务?何时保存当前状态?
这就是事件调度的用武之地。
什么是事件调度?
将Nagios Core引擎视为拥有主日历和闹钟的系统。事件调度 就是管理这个日历的机制,负责跟踪所有未来需要执行的任务并在预定时间触发
它们。
本质上,Nagios是一个高度组织化的事件调度器。它维护着未来定时事件的队列,这些事件代表着Nagios需要执行的所有操作
:
- 对主机和服务执行主动检查
- 在问题发生或解决时发送通知
- 执行状态保存、日志轮转等维护任务
- 检查被动检查结果的"新鲜度"
- 执行计划停机开始/结束任务
- 以及其他更多功能!
正是这种调度机制驱动着Nagios的所有活动。
用例:为"Web Server 1"调度检查
基于前几章加载的配置,Nagios已知晓"Web Server 1"及其HTTP和Ping服务,包括它们的check_interval
(如每5分钟检查)和check_period
(如24x7
)。
事件调度组件利用这些信息向日历添加事件:
- 立即(或启动/重载后不久)计算"
Web Server 1
"及其服务的首次检查时间 - 将"Web Server 1主机检查"和"Web Server 1: HTTP服务检查"、"Web Server 1: Ping服务检查"事件加入队列
- 当检查事件到期时触发执行
- 检查运行并处理结果后(后续章节详述),
调度系统计算下次检查时间
(如5分钟后)并新增检查事件
这种调度、触发和重新调度的持续循环确保按配置定期检查所有对象。
核心概念:事件队列与主循环
事件调度围绕两个核心理念构建:
- 事件队列 :存储所有未来事件的中心
数据结构
,按执行时间
排序。Nagios使用高效优先队列实现(内部称squeue
),专为快速事件添加和检索优化 - 主执行循环 :Nagios引擎持续运行的循环,每个周期中:
- 查看
队列首部
事件 - 等待至事件到期
- 取出事件并执行关联任务
- 对
循环事件计算下次执行时间并重新入队
- 查看
幕后工作机制
以下是Nagios完成配置加载后的核心调度流程:

- 初始化(
init_timing_loop
) :为所有配置对象计算初始next_check
时间,创建timed_event
对象入队 - 主循环(
event_execution_loop
):进入无限循环 - 查看(peek):获取队列首部事件时间
- 等待/轮询 :使用
iobroker_poll
系统调用休眠至事件到期或外部输入到达 - 取出处理(pop & handle) :取出事件并通过
handle_timed_event
处理 - 事件特定逻辑 :根据事件类型调用对应函数(如
run_scheduled_service_check
) - 重新调度:对循环事件计算下次执行时间并重新入队
- 循环往复:持续处理后续事件
(FIFO)
代码解析
核心调度逻辑位于base/events.c
及相关头文件:
c
// 简化版timed_event结构(来自include/nagios.h)
typedef struct timed_event_struct {
int event_type; // 事件类型
time_t run_time; // Unix时间戳
void *event_data; // 关联数据指针
int recurring; // 是否循环
unsigned long event_interval; // 循环间隔
// 其他字段...
} timed_event;
// 全局事件队列
extern squeue_t *nagios_squeue;
事件调度关键函数:
c
// 创建新事件并入队(base/events.c简化版)
timed_event *schedule_new_event(int event_type, ...) {
timed_event *new_event = calloc(1, sizeof(timed_event));
// 设置事件字段...
add_event(nagios_squeue, new_event);
return new_event;
}
// 主事件循环(base/events.c简化版)
int event_execution_loop(void) {
while(1) {
// 获取下个事件时间
temp_event = squeue_peek(nagios_squeue);
// 计算等待时间
poll_time_ms = ...;
// 等待事件或外部输入
iobroker_poll(nagios_iobs, poll_time_ms);
// 处理到期事件
if (should_run_event(temp_event)) {
handle_timed_event(temp_event);
remove_event(nagios_squeue, temp_event);
// 重新调度循环事件
if(temp_event->recurring)
reschedule_event(nagios_squeue, temp_event);
}
}
}
// 事件处理器(base/events.c简化版)
int handle_timed_event(timed_event *event) {
switch(event->event_type) {
case EVENT_SERVICE_CHECK:
run_scheduled_service_check(...);
break;
// 其他事件类型处理...
}
}
实现了一个事件调度系统的核心功能,用于管理和执行定时或周期性任务
(如服务检查)。
系统通过事件队列管理待执行任务,主循环持续检查while(1){}
并执行到期事件。
事件创建与入队
schedule_new_event()
函数负责创建新事件:
- 使用
calloc
动态分配内存,创建timed_event
结构体 - 初始化事件类型等参数(代码中
...
表示省略的细节) - 通过
add_event()
将事件插入优先级队列nagios_squeue
- 返回创建的事件指针,供后续操作使用
主事件循环
event_execution_loop()
是持续运行的核心循环: while(1){}
squeue_peek()
查看队列中下一个即将触发的事件(不取出)- 计算该事件的剩余等待时间
poll_time_ms
iobroker_poll()
同时监控外部输入和等待事件到期should_run_event()
判断事件是否到期,到期则调用handle_timed_event()
执行- 执行后通过
remove_event()
移出队列,若是周期性事件则用reschedule_event()
重新入队
事件处理逻辑
handle_timed_event()
根据事件类型执行具体操作:
EVENT_SERVICE_CHECK
类型触发run_scheduled_service_check()
- 其他事件类型通过类似
switch-case
分支处理 - 实际系统中会包含更多事件类型(代码中
//其他事件类型处理...
示意)
实现效果
通过事件调度系统,Nagios能够:
- 将"Web Server 1"的配置转换为
可执行的定时事件
- 通过
主循环持续监控
和执行检查 - 按配置间隔自动重新调度后续检查
- 保持对所有监控资源的持续监测
主循环机制
-
主循环机制是程序不断重复执行的核心流程,用于处理输入、更新状态和输出结果,直到满足退出条件。
-
eg. int event_execution_loop(void)
{ while(1) {}}
:无限循环查询,通过break
跳出 -
例如游戏循环会持续检测玩家操作、计算画面变化并刷新显示。
总结
事件调度是Nagios Core引擎的核心驱动力,通过精心设计的时间队列管理和主循环机制,确保监控配置的主动实施。下一章我们将探讨检查执行的具体过程
。
第六章:检查执行
在上一章第五章:事件调度中,我们了解到Nagios如何利用主日历来决定事件发生的时间------包括何时需要检查我们的朋友"Web Server 1"是否仍然可达或HTTP服务是否正常运作。
现在Nagios已经知道监控什么 (对象定义),知道如何查找这些定义 (配置加载),也知道*何时执行检查
*(事件调度)。
但它究竟如何执行检查?如何发送ping请求或尝试连接Web服务器?
这就是检查执行系统的职责所在。
什么是检查执行?
检查执行系统是Nagios Core中负责实际执行已定义监控任务的核心组件。它就像行动执行者。当调度器发出"立即检查Web Server 1的HTTP服务!"指令时,检查执行系统接收命令并付诸实施。
关键在于,Nagios Core本身并不包含
执行ping操作、检查网页或查看磁盘空间的代码。
相反,它依赖于称为插件 的外部程序(通常是小型脚本或可执行文件)。检查执行系统的主要职责包括:
- 根据对象定义确定特定检查所需的正确插件命令和参数
- 运行这个外部插件程序
- 捕获插件执行结果
我们可以将其理解为:
Nagios将特定工具(插件)交给临时工作进程,并指示"去检查这个目标并反馈结果!"
用例分析:检查"Web Server 1: HTTP"
让我们追踪当事件调度系统触发"Web Server 1"HTTP服务检查时发生的事件链:
- 调度器触发"Web Server 1: HTTP"服务检查的事件处理器
- 处理器查找该服务的配置信息(来自已加载的对象定义)。找到我们在第三章:对象定义中定义的
check_command
指令,即check_http
。完整命令行可能类似/usr/local/nagios/libexec/check_http -H 192.168.1.100
- 检查执行系统准备执行该命令
- 在运行Nagios Core的系统上启动
/usr/local/nagios/libexec/check_http
作为独立进程 - 系统等待
check_http
插件完成 - 插件运行时,检查执行系统捕获其标准输出(如"HTTP OK: HTTP/1.1 200 OK - 154 bytes in 0.052 second performance data...")和标准错误(如果有)
- 插件退出时,检查执行系统捕获退出代码。该代码遵循Nagios插件标准:
- 0: 正常
- 1: 警告
- 2: 严重
- 3: 未知
- 收集的输出、标准错误和退出代码组合成"检查结果"
- 该结果传回Nagios主引擎进行下一阶段处理:检查结果处理
这个循环过程会为调度引擎安排的所有活动主机和服务检查重复执行。
⭕核心:插件与进程执行
检查执行系统的核心思想包括:
- 插件 :实际执行监控的工作单元。它们是独立的程序(
Bash
、Python
、Perl脚本
或编译二进制文件),接收参数(如主机IP、端口号、阈值)并执行特定检查。其输出和退出代码是向Nagios反馈的标准方式。Nagios Core自带标准插件集(nagios-plugins
),但支持用户自定义开发 - 外部进程执行 :出于安全性和稳定性考虑,Nagios从不 在主进程内部运行插件。而是通过系统调用将每个插件作为独立子进程启动。这意味着即使插件崩溃或挂起,也
不会影响
整个Nagios引擎 - 命令构造 :Nagios从配置中获取
check_command
和定义参数(如第三章:对象定义中check_ping
的!100,20%!500,60%
),扩展任何宏(如$HOSTNAME$
,$SERVICESTATE$
),构建最终执行的命令行字符串 - 结果捕获 :Nagios需要通过管道连接插件的标准输出和标准错误流来读取打印内容,同时使用系统调用(
waitpid
)获取插件退出状态(linux下一切皆文件【Linux】重定向 | 为什么说"一切皆文件?")
频繁运行大量外部进程可能消耗系统资源。--
池化解决
Nagios Core采用专用系统高效管理这些插件执行,通常通过准备就绪的工作进程池(Workers)来启动检查。
⭕幕后工作原理
当事件处理器决定运行检查时(如第五章:事件调度中提到的handle_timed_event
函数,特别是调用run_scheduled_service_check
或run_scheduled_host_check
的部分),检查执行的简化流程如下:

- 事件(如计划检查或按需检查)通知主引擎需要执行特定主机/服务检查
- 引擎查找相关对象定义及其关联的
check_command
- 准备需要执行的
完整命令行
字符串,包括扩展宏
- 主引擎不直接执行命令,而是通过消息或请求将任务分派给工作进程(如
base/nagios.c
和include/workers.h
代码片段所示,详细内容将在下一章讨论)。该请求包含命令行
、检查超时
和被检主机/服务信息
- 工作进程接收任务请求
- 工作进程使用底层系统调用(
fork
、exec
、pipe
、waitpid
)启动指定插件命令作为自身子进程
。建立管道捕获
插件的标准输出和标准错误 - 插件程序运行,执行检查(如连接目标主机/端口),向标准输出打印结果,并以适当状态码退出
- 工作进程读取插件写入标准输出和标准错误管道的内容。同时等待插件进程结束并获取退出状态
- 工作进程将收集的输出、标准错误、退出状态和计时信息打包成结构化结果
工作进程
通过进程间通信通道
(如套接字)将结构化检查结果返回主引擎
- 主引擎接收结果并传递给检查结果处理系统更新状态、记录事件并可能发送通知
这种架构(特别是工作进程机制)使Nagios能够
(多进程)并发
运行成百上千次检查,同时保持主调度和事件处理循环的持续运行
。
代码
Nagios Core的实际底层进程执行逻辑主要依赖runcmd
库(lib/runcmd.h
、lib/runcmd.c
),该库提供安全执行外部命令并捕获输出和退出状态的方法。
但在标准Nagios Core设置中,主引擎不直接调用runcmd
执行检查,而是通过工作进程系统在内部使用runcmd
。
查看主引擎用于向工作进程请求 检查执行任务的接口(定义于include/workers.h
):
c
// 摘自 include/workers.h(简化版)
// 保存工作进程执行任务后返回结果的结构体
typedef struct wproc_result {
unsigned int job_id;
unsigned int type; // 任务类型(WPJOB_CHECK、WPJOB_NOTIFY等)
char *command; // 实际执行的命令行
char *outstd; // 命令的标准输出
char *outerr; // 命令的标准错误
int wait_status; // waitpid的原始状态(包含退出码)
int exited_ok; // 进程是否正常退出
int early_timeout; // 是否提前超时
// ... 其他时间和状态字段 ...
} wproc_result;
// 主引擎请求工作进程执行检查任务的函数
// 注意:这是主引擎概念上的简化函数调用
// 实际接口使用消息队列或套接字
// 摘自 include/workers.h(概念简化版)
extern int wproc_run_check(check_result *cr, char *cmd, nagios_macros *mac);
// 该函数向工作进程发送执行'cmd'命令的请求
// 'cr'是用于存储结果指针的结构体,'mac'是环境宏
wproc_result
结构体是关键------它是工作进程执行命令后返回给主引擎的信息包,包含所有必要数据:执行的命令、插件标准输出(outstd
)、标准错误(outerr
)和退出状态(源自wait_status
)。
概念上的wproc_run_check
(或run_scheduled_*_check
使用的类似内部函数)接收检查细节,并将其发送到工作系统进行异步执行。
在工作进程 内部,实际运行插件命令和捕获输出的代码使用
runcmd
库。
runcmd_open
函数是启动命令的起点:
c
// 摘自 lib/runcmd.h(简化版)
/**
* 从命令行字符串启动命令
* @param[in] cmd 要执行的命令
* @param[out] pfd 子进程stdout文件描述符
* @param[out] pfderr 子进程stderr文件描述符
* // ... 其他参数 ...
* @return 子进程pid,或负错误码
*/
extern int runcmd_open(const char *cmd, int *pfd, int *pfderr, /* ... */);
/**
* 等待命令退出并获取状态
* @param[in] pid runcmd_open启动的子进程ID
* @param[out] status 来自waitpid()的等待状态
* @return 成功返回pid,错误返回-1
*/
extern pid_t runcmd_wait(pid_t pid, int *status);
-
工作进程接收到检查任务后,使用
runcmd_open
启动插件。 -
该函数fork新进程,为标准输出和标准错误建立管道(返回文件描述符
pfd
和pfderr
),并执行插件命令。 -
工作进程从
pfd
和pfderr
读取数据直到关闭(表示插件完成输出),然后对返回的pid
使用runcmd_wait
(封装waitpid
)获取插件退出status
。 -
捕获的数据随后存入前文提到的
wproc_result
结构体并返回主引擎。
lib/wproc.c
文件包含工作进程的实现代码,包括监听任务请求的循环和调用runcmd_open
读取结果的代码。
示例lib/wproc.c
中的print_input
函数展示了进程(如Nagios主引擎)如何从工作进程套接字读取结构化结果(简化为键值对的wproc_result
消息)。
用例
通过使用检查执行系统(由工作进程使用runcmd
等工具协调),Nagios Core成功运行"Web Server 1"的check_http
插件,获取关键信息:文本输出("HTTP OK: ...")和退出代码(0表示正常,其他值表示问题)。
这些信息是Nagios引擎更新内存状态数据(状态数据管理)和status.dat
文件的原始材料,最终显示在CGI界面中。
总结
检查执行机制通过运行实际外部插件,将监控配置转化为具体行动。
它将对象定义中的check_command
转换为可执行进程,管理其执行(通常通过辅助工作进程),并捕获关键输出和退出代码。
这些结果是Nagios判断主机和服务状态的核心依据。
然而我们多次提到"工作进程"却未完整解释。Nagios如何管理与这些独立辅助进程的启动和通信
?这正是下一章要探讨的内容。