首页 / 资讯中心 / 文章详情

CentOS 7 下 Rsyslog + MySQL 集中式日志服务器搭建与调优实战

CentOS 7 下 Rsyslog + MySQL 集中式日志服务器搭建与调优实战 ★ FEATURED ARTICLE
1. 为什么集中式日志服务器是运维绕不开的一环手里管着三五台服务器的时候登录每台机器tail -f /var/log/messages还能应付。一旦机器数量上到十几台、几十台这套做法立刻崩盘——你不可能同时开着几十个终端窗口更不可能在故障发生后逐台回溯时间线。集中式日志服务器要解决的就是这个痛点把所有机器的系统日志、服务日志统一收集到一个地方集中存储、集中检索、集中告警。Rsyslog 是 Linux 世界最成熟的日志转发与收集组件几乎所有的发行版都预装了它。它基于传统的 syslog 协议但做了大量增强支持 TCP 传输、支持 RELP 可靠传输、支持模板自定义格式、支持把日志直接写进数据库。MySQL 作为落地存储好处是查询灵活、可以用 SQL 做聚合统计、方便和现有的监控或报表系统对接。这套组合的典型适用场景包括中小规模服务器集群的系统日志归集、等保合规要求的日志留存、故障排查时的跨机时间线还原、以及安全审计场景下的操作记录集中化。整套方案全部基于开源组件不依赖任何商业软件一台 2 核 4G 的虚拟机就能撑起几十个节点的日志收集。我下面要讲的这套搭建流程是在 CentOS 7 环境下从零开始把 Rsyslog 服务端、MySQL 数据库、日志接收规则、入库模板、日志轮转、性能调优这一整条链路全部打通。每一步我都会说清楚为什么这么做以及实际跑起来之后容易在哪里翻车。2. 动手之前必须想清楚的几个设计决策2.1 传输协议选 UDP、TCP 还是 RELP这是搭建日志服务器第一个要拍板的事选错了后面全是坑。UDP 是 syslog 的传统默认协议优点是开销小、发送端不需要等待确认。但它的致命问题是不保证送达——网络拥塞、接收端缓冲区满了日志包直接丢掉发送端毫不知情。对于合规审计场景丢日志等于埋雷。TCP 解决了可靠性问题发送端和接收端建立连接后再传数据有重传机制。但 TCP 的坑在于如果接收端 Rsyslog 服务重启发送端在连接断开期间产生的日志仍然会丢因为 syslog over TCP 没有应用层的消息队列。RELPReliable Event Logging Protocol是 Rsyslog 自己搞的可靠传输协议它在应用层做了确认和重传接收端确认收到之后发送端才删除本地缓冲。这是三种方案里最可靠的但配置也最复杂需要额外加载omrelp和imrelp模块。我的建议是内网环境、对可靠性要求不是极端苛刻的场景用 TCP 就够了。RELP 留给金融、医疗这类对日志完整性有硬性合规要求的场景。UDP 除非你有特殊理由否则不要用在集中收集场景。2.2 日志存文件还是存数据库很多人一上来就想把所有日志塞进 MySQL觉得这样查询方便。但这里有个容易忽略的事实系统日志的量级可能远超你的预期。一台普通业务服务器/var/log/messages加上secure、cron等一天产生几十万条日志很正常。几十台机器汇总起来一天就是千万级记录。MySQL 单表到千万级之后如果没有做好分区和索引查询会明显变慢写入也可能成为瓶颈。所以更务实的做法是分层存储高频访问的近期日志比如 7 天存 MySQL 方便查询全量日志同时以文件形式落盘做长期归档。Rsyslog 支持一份日志同时走多个 output action配置起来并不复杂。2.3 数据库表结构怎么设计Rsyslog 官方提供了一套标准的 MySQL 表结构核心是SystemEvents表。但直接用官方结构有几个问题字段类型偏保守比如Message用的是text实际可能很长、没有分区、索引不一定贴合你的查询模式。我实际用的做法是在官方结构基础上做三处调整把Message改成mediumtext防止长日志被截断、按ReceivedAt做按月分区、给FromHost和SysLogTag加联合索引。这些调整后面会给出具体 SQL。3. 服务端环境准备与 MySQL 落地3.1 系统基础配置先确认系统版本和 Rsyslog 版本。CentOS 7 默认带的 Rsyslog 是 8.x 系列功能已经足够。cat /etc/redhat-release rsyslogd -v输出里要重点关注两个东西一是版本号8.x 以上才支持我们后面要用的ommysql模块的完整功能二是看Compiled with那一行列出的模块确认ommysql在列表里。如果不在说明需要额外安装rsyslog-mysql包。yum install -y rsyslog-mysql这个包会装上ommysql.so模块和一套建表 SQL 脚本脚本通常在/usr/share/doc/rsyslog-*/mysql-createDB.sql。注意路径里的版本号可能不同用rpm -ql rsyslog-mysql可以精确找到。关闭 SELinux 或者配置放行规则。生产环境我不建议直接关 SELinux但 Rsyslog 写 MySQL 涉及网络连接和文件写入SELinux 默认策略会拦。最省事的验证方式是先设成 permissive 模式跑通再根据 audit 日志补策略。setenforce 0防火墙要放行 Rsyslog 的接收端口。如果选了 TCP默认是 514如果选了 RELP默认是 20514。firewall-cmd --permanent --add-port514/tcp firewall-cmd --reload3.2 MySQL 安装与安全初始化CentOS 7 的 yum 源里 MySQL 版本偏老我一般用官方社区版仓库。rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm yum install -y mysql-community-server systemctl start mysqld systemctl enable mysqldMySQL 8 首次启动会生成一个临时 root 密码在/var/log/mysqld.log里。grep temporary password /var/log/mysqld.log拿到临时密码后执行安全初始化这一步会要求改 root 密码、删除匿名用户、禁止 root 远程登录。注意 MySQL 8 默认的密码策略要求大小写字母加数字加特殊字符长度至少 8 位设太简单的密码会直接报错。mysql_secure_installation3.3 创建日志库和专用账号不要用 root 账号让 Rsyslog 连数据库这是基本的安全习惯。创建一个只对日志库有写权限的专用账号。CREATE DATABASE Syslog CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER rsysloglocalhost IDENTIFIED BY YourStrongPass123!; GRANT INSERT, SELECT ON Syslog.* TO rsysloglocalhost; FLUSH PRIVILEGES;这里只给了INSERT和SELECT因为 Rsyslog 写入时只需要插入偶尔做健康检查需要查询。不给UPDATE和DELETE是为了防止程序异常时误删数据。3.4 建表与分区设计官方脚本建的表结构如下我做了字段类型调整USE Syslog; CREATE TABLE SystemEvents ( ID int unsigned NOT NULL AUTO_INCREMENT, CustomerID bigint DEFAULT NULL, ReceivedAt datetime NULL, DeviceReportedTime datetime NULL, Facility smallint NULL, Priority smallint NULL, FromHost varchar(60) NULL, FromIP varchar(60) NULL, Message mediumtext, NTSeverity int NULL, Importance int NULL, EventSource varchar(60), EventUser varchar(60), EventCategory int, EventID int, EventRawMessage text, InfoUnitID int, SysLogTag varchar(60), EventLogType varchar(60), GenericFileName varchar(60), SystemID int, PRIMARY KEY (ID), KEY idx_host_tag (FromHost, SysLogTag), KEY idx_received (ReceivedAt) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;分区这块MySQL 8 支持按RANGE对ReceivedAt做分区。但要注意分区键必须是主键的一部分所以如果按ReceivedAt分区主键得改成(ID, ReceivedAt)的复合主键。这个改动会影响自增行为需要权衡。如果日志量不是特别大日均百万级以下我建议先不分区靠索引撑住等真的到瓶颈了再改。4. Rsyslog 服务端配置逐行拆解4.1 加载模块与全局参数Rsyslog 的配置文件在/etc/rsyslog.conf模块加载部分在文件顶部。需要确保这几行没有被注释掉module(loadimtcp) module(loadommysql)imtcp是 TCP 接收模块ommysql是 MySQL 输出模块。如果选了 RELP还要加module(loadimrelp)全局参数部分有几个直接影响性能和稳定性的设置$WorkDirectory /var/lib/rsyslog $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat $FileOwner root $FileGroup adm $FileCreateMode 0640 $DirCreateMode 0755 $Umask 0022 $IncludeConfig /etc/rsyslog.d/*.conf$WorkDirectory是 Rsyslog 的工作目录磁盘队列文件会放在这里。如果日志量大这个目录所在的磁盘要有足够空间。$FileCreateMode 0640保证日志文件权限不会太开放符合安全基线要求。4.2 接收规则与模板定义接收规则决定哪些日志被接收、以什么格式处理。我一般把自定义配置放在/etc/rsyslog.d/下单独的文件里方便管理。# /etc/rsyslog.d/10-remote.conf $ModLoad imtcp $InputTCPServerRun 514 # 定义入库模板 $template MySQLInsert,insert into SystemEvents \ (ReceivedAt, FromHost, FromIP, Priority, Facility, SysLogTag, Message) \ values (%timereported:::date-mysql%, %fromhost-ip%, %fromhost-ip%, \ %pri%, %syslogfacility%, %syslogtag%, %msg:::escape-html%), SQL # 所有远程日志走 MySQL if $fromhost-ip ! 127.0.0.1 then { action(typeommysql serverlocalhost dbSyslog \ uidrsyslog pwdYourStrongPass123! \ templateMySQLInsert) stop }这里有几个关键点值得展开说。%timereported:::date-mysql%这个属性替换器把日志时间转成 MySQL 能识别的datetime格式。如果不加date-mysql修饰符Rsyslog 会输出它自己的时间格式MySQL 插入时会报错或者存成0000-00-00。%msg:::escape-html%对消息内容做转义防止日志里包含单引号导致 SQL 语法错误。这是必须加的否则一条带单引号的日志就能让整个插入失败。更严谨的做法是用参数化查询但 Rsyslog 的ommysql模块不支持参数绑定只能靠转义。if $fromhost-ip ! 127.0.0.1这个条件是为了避免本机日志也走远程接收路径造成重复。本机日志应该由本地规则处理远程日志才走这个分支。4.3 本地日志同时落文件和入库远程日志走 MySQL 之后本机日志也要处理。我通常让本机日志既写文件又入库双保险。# 本机日志入库 $template LocalMySQLInsert,insert into SystemEvents \ (ReceivedAt, FromHost, FromIP, Priority, Facility, SysLogTag, Message) \ values (NOW(), localhost, 127.0.0.1, %pri%, %syslogfacility%, \ %syslogtag%, %msg:::escape-html%), SQL if $fromhost-ip 127.0.0.1 then { action(typeommysql serverlocalhost dbSyslog \ uidrsyslog pwdYourStrongPass123! \ templateLocalMySQLInsert) }注意本机日志的ReceivedAt我用了NOW()因为本机日志的时间就是当前时间用数据库函数更准确。远程日志用%timereported%是因为日志从远端发过来可能有延迟用日志本身的时间戳更符合审计要求。4.4 队列配置防止日志丢失Rsyslog 默认使用内存队列服务重启或者队列满了日志就丢了。生产环境必须配磁盘辅助队列。$ActionQueueType LinkedList $ActionQueueFileName mysqlqueue $ActionQueueMaxDiskSpace 1g $ActionQueueSaveOnShutdown on $ActionResumeRetryCount -1$ActionQueueType LinkedList用链表做内存队列性能比固定数组好。$ActionQueueFileName指定磁盘队列文件名队列溢出时写到磁盘。$ActionQueueSaveOnShutdown on保证服务关闭时把内存队列刷到磁盘。$ActionResumeRetryCount -1表示 MySQL 连不上时无限重试不会丢日志。这套配置的代价是如果 MySQL 长时间不可用磁盘队列会一直涨最多涨到$ActionQueueMaxDiskSpace设定的 1G。所以监控磁盘队列文件大小是必要的。5. 客户端配置与日志转发验证5.1 客户端最小化配置客户端只需要把日志转发到服务端配置很简单。在/etc/rsyslog.d/下新建文件# /etc/rsyslog.d/90-forward.conf *.* 192.168.1.100:514表示用 TCP 转发单个是 UDP。这一行的意思是把所有 facility、所有 priority 的日志都转发到192.168.1.100的 514 端口。如果只想转发特定类型的日志可以精确指定*.info;mail.none;authpriv.none;cron.none 192.168.1.100:514这行的意思是转发 info 及以上级别的日志但排除 mail、authpriv、cron 这几类。排除的原因通常是这些日志量特别大或者有隐私顾虑。5.2 客户端队列也要配客户端同样需要队列配置否则服务端不可达时日志会丢。$ActionQueueType LinkedList $ActionQueueFileName fwdRule1 $ActionQueueMaxDiskSpace 512m $ActionQueueSaveOnShutdown on $ActionResumeRetryCount -1客户端队列可以比服务端小一些512M 通常够用。$ActionResumeRetryCount -1同样表示无限重试。5.3 验证链路是否打通配置改完两端都要重启 Rsyslogsystemctl restart rsyslog然后在客户端手动发一条测试日志logger -p local0.info test log from client $(hostname)到服务端 MySQL 里查SELECT ID, ReceivedAt, FromHost, SysLogTag, Message FROM SystemEvents WHERE Message LIKE %test log from client% ORDER BY ID DESC LIMIT 5;如果查不到按这个顺序排查客户端systemctl status rsyslog看有没有报错、服务端ss -tlnp | grep 514看端口有没有监听、服务端tcpdump -i any port 514 -nn看有没有收到包、MySQL 里SHOW PROCESSLIST看 rsyslog 账号有没有连接进来。6. 性能调优与日常维护的实战经验6.1 批量插入比逐条插入快一个数量级Rsyslog 的ommysql模块默认是逐条插入每条日志一个INSERT语句。日志量大的时候MySQL 的 QPS 会被打满。解决办法是开启批量模式$ActionQueueType LinkedList $ActionQueueDequeueBatchSize 100$ActionQueueDequeueBatchSize 100表示每次从队列取 100 条一起处理。配合ommysql的批量插入能力写入性能能提升 5 到 10 倍。这个参数需要根据实际日志量调整太小没效果太大内存占用高。6.2 表数据清理不能靠 DELETE日志表会无限增长必须定期清理。但直接DELETE FROM SystemEvents WHERE ReceivedAt DATE_SUB(NOW(), INTERVAL 30 DAY)在千万级表上会锁表很久还可能把 MySQL 的 undo log 撑爆。正确做法是用分区表清理时直接ALTER TABLE ... DROP PARTITION秒级完成。如果没做分区退而求其次用pt-archiver这类工具分批删除每次删几千条加--sleep参数控制节奏。我实际用的分区方案是按月分区ALTER TABLE SystemEvents PARTITION BY RANGE (TO_DAYS(ReceivedAt)) ( PARTITION p202401 VALUES LESS THAN (TO_DAYS(2024-02-01)), PARTITION p202402 VALUES LESS THAN (TO_DAYS(2024-03-01)), PARTITION p202403 VALUES LESS THAN (TO_DAYS(2024-04-01)), PARTITION pmax VALUES LESS THAN MAXVALUE );每月初加一个新分区同时删掉最老的分区。这个操作可以写成脚本挂到 crontab 里。6.3 监控这几个指标就够了日志服务器本身也需要被监控。我关注这几个指标指标获取方式告警阈值磁盘队列文件大小du -sh /var/lib/rsyslog/*.qi超过 500MMySQL 写入延迟SHOW GLOBAL STATUS LIKE Innodb_row_lock_time_avg超过 100ms日志表行数SELECT COUNT(*) FROM SystemEvents超过 5000 万Rsyslog 进程内存ps auxgrep rsyslog接收端口连接数ss -tn state established ( sport :514 )wc -l磁盘队列文件持续增长说明 MySQL 写入跟不上要么是 MySQL 性能问题要么是日志量突增。日志表行数到 5000 万就该考虑清理或者分表了。6.4 几个我踩过的坑坑一时区问题导致日志时间错乱。Rsyslog 默认用 UTC 时间MySQL 用系统时区两边不一致时入库时间会差 8 小时。解决办法是在 Rsyslog 配置里加$ActionFileDefaultTemplate时指定时区或者统一都用 UTC。坑二日志内容里的特殊字符导致插入失败。除了单引号反斜杠、换行符也会出问题。escape-html能解决大部分但换行符需要额外处理。我后来在模板里加了%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%来去掉末尾换行。坑三MySQL 连接数被打满。Rsyslog 每个 action 会维持一个连接如果配置了多个 action 或者有重试逻辑连接数可能失控。在 MySQL 里给 rsyslog 账号设MAX_USER_CONNECTIONS限制同时在 Rsyslog 里配$ActionQueueDequeueBatchSize减少连接频率。坑四SELinux 拦截导致模块加载失败。ommysql模块需要网络连接权限SELinux 默认策略会拦。用ausearch -m avc -ts recent看有没有相关拒绝记录有的话用audit2allow生成策略模块。7. 从单机到集群的扩展思路单台日志服务器撑几十个节点没问题但节点数上百之后单机的磁盘 IO 和 MySQL 写入都会成为瓶颈。这时候有几个扩展方向。方向一读写分离。日志写入走主库查询走从库。Rsyslog 只往主库写运维人员查日志连从库。MySQL 主从复制配置简单对 Rsyslog 配置零改动。方向二按业务分库。不同业务线的日志写到不同的库或不同的表减轻单表压力。Rsyslog 支持根据$syslogfacility或$fromhost-ip动态选择 output action配置多个ommysqlaction 即可。方向三引入消息队列缓冲。Rsyslog 先把日志写到 Kafka 或 Redis再由消费者程序批量写入 MySQL。这样 Rsyslog 和 MySQL 解耦MySQL 维护时日志不丢。代价是架构复杂度上升需要额外维护消息队列和消费者。方向四换用 Elasticsearch。如果查询需求复杂全文检索、聚合分析、可视化MySQL 不是最优选择。Rsyslog 支持omelasticsearch模块直接把日志写进 ES。但 ES 的资源消耗比 MySQL 大得多小规模场景不划算。我的建议是先用单机 MySQL 方案跑起来等真的遇到瓶颈再考虑扩展。很多团队一上来就上 ELK 全家桶结果维护成本远超收益。日志系统的核心价值是日志不丢、能查到不是技术栈先进。8. 写在最后的一点个人体会这套 CentOS 7 Rsyslog MySQL 的方案我从几年前开始在不同规模的环境里反复搭过。最大的感受是日志服务器的难点不在搭建而在长期稳定运行。搭建可能半天就搞定了但后面会遇到磁盘满了、MySQL 慢了、日志格式变了、某台机器时间不对了各种问题。所以搭建的时候就要把监控和清理机制一起做好不要等出问题了再补。磁盘队列大小、日志表行数、MySQL 写入延迟这三个指标一定要监控起来。清理脚本一定要提前写好挂到 crontab 里不要等到表几千万行了才想起来清理。另外Rsyslog 的配置文件语法在不同版本之间有差异CentOS 7 自带的 8.x 版本用的是新的 RainerScript 语法网上很多老教程用的是旧的$指令语法混用会出问题。遇到配置不生效先rsyslogd -N1检查语法这个命令会告诉你哪一行有问题。最后说一个实用技巧调试阶段把 Rsyslog 的前台日志级别调高能看到每条日志的处理路径。在配置文件里加$DebugFile /var/log/rsyslog-debug.log和$DebugLevel 2重启后就能看到详细的处理日志。调完记得关掉不然日志文件会涨得很快。
阅读完成 · 觉得有帮助?
咨询建站