为什么个人站长也需要一台日志集中服务器
很多个人站长在一台 VPS 上跑着 Nginx、PHP-FPM、MySQL,出了故障第一反应是 SSH 上去翻 tail -f。问题是:一旦服务器被打挂、被入侵、或者干脆起不来,你连不上去,日志就全部锁在那台机器里。更麻烦的是当你手上有两三台机器(主站一台、图床一台、备份一台),排查一个跨机请求要从三个终端里来回对照时间戳,效率极低。
我之前一直用 journalctl 和本地文件日志凑合,直到有一次主站磁盘写满导致 sshd 都登不上,我才意识到日志必须离开故障机、单独存放。这篇文章讲的就是用 rsyslog 搭一台集中日志服务器:不改包、不占内存、Debian/Ubuntu 默认就装好了,几百台机器的场景也够用。
方案选型:为什么是 rsyslog 而不是 ELK
先说清楚取舍,避免你走弯路。常见的日志集中方案有三类:
- rsyslog / syslog-ng:系统自带、纯 C 实现、内存占用几 MB、支持 TCP/TLS 转发与落盘,天然适合"把日志原样搬到一台机器上存着"这种需求。
- Elasticsearch/Loki 全家桶:查询能力强大,但要额外吃几百 MB 到几 GB 内存,个人站的小机器上性价比极低。
- Vector / Promtail:新一代采集器,适合结构化管道与脱敏,但需要额外的部署维护成本。
我的建议是:如果你只是要"日志不丢、能按主机和时间检索、能做告警",rsyslog 是最省心的。等你有真正的全文检索需求,再在 rsyslog 落盘的目录上叠加 Vector 或 Loki 也不迟——rsyslog 完全可以把日志同时写文件(供 Vector 读取)和转发。
架构设计:客户端、服务端与端口
一个最小可用的集中日志架构只有两个角色:
- 日志服务端(collector):监听 UDP/TCP 514 或自定义端口,把收到的日志按来源主机分目录落盘,配置
logrotate轮转。 - 日志客户端:每台业务机上的 rsyslog 把本地日志转发过去。
端口选择上有两个坑:一是标准 514/udp 在部分云厂商那里被限速甚至封禁,且 UDP 会丢包;二是在容器或 NAT 环境里 UDP 的源 IP 容易变成网关地址,导致你分不清日志来自哪台机器。所以我强烈建议:内网用 TCP 514,公网或跨机房用 TCP 加 TLS 走 10514。
服务端配置:按主机分目录落盘
服务端假设是一台只做日志的 1 核 1G 小机器。先确认 rsyslog 已安装:
apt install -y rsyslog
rsyslogd -v | head -3然后创建自定义配置。Debian/Ubuntu 上不要直接改 /etc/rsyslog.conf,而是放一个独立文件进 /etc/rsyslog.d/,升级时不冲突:
cat > /etc/rsyslog.d/10-remote.conf <<'EOF'
# 加载 TCP 接收模块
module(load="imtcp")
# 允许的客户端网段,防止被公网随便灌日志
input(type="imtcp" port="514" ruleset="remote")
# 基于来源 IP 做 rDNS,没有内网 DNS 时可关掉以省开销
# global(net.enableDNS="off")
# 模板:/var/log/remote/来源IP/年-月-日/程序名.log
template(name="RemotePerHost" type="string"
string="/var/log/remote/%FROMHOST-IP%/%$YEAR%-%$MONTH%-%$DAY%/%programname%.log")
ruleset(name="remote") {
# 过滤掉本地噪声,只收远程
if $fromhost-ip != '127.0.0.1' then {
action(type="omfile" dynaFile="RemotePerHost"
dirCreateMode="0755" fileCreateMode="0640"
fileOwner="syslog" fileGroup="adm")
# 同时保留一份原始流,方便日后接入 Vector
action(type="omfile" file="/var/log/remote/all.log")
}
}
EOF
systemctl restart rsyslog
systemctl status rsyslog --no-pager几个关键点解释一下。%programname% 会生成 sshd.log、nginx.log 之类的文件,比笼统一个大文件好定位;如果你更想要"一台机器一天一个文件",把模板换成 /var/log/remote/%FROMHOST-IP%/%$YEAR%-%$MONTH%-%$DAY%.log 即可。dirCreateMode 和 fileCreateMode 是必须显式写的,默认权限由 umask 决定,可能让日志文件对所有用户可读——生产环境里日志可能含敏感信息(登录名、路径),不该是世界可读的。
防火墙与权限:先堵住公网灌日志的口子
裸奔的 514 端口会让任何人往你服务器写日志,轻则磁盘被灌满(本质是一次 DoS),重则伪造日志干扰你排查。所以在服务端一定要先设防火墙白名单:
# 只允许业务机网段访问 TCP 514
ufw allow from 10.8.0.0/24 to any port 514 proto tcp
ufw deny 514/tcp
ufw deny 514/udp
ufw status verbose如果走公网,务必上 TLS。rsyslog 支持基于 gtls 的加密传输,用自签 CA 即可:
# 服务端
module(load="imtcp" StreamDriver.Name="gtls"
StreamDriver.Mode="1" StreamDriver.AuthMode="x509/name")
input(type="imtcp" port="10514" ruleset="remote")
# 生成自签 CA 与证书
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \
-keyout ca.key -out ca.crt -subj "/CN=log-ca"
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
-subj "/CN=logserver.internal"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 1825 -out server.crt
cat server.crt server.key ca.crt > /etc/rsyslog.d/logserver.pem
chmod 600 /etc/rsyslog.d/logserver.pem客户端配置:把本地日志推过去
业务机上加一段转发规则。注意要和本地落盘并存,别只转发不落盘——网络抖动的瞬间你会同时失去本地和远程两份日志:
cat > /etc/rsyslog.d/90-forward.conf <<'EOF'
# 队列参数:网络断了先缓冲在内存,满了再落盘
$ActionQueueType LinkedList
$ActionQueueFileName fwdRule1
$ActionQueueMaxDiskSpace 1g
$ActionQueueSaveOnShutdown on
$ActionResumeRetryCount -1
# TLS 客户端
$DefaultNetstreamDriverCAFile /etc/rsyslog.d/ca.crt
$ActionSendStreamDriver gtls
$ActionSendStreamDriverMode 1
$ActionSendStreamDriverAuthMode x509/name
$ActionSendStreamDriverPermittedPeer logserver.internal
# 只转发这些设施,避免把 auth 之外的全推过去
*.* @@logserver.internal:10514
EOF
systemctl restart rsyslog这里的 @@ 表示 TCP,单写 @ 表示 UDP——这是 rsyslog 里最容易记混的一处语法。$ActionResumeRetryCount -1 表示无限重试,配合 SaveOnShutdown on,即使 logserver 重启,客户端也会把日志缓冲住,恢复后补发,这是很多人漏掉的关键配置。
磁盘保护:日志服务器最怕被写满
集中日志服务器最大的风险不是丢日志,而是被日志撑爆磁盘——一旦写满,rsyslog 自己都可能写不动,你就彻底瞎了。三件事必须做:
- 给
/var/log单独一个分区或 volume,别和系统盘混在一起。 - 配置 logrotate,按天轮转、压缩、保留 30 天。
- 加一个磁盘用量告警,到了 80% 就提醒。
cat > /etc/logrotate.d/remote-logs <<'EOF'
/var/log/remote/*/*/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 syslog adm
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
EOF
# 手动演练一次,确认不报错
logrotate -d /etc/logrotate.d/remote-logs注意 postrotate 里调用的是 rsyslog-rotate,它会给 rsyslog 发信号让它重开文件句柄。如果你漏了这一步,会出现"文件切了但磁盘没变小"——因为 rsyslog 还在往已改名(已被删除 inode)的老文件里写,这个坑我在另一篇讲 logrotate 的文章里详细写过。
验证与排查:确认日志真的到了
配完先做一次端到端验证,别等出事才发现根本没通:
# 在业务机上发一条测试日志
logger -p user.info "zz1984-connectivity-test $(date +%s)"
# 在服务端按主机找文件
find /var/log/remote -name '*.log' -newermt '-2 minutes' | head
grep -r "zz1984-connectivity-test" /var/log/remote/ | tail -3
# 看 TCP 握手与错误
ss -tnp | grep :514
journalctl -u rsyslog -n 50 --no-pager | grep -i -E 'error|queue|refused'没收到日志时按这个顺序查:一、防火墙是否放行来源 IP;二、客户端 @@ 的目标端口和主机名是否解析正确;三、服务端 imtcp 是否真的加载(rsyslogd -N1 可做语法检查);四、SELinux/AppArmor 是否拦了 rsyslog 写新目录。AppArmor 在 Ubuntu 上默认会给 rsyslog 一个路径白名单,你新建 /var/log/remote 后如果写不进去,八成是它——在 /etc/apparmor.d/local/usr.sbin.rsyslogd 里加一行路径再 reload 即可。
一套更省事的替代思路
如果你不想在客户端装一堆配置,还有两个务实的做法:一是用 systemd-journal-upload,它把 journald 的日志直接推给远端的 systemd-journal-remote,天然带二进制索引与 HMAC 校验;二是只在客户端开 imfile 模块把指定日志文件(比如 Nginx access.log)作为"新文件"读进来再转发,比用 syslog 设施过滤更直观。rsyslog 的 imfile 写法大致是:
module(load="imfile")
input(type="imfile" File="/var/log/nginx/access.log"
Tag="nginx-access" Severity="info" Facility="local5"
PersistStateInterval="200")这样 Nginx 的访问日志也会被搬走,配合服务端按 Tag 分文件,你就能在日志服务器上同时看到系统日志、SSH 登录、Nginx 访问与 PHP 错误,一台机器管全站。
进阶:把关键日志变成能告警的信号
日志存下来只是第一步,真正的价值在于让它主动告诉你出事了。个人站没必要上整套告警平台,用最土的 logcheck 或者自己写个定时脚本扫关键字就够。核心思路是:把"值得你半夜爬起来看"的日志模式列出来,用 cron 每分钟扫一遍新写入的日志,命中就发一封邮件或者推送一条消息到手机上。
cat > /usr/local/bin/logwatch.sh <<'EOF'
#!/bin/bash
PATTERNS='Failed password|authentication failure|Invalid user|segfault|OOM|out of memory|Banned|error while loading'
LOG=/var/log/remote/all.log
STATE=/var/lib/logwatch.offset
[ -f "$STATE" ] || echo 0 > "$STATE"
START=$(cat "$STATE")
TOTAL=$(wc -l < "$LOG")
if [ "$TOTAL" -lt "$START" ]; then START=0; fi
tail -n +$((START+1)) "$LOG" | grep -Ei "$PATTERNS" | \
while read -r line; do
logger -t logwatch "$line"
done
echo "$TOTAL" > "$STATE"
EOF
chmod +x /usr/local/bin/logwatch.sh
# 每分钟跑一次
echo '* * * * * root /usr/local/bin/logwatch.sh' > /etc/cron.d/logwatch这段脚本里有个容易踩的坑:不能用 grep 每次扫全文件——日志大了之后每次都读几百 MB,几分钟就把小机器的 CPU 吃满。所以要用一个 offset 文件记录"上次读到第几行",只处理新增的部分。另外注意日志轮转之后行号会重置,脚本里那句 if [ "$TOTAL" -lt "$START" ] 就是处理这种情况的兜底,不加的话轮转当天会静默漏掉所有日志。
关于保留策略与合规的几句提醒
日志里会记录访客 IP、User-Agent、登录用户名、访问路径,这些在多数司法辖区里属于个人信息。个人站不必过度紧张,但有两点建议:一是只保留你真正会看的时长,30 天对绝大多数个人站足够,保留三年既无用处又徒增风险;二是如果站点面向欧洲用户,日志里最好把完整 IP 做个末段掩码(比如 203.0.113.x),既不影响排查攻击来源,又能降低合规压力。rsyslog 可以用 mmfields 或 mmnormalize 模块在落盘前做简单的字段替换,也可以退一步,用前面提到的 Vector 管道在读取时做脱敏。
还有一个现实问题:日志服务器本身要备份吗? 我的答案是"不必全量备份,但关键日志要"。全站日志动辄几十 GB,每天备份不现实;但安全相关的日志(SSH 登录、认证失败)体积极小,单独转发到第二个位置、保留一年,是很划算的。真出事的时候,这几 MB 的认证日志往往比几十 GB 的访问日志更关键。
写在最后
集中日志的价值不在于"能查日志",而在于故障时你依然看得见。服务器被 DDoS 打挂、磁盘写满登不上去、甚至被入侵后攻击者 history -c 清痕迹——只有离开本机的日志才靠得住。这套 rsyslog 方案在 1G 内存的小机器上跑一年也不会有存在感,却能在关键时刻救你一次。建议这周就先把服务端搭起来,客户端从主站开始接,跑顺了再铺到其他机器。