CentOS 7 基于 rsyslog 搭建集中日志服务器的完整指南
1. 集中日志的刚需凌晨两点翻三台服务器日志的教训先讲个真实的事。有一年我们线上有一个支付接口在半夜报错业务方第二天早上才能复现但当时只有一台机器上有线索。我凌晨两点爬起来挨个登录四台应用服务器用 grep 在 /var/log/messages 和业务日志里翻关键字翻了四十分钟最后发现真正有用的报错在第三台机器上而它的系统时间和另外三台差了整整两分钟导致我把报错顺序完全看反了。那次之后我就下定决心不管团队多小都必须有一套集中日志收集的方案。这个需求的本质不是“把日志收集起来”这么简单而是当机器数量超过三台登录单机翻日志的效率就会断崖式下降日志分散在各自的磁盘上意味着排查故障时你根本没有全局视角。而 rsyslog 作为 CentOS 7 自带的日志转发组件不需要额外安装 agent不需要引入 Java 系的大件框架用最小的成本就能把分散的日志统一送到一台服务器上从时间线、主机维度、服务维度去串联问题。这篇文章写给谁给那些刚接手几台 Linux 服务器的运维新人给还在用“登录每台机器去看日志”这种原始方式的团队也给你自己留一份以后可以照着抄的作业。我会把从环境准备、服务端配置、客户端接入到常见故障排查的完整链路讲清楚包括我在实际部署中踩过的坑和最后采用的方案。2. 选型定调为什么是 rsyslog 而不是 syslog-ng 或 journald在做日志集中这件事之前先搞清楚 CentOS 7 里日志到底是怎么流转的不然你后面会遇到“日志文件里什么都没有”这种诡异问题。2.1 journald 和 rsyslog 各自管什么CentOS 7 用 systemd 接管了系统启动和很多服务管理这里面有个容易混淆的点系统里同时跑着 systemd-journald 和 rsyslogd 两个日志组件。journald 负责把所有 unit 的标准输出、内核日志、系统日志写进自己的二进制日志库然后通过 journalctl 命令查询rsyslog 则是一个传统 syslog 实现它从 /dev/log 这个 socket 读取本地日志消息也通过 imjournal 模块读取 journald 里的日志然后按照 /etc/rsyslog.conf 里的规则把日志分类写入纯文本文件或者转发给远程 rsyslog 服务器。有人会问“那我直接用 journalctl 不就行了为什么还要折腾 rsyslog 转发”这里要分清两个场景的区别单机排查问题journalctl 是非常好用的。它支持按时间、按 unit、按优先级过滤还能查看二进制格式的详细字段。多机集中收集journald 没有原生的“把日志集中到一台远程服务器”的能力。虽然 journald 支持远程日志轮询Journal Remote但是配置成本远高于 rsyslog而且生态里大多数工具仍然以 syslog 协议为主。rsyslog 则是原生就设计为可接收和转发几行配置就能实现。在 CentOS 7 上rsyslog 通过 imjournal 模块读取 journald 的日志再按规则写文件或转发。所以两者不是竞争关系而是配合关系journald 负责采集和本地缓冲rsyslog 负责格式化、分类、转发和集中。2.2 rsyslog 和 syslog-ng 的选择开源 syslog 服务器里rsyslog 和 syslog-ng 是两大主流。syslog-ng 在配置语法和消息解析上有优势但 rsyslog 最大的特点是 CentOS/RHEL 系默认自带而且性能足够覆盖中小规模场景。在 CentOS 7 上安装 rsyslog 基本是零成本的事软件源里直接就有不用引入额外的 yum 源。如果你只是要集中收集几百台机器的系统日志rsyslog 绰绰有余如果你的需求涉及复杂的数据解析、消息改写、多级过滤那 syslog-ng 或者其他重量级方案如 Loki、ELK才值得考虑。我自己的判断是先让 rsyslog 跑起来解决“有和没有”的问题再考虑要不要上更重的方案。很多团队一上来就想上 ELK结果运维成本、机器成本、维护成本全上来了最后日志收集这件事本身变成了一个新的运维负担。合理路径是先有 rsyslog 集中等日志量大了、需要全文检索和可视化的时候再在 rsyslog 前面或后面接 Logstash/Fluentd 等组件。2.3 一个容易被忽略的设计前提日志的格式化和时序一致在配置之前必须明确一个前提集中日志系统的价值依赖日志内的“时间字段”准确。如果每台机器的时间不一致再好的集中方案也会让你在排查问题时被误导。所以环境准备阶段的时间同步配置不是可选项而是基础设施。3. 环境准备VMware 里的 CentOS 7 与网络基线配置很多人在搭建的时候不重视环境准备直接在现网机器上改 rsyslog 配置结果出了问题也不知道是系统环境导致的还是配置导致的。我建议你先在虚拟机里搭一套完整的服务端和客户端环境验证通了再上生产。3.1 VMware 安装 CentOS 7 的几个关键选项在 VMware 里安装 CentOS 7 看起来很简单但实际上有几个选项会影响后面的日志服务器搭建内存至少分配 2GB。日志服务器的内存主要被 rsyslog 的队列和文件缓冲占用如果收集量比较大1GB 会频繁触发交换导致日志写盘抖动。硬盘建议 40GB 以上并且单独划分一个分区给日志存储。这一点我在后面容量规划部分会详细说。网络模式用 NAT 还是桥接如果只是实验环境NAT 没问题方便虚拟机和外网通信但如果要模拟多台机器互相访问建议用桥接或者建一个自定义虚拟网络保证虚拟机之间能互通。安装时选择“最小化安装”不要装 GUI省资源也避免不必要的软件干扰。最重要的一点安装完成后用 nmcli 配置静态 IP。日志服务器最忌讳 IP 频繁变动很多“日志突然收不到了”的问题起因就是 DHCP 分配了新的地址。# 配置静态 IP 的示例以 ens32 为例 nmcli connection modify ens32 ipv4.addresses 192.168.10.100/24 nmcli connection modify ens32 ipv4.gateway 192.168.10.2 nmcli connection modify ens32 ipv4.dns 192.168.10.2 nmcli connection modify ens32 ipv4.method manual nmcli connection up ens323.2 时间同步日志时序一致的生命线这步如果你跳过后面排查问题时一定会后悔。CentOS 7 默认安装了 chronyd只需要确认它开机自启并指向正确的时间服务器即可。systemctl start chronyd systemctl enable chronyd chronyc sources -v然后统一设置时区这一步比大多数人想象的更重要。如果你的机器分散在不同地域有的用 CST 有的用 UTC日志集中的时间线就会乱。我在生产环境里强制所有机器统一使用 Asia/Shanghai 时区。timedatectl set-timezone Asia/Shanghai timedatectl status还要注意修改完时间策略后重启一下 rsyslog 让它读取新的系统时间否则可能出现“当前日志写入昨天的文件”这种问题。3.3 主机名规划日志文件路径里的关键维度日志集中后为了让不同机器的日志不混在一起文件名里一定要包含主机名。所以每台客户端的主机名必须提前规划好别用默认的 localhost。我用的是“机房缩写-角色-编号”的命名方式。hostnamectl set-hostname prod-pay-01 hostnamectl set-hostname prod-app-02不建议在主机名里带空格或特殊字符rsyslog 对主机名的解析基于传统 syslog 协议特殊字符可能导致文件名异常。4. 服务端配置让 rsyslog 从“本地收集”变成“集中接收”环境准备好之后开始动真格的。这里我先讲原理再给配置因为 rsyslog 的配置规则一旦理解后面遇到问题就知道往哪查。4.1 安装与版本确认CentOS 7 的软件源里自带 rsyslog直接安装即可。yum install -y rsyslog rsyslogd -vCentOS 7 自带的 rsyslog 版本通常是 8.x具体小版本可能因系统更新而异这就够了。8.x 版的配置语法兼容旧的 * 号规则写法也支持新的 RainerScript 语法我下面会尽量用兼容性最好的写法。4.2 配置文件结构和工作流程rsyslog 的主配置文件是 /etc/rsyslog.conf默认它会 include /etc/rsyslog.d/ 目录下的所有 .conf 文件。我推荐的实践是不要动默认的 /etc/rsyslog.conf 里面的正规写法而是在 /etc/rsyslog.d/ 下新建一个独立的配置文件这样以后升级或者排查时更清晰。rsyslog 处理消息的流程如下输入模块收集日志imuxsock 收本地、imudp/imtcp 收远程、imjournal 收 journald。经过过滤规则判断日志的 facility来源类型和 priority优先级。匹配到的规则执行对应动作写本地文件、转发到远程、丢弃等。所以配置一个集中日志服务器核心就三件事加载接收模块、定义存储规则和模板、创建目录并保证写权限。4.3 接收模块配置UDP 还是 TCP在 /etc/rsyslog.d/server.conf 中写入# 加载 UDP 和 TCP 接收模块 $ModLoad imudp $UDPServerRun 514 $ModLoad imtcp $InputTCPServerRun 514 # 允许接收远程日志 $AllowedSender UDP, 127.0.0.1, 192.168.10.0/24 $AllowedSender TCP, 127.0.0.1, 192.168.10.0/24UDP 和 TCP 选哪个取决于你的场景特性UDP (514)TCP (514)传输可靠性可能丢包可靠不丢包传输性能高适合高量日志相对低需维护连接应用场景防火墙等设备日志、量大且可容忍丢失的场景业务日志、需要保证完整的场景故障表现远端挂了日志静默丢失连接断开rsyslog 会尝试重连我的建议是一开始两个都开客户端可以根据日志类型自由选择。生产环境如果条件允许优先使用 TCP特别是你计划在客户端上用 RELP 协议做可靠传输时TCP 是你最省事的选择。4.4 日志模板按主机名和日期分目录保存默认情况下远程日志会写进 /var/log/messages 里几台机器混在一起那这集中日志就白做了。必须用模板按主机名区分。# 定义远程日志存储模板 $template RemoteLogs,/var/log/remote/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log # 所有远程来源的日志按模板存储 :source, isequal, remote ?RemoteLogs stop这里有几个容易踩坑的点$template 后面的模板名可以随意但建议有含义。模板字符串中 %HOSTNAME% 是客户端上报的主机名如果客户端没配好 hostname这里会显示成 localhost。“ stop” 表示匹配到远程日志后不再往下处理避免它同时往 /var/log/messages 里也写一份。如果你希望保留一份本地完整记录可以去掉这行但我不建议那样做日志量翻倍对排查没有实质帮助。目录必须手动创建并设置好属主和权限。rsyslog 以 root 身份运行时通常能自动创建目录CentOS 7 的 rsyslog 8.x 默认支持但为了保险起见我建议手动创建并设置权限尤其是你对系统安全有要求、用非 root 用户跑 rsyslog 时。mkdir -p /var/log/remote chown root:root /var/log/remote chmod 755 /var/log/remote如果 rsyslog 以非 root 身份运行还需要usermod -a -G rsyslog rsyslog chown rsyslog:rsyslog /var/log/remote4.5 重启服务并验证监听端口配置写好之后重启 rsyslog然后检查端口是否在监听。systemctl restart rsyslog ss -tlnup | grep -E :(514)正常情况下应该能看到 udp 127.0.0.1:514 和 tcp 127.0.0.1:514 都在监听。如果端口没出现首先确认 iptables/firewalld 是否拦截了 514 端口然后看 rsyslog 的日志journalctl -u rsyslog -n 505. 客户端接入把业务机器日志送上来并验证链路服务端装好只是第一步真正见到效果还要看客户端能不能把日志送上来。这里我用一台 CentOS 7 客户端做演示。5.1 客户端最小配置在客户端的 /etc/rsyslog.conf 末尾追加一行# 把所有日志转发到日志服务器TCP 方式 *.* 192.168.10.100:514注意语法的区别单个 是 UDP两个 是 TCP。这一行的含义是所有等级的日志全部转发给 192.168.10.100 这台服务器的 514 端口。如果你只想转发特定类型的日志修改前面的选择器即可比如只转发认证日志authpriv.* 192.168.10.100:514然后在客户端重启 rsyslogsystemctl restart rsyslog5.2 验证链路用 logger 打一条测试日志服务端和客户端都配好后在客户端执行logger -t mytest hello rsyslog server, this is a test message from client然后到服务端查看对应文件cat /var/log/remote/客户端主机名/当前日期.log如果能看到这条日志说明整条链路已经通了。这里有一个新手最容易困惑的地方我在客户端用 journalctl 能看到这条日志但服务端没收到。原因通常是客户端 rsyslog 进程没重新加载配置或者客户端本身没有把 journald 的日志接进 rsyslog 的流转中。执行 logger 发送的日志是直接写到 /dev/log 的rsyslog 通过 imuxsock 读取所以理论上一定能读到如果这条都没有那问题基本出在 rsyslog 服务本身没跑起来或者端口不通。5.3 理解 CentOS 7 客户端的日志流转链路在 CentOS 7 客户端上日志的流转链路是systemd-journald 从内核、服务标准输出收集日志。rsyslog 通过 imjournal 模块读取 journald 中的日志。rsyslog 根据配置把日志写入本地文件如 /var/log/messages同时通过 规则转发给远程服务器。这个链路意味着你通过 journalctl 能看到日志不代表 rsyslog 一定会转发它。某些特殊字段比如二进制结构化的日志在传统 syslog 协议中可能无法完整表达会被截断或重新格式化。所以在设计客户端时如果你的业务应用直接调 systemd 的 sd_journal_print 写日志建议同时配置应用的日志输出走 syslog 协议保证服务端能完整接收到。5.4 TCP 和 UDP 在生产环境的取舍我前面说过建议两个都开但在客户端侧怎么选我的经验是业务应用日志、认证日志、关键系统日志走 TCP保证不丢。防火墙、网络设备的 syslog 输出、监控系统的高频状态日志走 UDP量大且可容忍少量丢失。还有一点如果走 TCP建议在客户端配置重连和队列缓冲。rsyslog 的队列机制能解决“服务端暂时不可用”时的日志缓冲问题。# 客户端配置带队列缓冲的 TCP 转发 $ActionQueueFileName queue_forward $ActionQueueMaxDiskSpace 1g $ActionQueueSaveOnShutdown on $ActionQueueType LinkedList $ActionResumeRetryCount -1 *.* 192.168.10.100:514这段配置的含义是当服务端不可达时日志先进入本地队列缓存等网络恢复后再补发。$ActionResumeRetryCount 设为 -1 表示无限重试否则默认重试失败次数后日志会被丢弃。6. 避坑实录从“收不到日志”到“日志乱窜”的完整排查链路这里我把实际部署中踩过的坑按“症状-原因-解决办法”整理出来方便你以后遇到类似问题直接对号入座。6.1 防火墙放行 514 端口先问自己服务端防火墙放行 514 端口了吗CentOS 7 默认的 firewalld 会拦截外部对 514 的访问。放行方法firewall-cmd --permanent --add-port514/udp firewall-cmd --permanent --add-port514/tcp firewall-cmd --reload firewall-cmd --list-ports注意如果修改配置后 rsyslog 已经重启但 ss 还是看不到监听端口大概率就是防火墙或者 SELinux 拦截了。这个过程可以用 tcpdump 来确认tcpdump -i any port 514 -nn -vv如果 tcpdump 能看到客户端发来的包但服务端收不到那问题就在服务端本地的防火墙或 SELinux。6.2 SELinux 拦截导致的“静默丢包”这是我最想强调的一个坑。很多人配置完 rsyslog 后发现端口监听正常客户端也显示发送成功但服务端就是没写入文件排查半天发现是 SELinux 在捣乱。CentOS 7 默认开启 SELinuxrsyslog 有一个布尔值控制它能否接收远程日志getsebool -a | grep rsyslog setsebool -P rsyslog_remote_message 1如果不设置这个布尔值rsyslog 的 TCP 接收模块可能被 SELinux 拒绝但日志里不会出现明显的报错。如果日志目录在非标准路径还需要检查 rsyslog 是否有权限写入ls -Z /var/log/remote正常应该看到 system_u:object_r:var_log_t:s0 这样的上下文。如果上下文不对执行semanage fcontext -a -t var_log_t /var/log/remote(/.*)? restorecon -Rv /var/log/remote6.3 日志目录“有权限”但写不进去如果你按我的建议手动创建了 /var/log/remote 目录而且 SELinux 也放行了但日志还是不写入检查一下目录属主。rsyslog 默认以 root 运行所以一般不会出权限问题但如果你出于安全考虑让 rsyslog 以非 root 用户运行就要确保 rsyslog 用户对目录有写权限。这个问题在 CentOS 7 上尤其阴险rsyslog 配置文件里写了目录但系统里根本没这个目录rsyslog 虽然能自动创建但创建的目录属主可能不是预期的用户导致后续写入失败。ls -ld /var/log/remote/*/如果发现目录属主有问题直接用 chown 修正。6.4 主机名导致的“日志乱窜”当你开始接收多台客户端的日志另一个坑会出现多台机器日志明明来自不同主机却写进了同一个文件或者主机名显示为 localhost。这通常是客户端没有正确设置 hostname。hostname如果返回 localhost请在客户端重新设置主机名并重启 rsyslog。另外rsyslog 默认会解析客户端 IP 对应的反向域名DNS 反查如果反查失败可能导致主机名被替换成 IP 或 unknown。为了避免这种情况可以在服务端模板中用 %FROMHOST-IP% 替代 %HOSTNAME%这两种字段的区别%HOSTNAME%客户端在 syslog 消息中上报的主机名。%FROMHOST%服务端根据接收到的来源解析出来的主机名依赖 DNS。%FROMHOST-IP%来源 IP 地址不依赖 DNS最稳定。如果你不想依赖客户端上报的主机名直接按 IP 分目录存可以把模板改成$template RemoteLogs,/var/log/remote/%FROMHOST-IP%/%$YEAR%-%$MONTH%-%$DAY%.log这样至少不会因为 DNS 反查问题导致日志目录名五花八门。6.5 journald 里能查到但 /var/log/messages 为空CentOS 7 上还有个常见问题用 journalctl 能看到系统日志但 /var/log/messages 文件里是空的甚至根本不更新。这个现象通常不是 rsyslog 配置错了而是 rsyslog 的 imjournal 模块没有正确读取 journald 的日志。排查步骤systemctl status rsyslog journalctl -u rsyslog -n 50如果看到类似 “imjournal: begin transaction” 之类的错误或者 rsyslog 服务一直处于 running 状态但不写文件尝试重启 rsyslogsystemctl restart rsyslog如果重启后还不行查看 journald 的状态systemctl status systemd-journaldCentOS 7 默认 journald 把日志写在 /run/log/journal 下内存盘重启后丢失。而 rsyslog 的 imjournal 模块默认读取这个路径。如果你在配置文件中指定了持久化路径但目录没创建也会导致读取失败。处理办法是开启 journald 持久化mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal systemctl restart systemd-journald6.6 时间错乱导致日志文件“跳到明天”这个问题我开头提过。当客户端时间和服务端时间不一致时如果你的模板里用了 %$YEAR% 等系统时间变量日志文件会有两个结果客户端上报时间错乱某些网络设备的 syslog 消息里自带时间戳rsyslog 默认会信任这个消息自带的时间导致日志写入“未来时间”的文件。服务端接收时间错乱如果消息里没有时间戳rsyslog 用服务端系统时间写入这时只要服务端时间是对的就没事。最稳妥的做法是在 rsyslog 配置里强制使用接收时间$ActionFileDefaultTemplate RSYSLOG_FileFormat这行配置让日志文件格式使用 rsyslog 接收消息时的系统时间。不过这只影响写入格式不影响你按 %$YEAR% 这些模板变量来分文件。如果文件目录时间有误多半是客户端消息自带的时间戳影响了 rsyslog 对消息时间的判断。这时可以设置$ActionFileEnableSync on或者更彻底在客户端转发时统一使用本地接收时间。实际上我最终的做法是客户端在转发前先把日志格式统一规范服务端模板用服务端系统时间。这需要在客户端 rsyslog 配置里面对模板做处理比较复杂如果你的日志量不大最简单的办法是确保所有机器用 chrony 同步时间这能解决 95% 的时间错乱问题。6.7 日志量突然增大导致的队列阻塞rsyslog 默认的队列是内存队列大小有限。当同时有多台机器的高量日志打进服务端可能出现 rsyslog 处理不过来导致队列满、日志丢失。解决方法是给主消息队列设置更大的内存队列和磁盘缓冲。$MainMsgQueueSize 50000 $MainMsgQueueTimeoutShutdown 10000 $MainMsgQueueSaveOnShutdown on $WorkDirectory /var/lib/rsyslog$MainMsgQueueSize 是内存队列最大消息数设成 50000 基本够用。如果还怕丢加一行$MainMsgQueueType LinkedList $MainMsgQueueMaxDiskSpace 2g这样当内存队列满时日志会写入磁盘缓冲等处理能力恢复后再补处理有效避免瞬时高负载导致日志丢失。7. 上线后要做的三件事轮转、限速与容量规划日志服务器不是搭完就结束了如果不去管磁盘、轮转和异常流量过段时间你会发现日志文件已经吃掉整个磁盘。7.1 logrotate 配置别让日志文件无限长大默认的 /etc/logrotate.d/syslog 只处理系统日志文件不会处理你在 /var/log/remote 下的日志。需要手动加一个轮转规则。/var/log/remote/*/*.log { daily rotate 30 compress delaycompress missingok notifempty create 0640 root root sharedscripts postrotate /usr/bin/systemctl restart rsyslog 2/dev/null || true endscript }这段配置的含义是每天轮转一次保留 30 天压缩旧日志轮转后重启 rsyslog 让它重新打开新的文件。关于轮转间隔要根据日志量和磁盘大小调整如果一天几百 MB可以 daily 30 天如果一天几 GB可能要 hourly 或者缩短保留天数。日志量评估可以这样估算一台机器每天产生多少日志乘以机器数量再乘以保留天数得出需要预留的空间。7.2 限速配置防止日志风暴打爆服务器有一种比较危险的情况某台客户端因为程序异常开始疯狂输出日志直接把服务端磁盘打满连带影响整个日志集群。rsyslog 默认对每条来源的消息有速率限制但默认值偏宽松建议根据实际规模调紧。$SystemLogRateLimitInterval 10 $SystemLogRateLimitBurst 500这段配置表示在 10 秒内单个来源最多处理 500 条消息超出的消息丢弃。如果你的业务特性是突发日志量很大比如凌晨的定时任务集中执行可以把 burst 调大比如 5000避免误杀正常日志。7.3 磁盘容量规划日志服务器的磁盘规划不能事后补要在上线前就估算好。我用的估算公式是预估单台机器日均日志量 × 需要集中的机器数 × 日志保留天数 × 1.5压缩冗余和文件系统开销举个例子每台机器一天产生 200MB 日志有 20 台机器需要集中保留 30 天那总空间需求是200MB × 20 × 30 × 1.5 180GB如果空间紧张能做的事只有三件减少保留天数、开启压缩logrotate 的 compress 选项、在客户端侧减少日志上报量。不要指望日志服务器上的磁盘能够无限增长这个必须提前达成共识。7.4 与 ELK/Loki 等系统的对接思路rsyslog 集中收集后日志是纯文本文件检索效率并不高。如果你后续需要全文检索、可视化分析可以把 rsyslog 作为日志采集前端然后对接 ELK 或 Loki。常见做法是在服务端用 rsyslog 的 omelasticsearch 模块直接写入 Elasticsearch也可以让 rsyslog 转发给 Logstash由 Logstash 做解析后写入 ES。不过我要泼一盆冷水在日志量没有达到每天几十 GB 之前用 rsyslog 加 grep 其实够用。先想想现阶段到底需要什么别急着引入一堆重型组件。等真的需要全文检索和仪表盘再考虑逐步迁移。这个渐进思路能省掉大量无谓的维护成本。7.5 最后再分享一个小技巧我给所有生产环境日志服务器都加了一个监控项检查最近 5 分钟内 /var/log/remote 下的文件是否有更新。如果超过 5 分钟没有新日志写入说明可能出现了链路故障、客户端断连或服务端配置错误。这个监控的复杂度不高但能帮你在日志链路出问题时第一时间发现而不是等到排查故障时才知道服务器早就不收日志了。# 查看每个远程主机的最近日志写入时间 find /var/log/remote -name *.log -mmin -5 | wc -l如果这个数字长时间为 0就该去查链路了。rsyslog 是个老牌又沉稳的工具用得好的时候你几乎感觉不到它的存在直到某天你需要把一个跨机器的故障链路串起来才会意识到那台安安静静接收日志的服务器替你省了多少事。这大概也是运维工作里最朴素的一份成就感。