Wazuh wazuh-manager-db(Wazuh DB)持久化数据库守护进程:线程架构、Socket 协议与数据模型详解
Wazuh wazuh-manager-db(Wazuh DB)持久化数据库守护进程:线程架构、Socket 协议与数据模型详解
Wazuh Manager 的持久化状态——代理注册、分组归属、任务生命周期与 MITRE 参考数据——全部由 wazuh-manager-db 守护进程统一托管。本文基于仓库中 Wazuh DB 模块文档 与 配置参考,结合 src/wazuh_db/ 下的实际源码,完整解析该守护进程的线程模型、Unix Socket 查询协议、SQLite 数据模型以及备份与调优配置,帮助你既能读懂 Wazuh 代理管理的底层存储机制,也能独立完成其备份、恢复与性能调优。
1. 模块定位:一个守护进程管理四类数据
wazuh-manager-db 是 Wazuh Manager 的持久化数据库守护进程,负责管理用于代理注册(agent registration)、分组分配(group assignments)、任务跟踪(task tracking)以及 MITRE ATT&CK 框架数据的 SQLite 数据库。其源码位于 src/wazuh_db/ 目录。
从源码结构看,整个模块由以下文件构成:
| 文件 | 职责 |
|---|---|
| src/wazuh_db/src/main.c | 守护进程入口:Socket 绑定、线程启动 |
| src/wazuh_db/src/wdb_parser.c | 所有数据库目标的查询路由 |
| src/wazuh_db/src/wdb_global.c | 全部 global 子命令实现 |
| src/wazuh_db/src/wdb_task.c | 全部 task 子命令实现 |
| src/wazuh_db/src/wdb.c | SQLite 句柄管理、预处理语句缓存 |
| src/wazuh_db/src/wdb_com.c | JSON 命令处理器(getstats、getconfig) |
| src/wazuh_db/src/wdb_pool.c | 数据库句柄池 |
| src/wazuh_db/src/wdb_state.c | 全局统计与运行状态 |
| src/wazuh_db/src/wdb_metadata.c | 元数据读写 |
| src/wazuh_db/schemas/schema_global.sql | global.db 的 DDL |
| src/wazuh_db/schemas/schema_task_manager.sql | tasks.db 的 DDL |
2. 架构:四线程模型 + HTTP 端点
wazuh-manager-db 运行时维护四个线程,外加一个 HTTP API 端点:
| 线程 | 角色 |
|---|---|
| Dealer(经销商线程) | 接受传入的 Unix Socket 连接,将对端(peer)加入队列 |
| Worker Pool(工作池,默认 ×8) | 从队列取出 peer,执行查询并发送响应 |
| Garbage Collector(垃圾回收线程) | 关闭陈旧的数据库句柄、提交旧事务、检查碎片化 |
| Backup(备份线程) | 按配置周期为 global.db 创建备份 |
2.1 入口流程与线程启动
在 main.c 中,main() 依次完成:解析命令行参数(-V 版本、-h 帮助、-d 调试、-t 配置测试、-f 前台运行,见 main.c)、读取 wazuh_db.* 内部选项、读取 Wazuh 主配置文件(ReadConfig,含 <wdb> 备份块)、初始化句柄池(wdb_pool_init),随后设置文件描述符上限并降权至 wazuh 用户。
线程的创建顺序可以在 main.c 中直接看到:
- 先创建
run_dealer线程; - 注册并启动 HTTP API(见 2.5 节);
- 按
worker_pool_size批量创建run_worker线程; - 创建
run_gc线程; - 仅当
wdb_check_backup_enabled()返回启用时,才创建run_backup线程——这意味着禁用备份后根本不会启动该线程,对性能零开销。
2.2 Dealer 线程:连接受理
run_dealer(main.c)负责绑定 WDB_LOCAL_SOCK(即文档中的 /var/wazuh-manager/queue/db/wdb Unix stream socket),在 select 循环中等待新连接。每接受一个 peer,就通过 wnotify_add(notify_queue, peer, WO_READ) 将其投入一个带读写监视的通知队列。Dealer 本身不处理任何业务,只做“接单”,实现了连接受理与查询执行解耦。
2.3 Worker 线程:查询执行
run_worker(main.c)循环地从 wnotify 队列中取出一个 peer,用 OS_RecvSecureTCP 读取一行请求(最大 OS_MAXSTR 字节,超长则丢弃该连接),然后按请求首字符分派:
if (buffer[0] == '{') {
wdbcom_dispatch(buffer, response); // JSON 命令(getstats/getconfig 等)
} else {
wdb_parse(buffer, response, peer); // 常规 "actor command [payload]" 查询
}
处理完成后,若请求以换行符结尾(terminal 请求),响应会追加换行符后发回,并再次把 peer 挂回通知队列以支持同一连接上的后续请求。这解释了 Socket 协议一节中“换行符决定是否保持会话”的语义。
2.4 垃圾回收线程:事务提交、碎片检查与句柄回收
run_gc(main.c)以 1 秒为节拍循环执行三件事:
wdb_commit_old():提交超时的事务。提交窗口由commit_time_min/commit_time_max内部选项控制,对应文档中“数据库锁定错误可增大 commit_time”的排障建议;- 按
check_fragmentation_interval(默认 7200 秒)周期执行wdb_check_fragmentation(),该计数器每轮减 1,归零触发检查并复位; wdb_close_old():关闭长期不活跃的数据库连接,回收文件描述符。
2.5 HTTP API 端点
除 Unix Socket 外,守护进程还通过 router_register_api_endpoint 暴露一组 REST 接口,监听 queue/sockets/wdb-http.sock,供偏好 REST 接口的内部组件(集群、Server API)使用。从 main.c 可确认注册的端点:
| 方法 | 路径 |
|---|---|
| GET | /v1/agents/ids |
| GET | /v1/agents/ids/groups/:name |
| GET | /v1/agents/ids/groups |
| GET | /v1/agents/:agent_id/groups |
| POST | /v1/agents/summary |
| GET / POST | /v1/agents/sync |
| POST | /v1/agents/restartinfo |
这些端点统一由 wdb_global_pre / wdb_global_post(wdb_parser.c)作为前置/后置钩子执行:前者打开 global.db 并开启事务,后者将句柄归还句柄池。
2.6 备份线程
run_backup(main.c)启动时先通过 wdb_global_get_most_recent_backup() 读取上一次备份时间,之后每秒检查一次:当 当前时间 - 上次备份时间 >= interval 时,打开 global.db 并调用 wdb_global_create_backup() 生成快照,随后刷新基准时间。这与 <wdb> 配置块中 interval 的语义完全一致。
3. Socket 查询协议
3.1 协议格式
- Socket 路径:
/var/wazuh-manager/queue/db/wdb(Unix stream) - 请求:以空字节或换行符结尾的纯文本字符串。第一个 token 指定目标数据库(actor),其余部分是命令与参数:
<database> <command> [<JSON payload>]
- 响应:固定两种前缀:
ok <JSON>
err <message>
3.2 示例查询
global insert-agent {"id":5,"name":"ubuntu-agent","ip":"10.0.0.5","date_add":1700000000}
global update-connection-status {"id":5,"connection_status":"active","sync_status":"synced"}
global get-agent-info {"agent_id":5}
task upgrade {"agent":5,"node":"master-node","module":"upgrade_module"}
task upgrade_update_status {"agent":5,"node":"master-node","status":"Done"}
3.3 查询路由:从 actor 到子命令
wdb_parse()(wdb_parser.c)先剥离前导空白,取出第一个空白前的 token 作为 actor,当前支持三个目标数据库:global、mitre、task(其他 token 返回 err Invalid DB query actor)。每个子命令都有独立的路由分支、独立计数器与耗时统计(w_inc_* / timersub),这使 getstats JSON 命令能够输出细粒度的性能指标。
global 目标(wdb_parser.c)在打开 global.db 后路由的子命令包括:
- 代理 CRUD:
insert-agent、update-agent-name、update-agent-data、update-keepalive、update-connection-status、update-status-code、delete-agent - 查询:
select-agent-name、select-agent-group、find-agent、get-agent-info、get-all-agents、get-agents-by-connection-status - 分组:
find-group、insert-agent-group、select-group-belong、get-group-agents、delete-group、select-groups、get-distinct-groups、set-agent-groups - 集群同步:
sync-agent-groups-get、sync-agent-info-get、sync-agent-info-set、get-groups-integrity、recalculate-agent-group-hashes、reset-agents-connection、disconnect-agents - 维护:
backup、vacuum、get_fragmentation、sleep、sql(透传任意 SQL,用于诊断)
以 insert-agent 为例,其解析函数 wdb_parse_global_insert_agent(wdb_parser.c)用 cJSON_ParseWithOpts 解析 JSON 负载,且仅将 id、name、date_add 三个字段作为必填约束(id/date_add 必须是数字,name 必须是非空字符串),ip、register_ip、internal_key、group 均可为空——这与 agent 表的 NOT NULL 约束精确对应。
mitre 目标(wdb_parser.c)只支持 sql 子命令,作为 MITRE ATT&CK 参考数据的只读查询通道。
task 目标(wdb_parser.c)路由的子命令为:upgrade、upgrade_custom、upgrade_get_status、upgrade_update_status、upgrade_result、upgrade_cancel_tasks、set_timeout、delete_old、sql。所有负载同样以 JSON 传递并由 cJSON_ParseWithOpts 校验,对应代理升级等长时任务的生命周期操作。
3.4 命令行诊断工具
wazuh-db 命令行工具可作为 Socket 客户端直接向守护进程发送查询,例如(引自 configuration.md 的排障章节):
echo 'agent 000 sql SELECT name FROM sqlite_master' | \
/var/wazuh-manager/bin/wazuh-db
注意:从当前 wdb_parse 的路由逻辑看,合法的 actor 为 global、mitre、task,使用 global sql ... 之类的形式是当前版本受支持的透传方式。
4. 数据库文件与表结构
4.1 数据库一览
| 数据库 | 路径 | 用途 |
|---|---|---|
global.db |
queue/db/global.db |
代理注册表、分组、连接状态 |
tasks.db |
queue/tasks/tasks.db |
长时任务生命周期(升级等) |
mitre.db |
var/db/mitre.db |
MITRE ATT&CK 参考数据 |
{id}.db |
queue/db/{id}.db |
每代理库存数据(遗留——仅 4.x,见下文说明) |
关于
{id}.db(4.x 遗留): 在 Wazuh 4.x 中,每个代理在queue/db/{agent_id}.db拥有独立 SQLite 数据库,存放该代理的库存数据(FIM 事件、软件包、进程、网络接口等)。在 Wazuh 5.0 中,这些数据改为直接经由 Indexer Connector 写入 OpenSearch 索引(如wazuh-states-fim-files、wazuh-states-inventory-packages)。每代理 SQLite 库不再创建或使用;迁移完成后,4.x 遗留文件可以删除。
4.2 global.db 表结构
global.db 包含四张表,DDL 定义于 schema_global.sql:
| 表 | 用途 |
|---|---|
agent |
每个注册代理一行:身份、操作系统信息、版本、分组、连接状态 |
group |
命名的代理分组 |
belongs |
带优先级排序的代理—分组归属关系 |
metadata |
全局元数据的键值存储 |
agent 表(schema_global.sql)的关键字段及约束:
id INTEGER PRIMARY KEY:代理 ID;name TEXT NOT NULL:代理名(有agent_name索引);ip/register_ip:当前 IP 与注册时 IP(ip有索引);internal_key:与 Manager 的共享密钥;os_name、os_version、os_major、os_minor、os_type、os_platform、os_arch:操作系统画像;version:代理版本;merged_sum:合并配置摘要;node_name:所属集群节点(默认unknown);date_add INTEGER NOT NULL:注册 UNIX 时间戳;last_keepalive:最近心跳时间;`group`:主分组(默认default);group_hash(有索引)与group_sync_status:group_sync_status枚举为synced/syncreq;sync_status:枚举synced、syncreq、syncreq_status、syncreq_keepalive;connection_status:枚举pending、never_connected、active、disconnected(默认never_connected)——即文档中列出的四种连接状态值;group_config_status:synced/not synced(默认not synced)。
belongs 表(schema_global.sql)以 (id_agent, id_group) 为主键、priority 记录排序优先级,双向 ON DELETE CASCADE,并保证同一代理内 (id_agent, priority) 唯一;id_group 上有独立索引。
4.3 tasks.db 表结构
tasks.db 的 DDL 见 schema_task_manager.sql:
| 表 | 列 | 用途 |
|---|---|---|
TASKS |
TASK_ID、AGENT_ID、NODE、MODULE、COMMAND、CREATE_TIME、LAST_UPDATE_TIME、STATUS、ERROR_MESSAGE |
每个任务实例一行 |
metadata |
key、value |
模式版本跟踪 |
TASKS 表(schema_task_manager.sql)中 TASK_ID 为自增主键,并为 AGENT_ID、NODE、MODULE、COMMAND、CREATE_TIME、LAST_UPDATE_TIME、STATUS 七列各建了一个索引,支撑“按代理/节点/模块/状态检索任务”的典型查询路径。任务状态取值:Pending、In progress、Done、Failed、Timeout、Cancelled,与 task upgrade_update_status 等子命令(如示例中 "status":"Done")的状态机一致。
5. 配置:备份策略与内部选项
Wazuh DB 的配置分为两层:主配置中的 <wdb> XML 块(控制自动备份)与内部选项文件中的 wazuh_db.* 键值(控制运行参数)。
5.1 主配置中的 <wdb> 备份块
配置文件:/var/wazuh-manager/etc/wazuh-manager.conf,XML 段:<wdb>。<backup> 子块通过 database 属性指定目标(当前仅支持 global),包含三个子选项:
| 子选项 | 默认值 | 取值 | 说明 |
|---|---|---|---|
enabled |
yes |
yes / no |
禁用后不创建自动备份,但手动备份仍可用 |
interval |
1d(86400 秒) |
带 s/m/h/d 后缀的正时间值 |
备份越频繁,恢复点越好,但磁盘占用越高 |
max_files |
3 |
正整数(1-999) | 保留份数,超出后删除最旧备份;总占用 ≈ 数据库大小 × max_files |
典型配置(引自 configuration.md):
<wdb>
<backup database="global">
<enabled>yes</enabled>
<interval>1d</interval>
<max_files>3</max_files>
</backup>
</wdb>
高频备份场景:
<wdb>
<backup database="global">
<enabled>yes</enabled>
<interval>6h</interval>
<max_files>8</max_files>
</backup>
</wdb>
大库调优(降低频率、减少份数):
<wdb>
<backup database="global">
<enabled>yes</enabled>
<interval>12h</interval>
<max_files>5</max_files>
</backup>
</wdb>
测试或存储受限环境可整体关闭:
<wdb>
<backup database="global">
<enabled>no</enabled>
</backup>
</wdb>
备份文件写入 /var/wazuh-manager/backup/db/。从源码看,enabled/interval 由 main.c 与 main.c 中的 run_backup 直接消费:enabled 决定备份线程是否创建,interval 决定两次快照的最小间隔。
5.2 内部选项 wazuh_db.*
配置文件:/var/wazuh-manager/etc/wazuh-manager-internal-options.conf(仓库模板见 etc/wazuh-manager-internal-options.conf)。建议修改该文件而非默认的 internal_options.conf,以在升级中保留自定义设置。
以下取值范围与默认值同时由 configuration.md 与 main.c 中的 getDefine_Int_default() 调用双重印证:
# Wazuh DB 调试级别 (0-2)
wazuh_db.debug=0
# 工作线程池大小 (1-32, 默认: 8)
wazuh_db.worker_pool_size=8
# 事务最小提交间隔(秒, 1-3600, 默认: 10)
wazuh_db.commit_time_min=10
# 事务最大提交间隔(秒, 1-3600, 默认: 60)
wazuh_db.commit_time_max=60
# 最大打开数据库连接数 (1-4096, 默认: 64)
wazuh_db.open_db_limit=64
# 最大文件描述符数 (1024-1048576, 默认: 458752)
wazuh_db.rlimit_nofile=458752
# 数据库碎片化阈值百分比 (0-100, 默认: 75)
wazuh_db.fragmentation_threshold=75
# 触发 vacuum 的碎片化增量 (0-100, 默认: 5)
wazuh_db.fragmentation_delta=5
# 目标自由页百分比 (0-99, 默认: 0)
wazuh_db.free_pages_percentage=0
# 允许的最大碎片化百分比 (0-100, 默认: 90)
wazuh_db.max_fragmentation=90
# 碎片检查间隔(秒, 1-30758400, 默认: 7200 = 2 小时)
wazuh_db.check_fragmentation_interval=7200
各参数在源码中的落点:worker_pool_size 决定 main.c 中创建的 worker 线程数;open_db_limit 与 rlimit_nofile 分别控制句柄池上限与 RLIMIT_NOFILE(main.c);fragmentation_threshold、fragmentation_delta、free_pages_percentage、max_fragmentation、check_fragmentation_interval 全部由 GC 线程的碎片检查逻辑(main.c)消费;commit_time_min/commit_time_max 控制 wdb_commit_old() 的事务提交窗口。
5.3 数据库文件位置
- 全局数据库:
/var/wazuh-manager/queue/db/global.db - 代理数据库(4.x 遗留):
/var/wazuh-manager/queue/db/agents/<agent-id>.db - 备份目录:
/var/wazuh-manager/backup/db/
5.4 手动备份与恢复
手动备份 global.db(引自 configuration.md):
# 停止 wazuh-manager 以保证一致性
systemctl stop wazuh-manager
# 如不存在则创建备份目录
mkdir -p /var/wazuh-manager/backup/db/manual
# 复制全局数据库
cp /var/wazuh-manager/queue/db/global.db \
/var/wazuh-manager/backup/db/manual/global-$(date +%Y%m%d-%H%M%S).db
# 重启 wazuh-manager
systemctl start wazuh-manager
从备份恢复:
# 停止 wazuh-manager
systemctl stop wazuh-manager
# 恢复备份(将 TIMESTAMP 替换为实际备份文件名)
cp /var/wazuh-manager/backup/db/global-TIMESTAMP.db \
/var/wazuh-manager/queue/db/global.db
# 恢复属主与权限
chown wazuh:wazuh /var/wazuh-manager/queue/db/global.db
chmod 660 /var/wazuh-manager/queue/db/global.db
# 启动 wazuh-manager
systemctl start wazuh-manager
查看现有备份:
ls -lh /var/wazuh-manager/backup/db/
6. 性能考量与排障
6.1 备份影响与存储估算
- 备份通过 SQLite 的 backup API 生成快照,操作期间性能影响极小,仅在快照创建瞬间对数据库加短锁;
- 每个备份都是数据库完整副本,可用
du -sh /var/wazuh-manager/backup/db/与du -sh /var/wazuh-manager/queue/db/监控占用; - 文档给出的规模参考:<100 代理约 50-200 MB,100-1000 代理约 200 MB-2 GB,1000+ 代理 2 GB 以上。
6.2 常用排障命令
# 确认 wazuh-db 在运行
systemctl status wazuh-manager | grep wazuh-db
# 检查 Socket 是否存在
ls -l /var/wazuh-manager/queue/db/wdb
# 日志跟踪
tail -f /var/wazuh-manager/logs/wazuh-manager.log | grep wazuh-db
# 完整性校验(期望输出 ok)
sqlite3 /var/wazuh-manager/queue/db/global.db "PRAGMA integrity_check;"
常见问题(引自 configuration.md):
- 备份文件未生成:检查磁盘空间、确认
<enabled>yes</enabled>、查看日志报错; - wazuh-db CPU 占用高:降低其他模块的查询频率,或在内部选项中增大
worker_pool_size; - 数据库锁定错误:排查长时查询,增大内部选项中的
commit_time。
此外,针对碎片化还有两个直接的运行时查询可用:global get_fragmentation 返回当前碎片百分比与自由页占比,global vacuum 执行压缩并在 metadata 中记录 vacuum 时间与压缩后碎片值(wdb_parser.c),可作为磁盘空间回收的运维手段。
7. 小结
wazuh-manager-db 是 Wazuh Manager 的存储中枢:Dealer/Worker/GC/Backup 四线程结构保证了连接受理、查询执行、资源回收、数据备份的并行与隔离;<database> <command> [JSON] 的轻量 Socket 协议叠加 ok/err 两态响应,使 remoted、集群、Server API 等组件能以统一方式读写代理注册表与任务库;global.db 与 tasks.db 的表结构(含枚举约束与索引布局)则直接决定了 5.0 中代理生命周期与升级任务的语义。理解上述机制后,配合 <wdb> 备份块与 wazuh_db.* 内部选项,即可对该组件做出与部署规模匹配的备份策略和性能调优。