1 可加载模块
概述
可加载模块为扩展 Zabbix 功能提供了一种注重性能的方式。
您可以通过多种方式扩展 Zabbix 功能,例如使用user parameters、external checks以及system.run Zabbix agent 监控项。 这些方式都非常有效,但有一个主要缺点,即 fork()。 Zabbix 每次处理用户指标时都必须 fork 一个新进程,这对性能不利。 通常这不算大问题,但在监控嵌入式系统、监控参数数量很多,或者脚本逻辑复杂、启动时间较长时,可能会成为严重问题。
对可加载模块的支持提供了在不牺牲性能的情况下扩展 Zabbix agent、服务器和 proxy 的方法。
可加载模块本质上是一个由 Zabbix 守护进程使用并在启动时加载的共享库。 该库应包含某些函数,以便 Zabbix 进程能够检测到该文件确实是它可以加载并使用的模块。
可加载模块具有许多优势。 出色的性能以及实现任意逻辑的能力非常重要,但也许最重要的优势是能够开发、使用和共享 Zabbix 模块。 这有助于实现无故障维护,并使新功能更容易、独立于 Zabbix 核心代码库地交付。
模块的许可和二进制形式分发受 AGPL-3.0 许可证约束(模块在运行时与 Zabbix 链接,并使用 Zabbix 头文件;自 Zabbix 7.0 起,整个 Zabbix 代码均采用 AGPL-3.0 许可证)。 Zabbix 不保证二进制兼容性。
模块 API 的稳定性在一个 Zabbix LTS(长期支持)发布周期内得到保证。 Zabbix API 的稳定性不作保证(从技术上讲,可以从模块中调用 Zabbix 内部函数,但无法保证此类模块能够正常工作)。
模块 API
要将共享库视为 Zabbix 模块,它应实现并导出若干函数。 当前 Zabbix 模块 API 中有六个函数,其中只有一个是必需的,其余五个是可选的。
必需接口
唯一必需的函数是 zbx_module_api_version():
int zbx_module_api_version(void);
此函数应返回此模块实现的 API 版本,并且为了使模块能够被加载,该版本必须与 Zabbix 支持的模块 API 版本匹配。 Zabbix 支持的模块 API 版本是 ZBX_MODULE_API_VERSION。 因此,此函数应返回该常量。 为保持源代码兼容性,之前用于此目的的旧常量 ZBX_MODULE_API_VERSION_ONE 现在被定义为等同于 ZBX_MODULE_API_VERSION,但不建议使用它。
可选接口
可选函数有 zbx_module_init()、zbx_module_item_list()、zbx_module_item_timeout()、zbx_module_history_write_cbs() 和 zbx_module_uninit():
int zbx_module_init(void);
此函数应执行模块所需的初始化(如果有)。 如果成功,应返回 ZBX_MODULE_OK。 否则,应返回 ZBX_MODULE_FAIL。 在后一种情况下,Zabbix 将不会启动。
ZBX_METRIC *zbx_module_item_list(void);
此函数应返回模块支持的监控项列表。 每个监控项都在一个 ZBX_METRIC 结构中定义,详情请参见下文。 列表以 key 字段为 NULL 的 ZBX_METRIC 结构结束。
void zbx_module_item_timeout(int timeout);
如果模块导出 zbx_module_item_list(),则 Zabbix 会使用此函数来指定 Zabbix 配置文件中的超时设置,模块实现的监控项检查应遵循该设置。 这里,"timeout" 参数的单位为秒。
ZBX_HISTORY_WRITE_CBS zbx_module_history_write_cbs(void);
此函数应返回 Zabbix 服务器用于导出不同数据类型历史数据的回调函数。 回调函数作为 ZBX_HISTORY_WRITE_CBS 结构的字段提供;如果模块不关心某种类型的历史数据,相应字段可以为 NULL。
int zbx_module_uninit(void);
此函数应执行必要的反初始化操作(如果有),例如释放已分配的资源、关闭文件描述符等。
除 zbx_module_uninit() 外,所有函数都会在 Zabbix 启动时模块加载后调用一次;zbx_module_uninit() 会在 Zabbix 关闭时模块卸载后调用一次。
定义监控项
每个监控项都在一个 ZBX_METRIC 结构中定义:
typedef struct
{
char *key;
unsigned flags;
int (*function)();
char *test_param;
}
ZBX_METRIC;
这里,key 是监控项键值(例如 "dummy.random"),flags 要么是 CF_HAVEPARAMS,要么是 0(取决于该监控项是否接受参数),function 是实现该监控项的 C 函数(例如 "zbx_module_dummy_random"),而 test_param 是在 Zabbix agent 使用 "-p" 标志启动时要使用的参数列表(例如 "1,1000",可以为 NULL)。
定义示例如下:
static ZBX_METRIC keys[] =
{
{ "dummy.random", CF_HAVEPARAMS, zbx_module_dummy_random, "1,1000" },
{ NULL }
}
实现监控项的每个函数都应接受两个指针参数,第一个参数类型为 AGENT_REQUEST,第二个参数类型为 AGENT_RESULT:
int zbx_module_dummy_random(AGENT_REQUEST *request, AGENT_RESULT *result)
{
...
SET_UI64_RESULT(result, from + rand() % (to - from + 1));
return SYSINFO_RET_OK;
}
如果成功获取了监控项值,这些函数应返回 SYSINFO_RET_OK。 否则,应返回 SYSINFO_RET_FAIL。 有关如何从 AGENT_REQUEST 获取信息以及如何在 AGENT_RESULT 中设置信息的详细说明,请参见下面的示例 "dummy" 模块。
提供历史数据导出回调
通过模块进行的历史数据导出已不再受 Zabbix proxy 支持。
模块可以按类型指定用于导出历史数据的函数:数值(浮点)、数值(无符号)、字符、文本和日志:
typedef struct
{
void (*history_float_cb)(const ZBX_HISTORY_FLOAT *history, int history_num);
void (*history_integer_cb)(const ZBX_HISTORY_INTEGER *history, int history_num);
void (*history_string_cb)(const ZBX_HISTORY_STRING *history, int history_num);
void (*history_text_cb)(const ZBX_HISTORY_TEXT *history, int history_num);
void (*history_log_cb)(const ZBX_HISTORY_LOG *history, int history_num);
}
ZBX_HISTORY_WRITE_CBS;
每个函数都应以 "history" 数组和 "history_num" 个元素作为参数。 根据要导出的历史数据类型,"history" 分别是以下结构体的数组:
typedef struct
{
zbx_uint64_t itemid;
int clock;
int ns;
double value;
}
ZBX_HISTORY_FLOAT;
typedef struct
{
zbx_uint64_t itemid;
int clock;
int ns;
zbx_uint64_t value;
}
ZBX_HISTORY_INTEGER;
typedef struct
{
zbx_uint64_t itemid;
int clock;
int ns;
const char *value;
}
ZBX_HISTORY_STRING;
typedef struct
{
zbx_uint64_t itemid;
int clock;
int ns;
const char *value;
}
ZBX_HISTORY_TEXT;
typedef struct
{
zbx_uint64_t itemid;
int clock;
int ns;
const char *value;
const char *source;
int timestamp;
int logeventid;
int severity;
}
ZBX_HISTORY_LOG;
回调将由 Zabbix 服务器 history syncer 进程在历史数据同步过程结束时使用,即在数据写入 Zabbix 数据库并保存到值缓存之后。
如果历史数据导出模块发生内部错误,建议将模块设计为:在恢复之前不要阻塞整个监控,而是丢弃数据,并允许 Zabbix 服务器继续运行。
构建模块
目前模块应在 Zabbix 源码树内构建,因为模块 API 依赖于 Zabbix 头文件中定义的一些数据结构。
对于可加载模块来说,最重要的头文件是 include/module.h,它定义了这些数据结构。 其他有助于 include/module.h 正常工作的必要系统头文件是 stdlib.h 和 stdint.h。
有了这些信息,模块的构建就已准备就绪。 模块应包含 stdlib.h、stdint.h 和 module.h,并且构建脚本应确保这些文件位于包含路径中。 有关详细信息,请参见下面的示例 “dummy” 模块。
另一个有用的头文件是 include/zbxcommon.h,它定义了 zabbix_log() 函数,可用于日志记录和调试。
配置参数
Zabbix agent、服务器和 proxy 支持两个 参数 来处理模块:
- LoadModulePath - 可加载模块所在位置的完整路径
- LoadModule - 启动时要加载的模块。
模块必须位于 LoadModulePath 指定的目录中,或者路径必须位于模块名称之前。 如果前面的路径是绝对路径(以 '/' 开头),则会忽略 LoadModulePath。 允许包含多个 LoadModule 参数。
例如,要扩展 Zabbix agent,我们可以添加以下参数:
LoadModulePath=/usr/local/lib/zabbix/agent/
LoadModule=mariadb.so
LoadModule=apache.so
LoadModule=kernel.so
LoadModule=/usr/local/lib/zabbix/dummy.so
在 agent 启动时,它将从 /usr/local/lib/zabbix/agent 目录加载 mariadb.so、apache.so 和 kernel.so 模块,而 dummy.so 将从 /usr/local/lib/zabbix 加载。 如果模块缺失、权限不正确,或者共享库不是 Zabbix 模块,agent 将无法启动。
前端配置
Zabbix agent、服务器和 proxy 支持可加载模块。 因此,Zabbix 前端中的监控项类型取决于模块加载的位置。 如果模块加载到 agent 中,则监控项类型应为 "Zabbix agent" 或 "Zabbix agent (active)"。 如果模块加载到服务器或 proxy 中,则监控项类型应为 "Simple check"。
通过 Zabbix 模块进行历史数据导出不需要任何前端配置。 如果模块已由服务器成功加载,并提供 zbx_module_history_write_cbs() 函数,且该函数返回至少一个非 NULL 的回调函数,那么历史数据导出将自动启用。
Dummy 模块
Zabbix 包含一个用 C 语言编写的示例模块。
该模块位于 src/modules/dummy 下:
alex@alex:~trunk/src/modules/dummy$ ls -l
-rw-rw-r-- 1 alex alex 9019 Apr 24 17:54 dummy.c
-rw-rw-r-- 1 alex alex 67 Apr 24 17:54 Makefile
-rw-rw-r-- 1 alex alex 245 Apr 24 17:54 README
该模块文档完善,可作为你自己模块的模板。
在按照上文说明在 Zabbix 源码树根目录运行 ./configure 之后,只需运行 make 即可构建 dummy.so。
/*
** Zabbix
** Copyright (C) 2001-2020 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include "module.h"
/* 该变量保存用于监控项处理的超时设置 */
static int item_timeout = 0;
/* 模块应将内部函数定义为 static,并使用不同于 Zabbix 内部 */
/* 符号(zbx_*)和可加载模块 API 函数(zbx_module_*)的命名模式,以避免冲突 */
static int dummy_ping(AGENT_REQUEST *request, AGENT_RESULT *result);
static int dummy_echo(AGENT_REQUEST *request, AGENT_RESULT *result);
static int dummy_random(AGENT_REQUEST *request, AGENT_RESULT *result);
static ZBX_METRIC keys[] =
/* KEY FLAG FUNCTION TEST PARAMETERS */
{
{"dummy.ping", 0, dummy_ping, NULL},
{"dummy.echo", CF_HAVEPARAMS, dummy_echo, "a message"},
{"dummy.random", CF_HAVEPARAMS, dummy_random, "1,1000"},
{NULL}
};
/******************************************************************************
* *
* Function: zbx_module_api_version *
* *
* Purpose: returns version number of the module interface *
* *
* Return value: ZBX_MODULE_API_VERSION - version of module.h module is *
* compiled with, in order to load module successfully Zabbix *
* MUST be compiled with the same version of this header file *
* *
******************************************************************************/
int zbx_module_api_version(void)
{
return ZBX_MODULE_API_VERSION;
}
/******************************************************************************
* *
* Function: zbx_module_item_timeout *
* *
* Purpose: set timeout value for processing of items *
* *
* Parameters: timeout - timeout in seconds, 0 - no timeout set *
* *
******************************************************************************/
void zbx_module_item_timeout(int timeout)
{
item_timeout = timeout;
}
/******************************************************************************
* *
* Function: zbx_module_item_list *
* *
* Purpose: returns list of item keys supported by the module *
* *
* Return value: list of item keys *
* *
******************************************************************************/
ZBX_METRIC *zbx_module_item_list(void)
{
return keys;
}
static int dummy_ping(AGENT_REQUEST *request, AGENT_RESULT *result)
{
SET_UI64_RESULT(result, 1);
return SYSINFO_RET_OK;
}
static int dummy_echo(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char *param;
if (1 != request->nparam)
{
/* 设置可选错误消息 */
SET_MSG_RESULT(result, strdup("Invalid number of parameters."));
return SYSINFO_RET_FAIL;
}
param = get_rparam(request, 0);
SET_STR_RESULT(result, strdup(param));
return SYSINFO_RET_OK;
}
/******************************************************************************
* *
* Function: dummy_random *
* *
* Purpose: a main entry point for processing of an item *
* *
* Parameters: request - structure that contains item key and parameters *
* request->key - item key without parameters *
* request->nparam - number of parameters *
* request->params[N-1] - pointers to item key parameters *
* request->types[N-1] - item key parameters types: *
* REQUEST_PARAMETER_TYPE_UNDEFINED (key parameter is empty) *
* REQUEST_PARAMETER_TYPE_ARRAY (array) *
* REQUEST_PARAMETER_TYPE_STRING (quoted or unquoted string) *
* *
* result - structure that will contain result *
* *
* Return value: SYSINFO_RET_FAIL - function failed, item will be marked *
* as not supported by zabbix *
* SYSINFO_RET_OK - success *
* *
* Comment: get_rparam(request, N-1) can be used to get a pointer to the Nth *
* parameter starting from 0 (first parameter). Make sure it exists *
* by checking value of request->nparam. *
* In the same manner get_rparam_type(request, N-1) can be used to *
* get a parameter type. *
* *
******************************************************************************/
static int dummy_random(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char *param1, *param2;
int from, to;
if (2 != request->nparam)
{
/* 设置可选错误消息 */
SET_MSG_RESULT(result, strdup("Invalid number of parameters."));
return SYSINFO_RET_FAIL;
}
param1 = get_rparam(request, 0);
param2 = get_rparam(request, 1);
/* 为简化起见,这里不对参数和类型进行严格校验 */
from = atoi(param1);
to = atoi(param2);
if (from > to)
{
SET_MSG_RESULT(result, strdup("Invalid range specified."));
return SYSINFO_RET_FAIL;
}
SET_UI64_RESULT(result, from + rand() % (to - from + 1));
return SYSINFO_RET_OK;
}
/******************************************************************************
* *
* Function: zbx_module_init *
* *
* Purpose: the function is called on agent startup *
* It should be used to call any initialization routines *
* *
* Return value: ZBX_MODULE_OK - success *
* ZBX_MODULE_FAIL - module initialization failed *
* *
* Comment: the module won't be loaded in case of ZBX_MODULE_FAIL *
* *
******************************************************************************/
int zbx_module_init(void)
{
/* dummy.random 的初始化 */
srand(time(NULL));
return ZBX_MODULE_OK;
}
/******************************************************************************
* *
* Function: zbx_module_uninit *
* *
* Purpose: the function is called on agent shutdown *
* It should be used to cleanup used resources if there are any *
* *
* Return value: ZBX_MODULE_OK - success *
* ZBX_MODULE_FAIL - function failed *
* *
******************************************************************************/
int zbx_module_uninit(void)
{
return ZBX_MODULE_OK;
}
/******************************************************************************
* *
* Functions: dummy_history_float_cb *
* dummy_history_integer_cb *
* dummy_history_string_cb *
* dummy_history_text_cb *
* dummy_history_log_cb *
* *
* Purpose: callback functions for storing historical data of types float, *
* integer, string, text and log respectively in external storage *
* *
* Parameters: history - array of historical data *
* history_num - number of elements in history array *
* *
******************************************************************************/
static void dummy_history_float_cb(const ZBX_HISTORY_FLOAT *history, int history_num)
{
int i;
for (i = 0; i < history_num; i++)
{
/* 对 history[i].itemid、history[i].clock、history[i].ns、history[i].value 等进行处理 */
}
}
static void dummy_history_integer_cb(const ZBX_HISTORY_INTEGER *history, int history_num)
{
int i;
for (i = 0; i < history_num; i++)
{
/* 对 history[i].itemid、history[i].clock、history[i].ns、history[i].value 等进行处理 */
}
}
static void dummy_history_string_cb(const ZBX_HISTORY_STRING *history, int history_num)
{
int i;
for (i = 0; i < history_num; i++)
{
/* 对 history[i].itemid、history[i].clock、history[i].ns、history[i].value 等进行处理 */
}
}
static void dummy_history_text_cb(const ZBX_HISTORY_TEXT *history, int history_num)
{
int i;
for (i = 0; i < history_num; i++)
{
/* 对 history[i].itemid、history[i].clock、history[i].ns、history[i].value 等进行处理 */
}
}
static void dummy_history_log_cb(const ZBX_HISTORY_LOG *history, int history_num)
{
int i;
for (i = 0; i < history_num; i++)
{
/* 对 history[i].itemid、history[i].clock、history[i].ns、history[i].value 等进行处理 */
}
}
/******************************************************************************
* *
* Function: zbx_module_history_write_cbs *
* *
* Purpose: returns a set of module functions Zabbix will call to export *
* different types of historical data *
* *
* Return value: structure with callback function pointers (can be NULL if *
* module is not interested in data of certain types) *
* *
******************************************************************************/
ZBX_HISTORY_WRITE_CBS zbx_module_history_write_cbs(void)
{
static ZBX_HISTORY_WRITE_CBS dummy_callbacks =
{
dummy_history_float_cb,
dummy_history_integer_cb,
dummy_history_string_cb,
dummy_history_text_cb,
dummy_history_log_cb,
};
return dummy_callbacks;
}
该模块导出三个新监控项:
dummy.ping- 始终返回 '1'dummy.echo[param1]- 原样返回第一个参数,例如dummy.echo[ABC]将返回 ABCdummy.random[param1, param2]- 返回位于 param1-param2 范围内的随机数,例如dummy.random[1,1000000]
限制
可加载模块的支持仅在 Unix 平台上实现。 这意味着它不适用于 Windows agent。
在某些情况下,模块可能需要从 zabbix_agentd.conf 中读取与模块相关的配置参数。 目前不支持这样做。 如果你需要模块使用某些配置参数,可能应当实现对模块专用配置文件的解析。