第五章:事件調度
歡迎回到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
及相關頭文件:
// 簡化版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;
事件調度關鍵函數:
// 創建新事件并入隊(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
):
// 摘自 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
函數是啟動命令的起點:
// 摘自 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如何管理與這些獨立輔助進程的啟動和通信
?這正是下一章要探討的內容。
第七章:工作進程