第十二章 —— WordPress安全实战指南 · 日志聚合

📌 第四部分:部署日志聚合

运行一个 WordPress 网站,最大的挑战之一就是安全防护。在这个攻击自动化、扫描泛滥的时代,仅靠防火墙和插件很难防住那些“聪明”的黑客。我们需要一个更全面的防线,而日志,正是揭示攻击者轨迹的第一现场。

但传统的日志分析方式——手动翻阅 Nginx、PHP、WordPress 生成的各类日志文件,既效率低、又容易遗漏。你可能根本不知道网站正遭受暴力破解,直到数据库里多了一个陌生的管理员用户。

这时候,日志聚合就派上用场了。

为什么需要日志聚合?

日志聚合,就是把分散在不同服务(如 Nginx、PHP、WordPress、系统日志等)中的数据集中存储、统一分析。它的优点包括:

  • 统一检索:无论是查找登录失败,还是排查 502 错误,只需一次搜索。
  • 结构化分析:结合图表、时间线,迅速看出问题模式。
  • 历史归档:支持长期保存,可对比趋势、分析安全态势。
  • 结合告警系统:检测到异常行为(如同一 IP 连续登录失败),自动提醒甚至触发阻断。

而传统的日志处理方式,往往:

  • ❌ 日志分散,难以统一检索;
  • ❌ 缺乏图形化界面,分析全靠肉眼;
  • ❌ 无法实时告警,安全响应滞后;
  • ❌ 对于非专业人员不够友好,配置复杂。
主流日志聚合方案对比

当前主流的日志聚合方案包括:

方案组件组成优点缺点适用场景
ELK (Elasticsearch + Logstash + Kibana)三大组件协同工作功能强大、搜索强、可视化出色占用资源大、配置复杂大中型网站或企业级应用
Grafana + Loki + Alloy/PromtailLoki 收集日志,Grafana 可视化,Alloy/Promtail 做采集器安装轻量、支持云原生、查询语法简单日志搜索能力略弱于 ELK中小型网站推荐
Prometheus + Grafana + Exporters更偏向指标监控(CPU、内存等)系统性能监控强、扩展灵活非日志专用,需要配合其他组件如 Loki适合做性能 + 安全综合监控

对于中小型 WordPress 网站,推荐使用 Grafana + Loki + Alloy/Promtail

  • 部署相对简单,资源占用较低;
  • 查询语法(LogQL)上手快;
  • 与 Grafana 配合紧密,可直接做出告警规则;
  • 支持实时搜索 + 日志可视化。

本指南将逐步带你完成:

  • 日志聚合方案部署(本篇);
  • Grafana 中的日志查询和告警设置(日志监控篇);

无论你是站长、开发者,还是系统管理员,相信你都能从中获得实战价值。

部署Grafana+Loki+Alloy

在众多日志聚合方案中,Grafana + Loki + Alloy(或 Promtail) 是近年来发展最快的轻量级组合,尤其适合中小型网站、个人服务器或容器化环境。

✨ 特点与优势

轻量部署、资源占用小

  • Loki 是为日志聚合而生的“时间序列日志数据库”,相比 ELK 占用资源少得多。
  • 不依赖 Java、无需复杂堆栈,安装简单,运行稳定。

完美集成 Grafana

  • 同一 Grafana 面板可同时展示:系统指标(Prometheus)+ 日志(Loki)+ 网络状态(Blackbox)。
  • 查询语法统一,界面友好。

Alloy:新一代全能采集器

  • 替代 Promtail,集成了日志、指标、事件采集能力。
  • 单一配置文件即可处理多个数据源,适合现代 DevOps 架构。
  • 支持自动识别日志格式、支持推送到 Loki、Prometheus、OTEL 等。

日志结构化支持

  • 虽然 Loki 本质是“按行存储”的日志系统,但配合 label 和 pipeline,可以对日志打标签、提取字段,方便查询。

查询语法简洁(LogQL)

  • 支持按标签筛选、按关键字过滤、正则匹配等。

支持告警(Alerting)

  • 可以设置告警规则,如:
    • 同一 IP 10 分钟内登录失败超过 5 次;
    • 特定关键字(如“sql injection”)出现频繁;
  • Grafana 可以通过邮件、钉钉、Telegram、Webhook 等方式发送告警。

🛠 如何部署日志聚合栈?

一般部署流程如下:

  1. 🐧 安装 Promtail 或 Alloy,配置要收集哪些日志(如 Nginx、auth.log、PHP)。
  2. 📦 安装 Loki,作为日志存储和查询引擎。
  3. 📊 配置 Grafana,用于展示日志、设置告警规则。
  4. 🔍 使用 LogQL 语法进行日志检索。
  5. 📣 设置告警规则,例如:当某 IP 5 分钟内出现超过 10 次登录失败,发送邮件提醒。

安装Alloy & Loki

添加grafana官方yum仓库

$ wget -q -O gpg.key https://rpm.grafana.com/gpg.key
$ echo -e '[grafana]\nname=grafana\nbaseurl=https://rpm.grafana.com\nrepo_gpgcheck=1\nenabled=1\ngpgcheck=1\ngpgkey=https://rpm.grafana.com/gpg.key\nsslverify=1\nsslcacert=/etc/pki/tls/certs/ca-bundle.crt' | sudo tee /etc/yum.repos.d/grafana.repo
$ sudo dnf makecache

安装Alloy

$ sudo dnf install alloy

尝试启动Alloy

$ sudo systemctl enable --now alloy.service

如果你的服务器配置了opendkim用于邮件防伪的话,启动alloy会报端口被占用的错误,我们换到5678

$ sudo vim /etc/sysconfig/alloy

#修改或添加如下内容:
CUSTOM_ARGS="--server.http.listen-addr=0.0.0.0:5678"

#再尝试启动Alloy
$ sudo systemctl enable --now alloy.service

安装Loki,并启动

$ sudo dnf install loki
$ sudo systemctl enable --now loki

配置Alloy

$ sudo vim /etc/alloy/config.alloy
🔍 第一部分:定义文件匹配规则
local.file_match "local_files" {
path_targets = [{"__path__" = "/var/log/nginx/access.log"}]
sync_period = "5s"
}
💡解释:

这是一个本地文件匹配器,告诉 Alloy 哪些日志文件是我们关心的“目标”。

  • local.file_match 是日志源头的定义模块,类似于 “我要看哪个文件”。
  • "local_files" 是这个模块的名字,可以在其他地方引用它。
  • path_targets 指定要采集的日志路径,这里是 /var/log/nginx/access.log
    • __path__ 是一个保留标签,必须这么写,代表真实文件路径。
  • sync_period = "5s" 表示每 5 秒检查一次文件更新(是否有新日志)。

📝 等价理解
“每隔 5 秒,检查一下 /var/log/nginx/access.log,如果新增了内容,就准备读取它。”


🧲 第二部分:定义文件日志采集器
loki.source.file "log_scrape" {
targets = local.file_match.local_files.targets
forward_to = [loki.process.filter_logs.receiver]
tail_from_end = true
}
💡解释:

这是实际“读取日志”的动作模块。

  • loki.source.file 表示这是一段读取本地日志文件的 Loki 源定义;
  • log_scrape 是这个输入源的名字;
  • targets = local.file_match.local_files.targets 表示:采集上面 local_files 匹配到的文件;
  • tail_from_end = true 表示:
    • 从文件末尾开始采集(忽略旧日志),常用于避免初次采集时拉一堆历史内容;
  • forward_to = [loki.process.filter_logs.receiver] 表示:
    • 把采集到的日志交给名为 filter_logs 的日志处理模块去做过滤。

📝 等价理解
“读取 /var/log/nginx/access.log 的新日志,从末尾开始,每行都交给 filter_logs 处理模块。”


🧹 第三部分:日志处理模块(过滤器)
loki.process "filter_logs" {
  stage.drop {
      source = ""
      expression  = ".*Connection closed by authenticating user root"
      drop_counter_reason = "noisy"
  }
  forward_to = [loki.write.grafana_loki.receiver]
}
💡解释:

这是对日志做处理的部分,这里做了“日志过滤(drop)”。

  • stage.drop:表示这是一个“丢弃”阶段;
  • expression
    • 使用正则匹配日志内容;
    • 如果日志中含有 Connection closed by authenticating user root,就把这一行日志“扔掉”;
  • drop_counter_reason = "noisy"
    • 仅用于统计用途,可在指标中看到有多少日志被过滤,理由是 “noisy”(噪声);
  • forward_to = [...]:将剩下的(未被丢弃的)日志发送到 Loki 写入模块。

📝 等价理解
“如果某一行日志包含特定内容,就丢弃它(不推送到 Loki),因为它是噪音。”


📤 第四部分:将日志写入 Loki
loki.write "grafana_loki" {
  endpoint {
    url = "http://localhost:3100/loki/api/v1/push"
  }
}
💡解释:

这是实际“将日志推送到 Loki 的写入端点”的配置。

  • loki.write:写入模块;
  • "grafana_loki":模块名;
  • url = "http://localhost:3100/loki/api/v1/push"
    • 表示日志将被发送到本地运行的 Loki 服务;
    • 这是 Loki 默认的 HTTP 接口,用于接收推送日志。

📝 等价理解
“把处理后的日志推送到本地 Loki 服务,供 Grafana 查询。”


✅ 小结
  • local.file_match:指定监控哪些日志文件;
  • loki.source.file:负责读取这些文件;
  • loki.process:对日志做处理(比如丢弃某些行);
  • loki.write:将结果发送到 Loki;
  • 整体架构非常模块化、声明式,易读易维护;
  • 每一部分都可以独立扩展或替换。

验证Alloy配置

配置完成后重启Alloy

$ sudo systemctl restart alloy.service

你会发现Alloy没有权限读取日志文件,我们通过修改目录ACL的方法给Alloy用户读取/执行权限

$ sudo setfacl -R -m u:alloy:rX /var/log

为需要监控的日志添加postrotate,防止logrotate后权限丢失导致Alloy抓不到新日志,这里用nginx的日志举例

$ sudo vim /etc/logrotate.d/nginx

#默认rotate配置
/var/log/nginx/*.log {
    create 0640 nginx root #rotate后,这里会还原日志的权限
    daily #每天都会轮转一次,导致第二天Alloy就抓不到日志
    rotate 10
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true
    endscript
}

#在postrotate下面添加
    /bin/setfacl -m u:alloy:r /var/log/nginx/access.log #rotate后配置Alloy用户的权限
    /bin/setfacl -m u:alloy:r /var/log/nginx/error.log

防火墙临时放行端口5678

$ sudo firewall-cmd --add-port=5678/tcp

然后用浏览器访问http://example.com:5678,如果配置正确,你会看到如下结果:

重新加载防火墙,不要让外部直接访问5678端口

$ sudo firewall-cmd --reload

配置Loki Log Retention

配置日志保留(retention)的主要目的一言以蔽之:防止日志无限膨胀,节约存储成本,提升查询性能,并满足合规要求。

  • 控制磁盘使用:定期清理过期日志,避免磁盘被旧数据占满导致服务异常。
  • 降低成本:无论本地磁盘还是云存储,存储量越大,费用越高;保留策略能大幅削减支出。
  • 提升查询效率:删除陈旧数据后,索引更小、扫描更快,Grafana 查询响应更迅速。
  • 满足合规需求:很多行业对日志保留期有明确规定(如 30 天、90 天),配置 retention 确保自动合规。

Loki的配置文件位于/etc/loki/config.yml

$ sudo vim /etc/loki/config.yml

#添加
compactor:
  working_directory: /var/lib/loki/retention #压缩和删除过程使用的临时文件目录,需要确保这个目录 Loki 有写权限。
  compaction_interval: 10m #每隔多久执行一次压缩和删除检查。
  retention_enabled: true #是否启用日志保留与过期删除功能。
  retention_delete_delay: 2h #标记某段日志要删除后,等待 2 小时再真正删除。
  retention_delete_worker_count: 150 #同时运行的日志删除任务的最大数量。
  delete_request_store: filesystem #记录删除请求的存储位置,当前是 filesystem,表示保存在本地磁盘(而不是对象存储或数据库)。

limits_config:
  retention_period: 840h #设置日志最大保留时间,即 35 天(840 小时)。

为Loki配置Nginx反向代理实现身份验证

反向代理(Reverse Proxy)是指由一台中间服务器(通常是 Nginx 或 Apache)代表客户端去访问后端的真实服务,并把响应返回给客户端。

🎯 通俗解释:

你可以把反向代理理解成“服务前台接待员”:

  • 客户(浏览器或应用)只对接前台(Nginx);
  • 前台根据请求内容,决定转发给哪个后端服务(比如 Loki、Grafana、WordPress 等);
  • 客户根本不知道真正处理请求的是谁,只知道跟 Nginx 打交道。

虽然 Loki 提供了完整的 HTTP API 接口(如 /loki/api/v1/push/loki/api/v1/query),但在实际部署中,直接暴露 Loki 的端口给外部访问并不安全,也不灵活,因此引入 Nginx 作为反向代理是一个更稳妥的选择。

🔐 安全控制

Loki 默认监听的是一个普通的 HTTP 服务,不自带身份认证、也不支持 TLS 加密。

  • 如果你直接暴露 3100 端口,就意味着任何人都可以向你的 Loki 推送日志,甚至查询日志;
  • 使用 Nginx 代理后,你可以:
    • 启用 HTTPS 加密通信(通过 Let’s Encrypt 或自签证书);
    • 加上 HTTP Basic Auth 或 IP 白名单来限制访问权限;
    • 屏蔽未授权的 /api/v1/push 接口,只允许 Alloy 内部访问。
⚙️ 路由与多端整合
  • 在实际部署中,你的 Loki 可能与其他服务(如 Grafana、Prometheus)部署在同一服务器;
  • Nginx 可以将 /loki/ 路径代理给 Loki,/grafana/ 路径代理给 Grafana;
  • 用户只需通过一个统一域名(如 https://log.example.com)访问所有服务,简洁又专业。
🧱 容器化部署友好
  • 如果你使用 Docker Compose 或 Kubernetes 部署多个组件,Nginx 作为统一入口更易于管理;
  • 比如将 Loki 服务暴露在 nginx:3100 后面,Alloy 配置就只需要指向 http://nginx/loki/api/v1/push 即可。

✅ 小结

使用 Nginx 反向代理 Loki,可以带来:

优势说明
🔒 更高的安全性增加 TLS、认证、防火墙规则
🧩 更强的兼容性更好地整合 Grafana、Alloy、Prometheus 等组件
🌍 更好的可维护性统一入口、更简洁的外部暴露、更灵活的访问控制策略

首先我们先创建.htpasswd文件,这个文件用于用户身份验证

$ sudo htpasswd -c /etc/nginx/.htpasswd example

添加虚拟主机

$ sudo vim /etc/nginx/conf.d/loki.conf

#添加如下内容
server {
    listen 8443 ssl;
    server_name www.example.com example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers PROFILE=SYSTEM;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1.2 TLSv1.3;


    location / {
        proxy_pass http://localhost:3100/; # 将请求转发到本机的3100端口(即 Loki 服务)
        proxy_set_header Host $host; # 把客户端请求中的主机名(Host 头)原样传给后端,便于后端识别访问的域名
        proxy_set_header X-Real-IP $remote_addr; # 把客户端的真实 IP 地址传给后端(否则后端只看到 127.0.0.1)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 把客户端的 IP 添加到 X-Forwarded-For 头中,形成一个转发链,用于记录代理路径

        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

配置SELinux策略,使Nginx有权限监听8443端口

$ sudo semanage port -a -t http_port_t -p tcp 8443

防火墙开放8443端口并重启Nginx

$ sudo firewall-cmd --permanent --add-port=8443/tcp
$ sudo firewall-cmd --reload
$ sudo systemctl restart nginx.service

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注