在构建可观测性体系时,一个健壮、解耦的日志采集架构至关重要。本文将手把手带你搭建一个生产级日志流水线:使用 Filebeat 采集 Nginx 的 JSON 格式日志,利用 Redis 作为高性能缓冲队列,最终通过 Logstash 处理并写入 Elasticsearch 集群,并最终在 Kibana 中实现 IP 访问源的地理位置地图可视化。
架构概述与设计考量
利用 Redis 缓存日志数据,主要解决了三大核心问题:应用解耦、异步消息传递和流量削峰。

如上图所示,整体流程为:Nginx 服务器上的 Filebeat 将收集到的访问日志写入 Redis 服务器,然后由独立的 Logstash 服务从 Redis 中取出数据,经过处理后写入 Elasticsearch 服务器。
相关官方文档参考:
方案局限性
- 不支持 Redis 集群模式,存在单点风险(但可通过部署多个 Redis 实例实现客户端负载均衡)。
- Redis 基于内存存储,缓存数据量受内存容量限制。
实战部署步骤
1. 部署 Nginx 并配置 JSON 格式访问日志
首先,我们需要在 Web 服务器上部署 Nginx 并将其访问日志格式配置为 JSON,便于后续结构化解析。
安装 Nginx 并配置日志格式
#安装部署nginx
[root@web-server ~]# apt install -y nginx
#修改nginx访问日志为Json格式
[root@web-server ~]#grep -Ev '^#|^$' /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
log_format json_logs escape=json
'{'
'"@timestamp":"$time_iso8601",'# 记录 ISO 8601 格式的时间戳
'"host":"$server_addr",'# 服务器地址
'"clientip":"$remote_addr",'# 客户端 IP 地址
'"size":$body_bytes_sent,'# 响应发送给客户端的字节数
'"responsetime":$request_time,'# 请求处理时间
'"upstreamtime":"$upstream_response_time",'# 上游服务器响应时间
'"upstreamhost":"$upstream_addr",'# 上游服务器地址
'"http_host":"$host",'# 请求中的 Host 头部
'"uri":"$uri",'# 请求的 URI
'"domain":"$host",'# 请求中的域名
'"xff":"$http_x_forwarded_for",'# 客户端的 X-Forwarded-For 头,表示客户端的原始 IP
'"referer":"$http_referer",'# 请求的 Referer 头
'"tcp_xff":"$proxy_protocol_addr",'# 通过 TCP 代理协议传递的原始客户端 IP 地址
'"http_user_agent":"$http_user_agent",'# 用户代理(客户端的浏览器或其他信息)
'"status":"$status"}'; # HTTP 响应状态码
access_log /var/log/nginx/access_json.log json_logs;
error_log /var/log/nginx/error_json.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#默认开启nginx的错误日志,但如果是ubuntu,还需要修改下面行才能记录错误日志
[root@web-server ~]#vim /etc/nginx/sites-available/default
......
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
#try_files $uri $uri/ =404; #将此行注释
}
[root@web-server ~]# systemctl restart nginx
配置 Web 服务站点
#web界面配置文件
[root@web-server ~]# vim /etc/nginx/conf.d/pc.conf
server {
listen 8080;
server_name t1.dinginx.org;
root /data/html/wordpress/;
index index.php index.html index.htm;
location /basic_status {
stub_status;
}
# PHP-FPM 状态查看地址
location ~ ^/(fpm_status|ping)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /data/html/wordpress/fpm_status.php; # 指定 PHP 文件位置
include fastcgi_params;
}
# PHP 处理,确保所有的 .php 文件都通过 PHP-FPM 处理
location ~ \.php$ {
root /data/html/wordpress;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
验证日志格式
执行以下命令,如果看到格式美观的 JSON 输出,说明配置成功。
[root@web-server ~]#tail -f /var/log/nginx/access_json.log -n1|jq
{
"@timestamp": "2024-11-18T04:47:14+00:00",
"host": "11.0.1.105",
"clientip": "11.0.1.99",
"size": 1484,
"responsetime": 0.073,
"upstreamtime": "0.072",
"upstreamhost": "127.0.0.1:9000",
"http_host": "phpipam.dinginx.org",
"uri": "/app/dashboard/widgets/top10_hosts_v4.php",
"domain": "phpipam.dinginx.org",
"xff": "",
"referer": "http://phpipam.dinginx.org/",
"tcp_xff": "",
"http_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"status": "200"
}
2. 使用 Filebeat 收集日志到 Redis
Filebeat 安装
在 Web 服务器上安装 Filebeat。
[root@web-server ~]# dpkg -i filebeat-8.14.3-amd64.deb
修改 Filebeat 配置
编辑 /etc/filebeat/filebeat.yml,配置输入为 Nginx 的 JSON 日志,输出指向 Redis。
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/access_json.log
json.keys_under_root: true # 将JSON键值提升到根级别
json.overwrite_keys: true # 如果键值存在,允许覆盖
tags: ["nginx-access"] # 给日志打上标签,便于区分
- type: log
enabled: true
paths:
- /var/log/nginx/error_json.log
json.keys_under_root: true # 将JSON键值提升到根级别
tags: ["nginx-error"] # 给日志打上标签,便于区分
output.redis:
hosts: ["11.0.1.10:6379"]
key: "filebeat"
password: "123456"
db: "0"
timeout: 5
注意:请根据实际情况修改 Redis 的 hosts、password 等参数。原配置中注释掉的 Logstash 和 Elasticsearch 输出部分已被移除以保持清晰。
启动 Filebeat 服务
[root@web-server ~]# systemctl enable --now filebeat.service
[root@web-server ~]# systemctl status filebeat.service
3. 安装与配置 Redis
在另一台服务器上部署 Redis,作为消息队列。
[root@redis ~]# apt update && apt -y install redis
[root@redis ~]# vim /etc/redis/redis.conf
bind 0.0.0.0
save "" #禁用rdb持久保存
requirepass 123456
[root@redis ~]# systemctl restart redis
4. 配置 Logstash 消费 Redis 数据并写入 Elasticsearch
此步骤在 Logstash 服务器进行,负责从 Redis 读取数据,进行加工(如添加地理位置信息),并写入 Elasticsearch。
安装 Logstash
#8.X 要求JDK11或17
[root@logstash ~]# apt update && apt -y install openjdk-17-jdk
[root@logstash ~]# wget https://mirrors.tuna.tsinghua.edu.cn/elasticstack/7.x/apt/pool/main/l/logstash/logstash-7.6.2.deb
[root@logstash ~]# dpkg -i logstash-7.6.2.deb
编写 Logstash 配置文件
创建配置文件 /etc/logstash/conf.d/nginx_redis_to_es.conf,内容如下。该配置实现了从 Redis 读取,通过 geoip 插件丰富客户端 IP 的地理信息,并按标签分索引写入 Elasticsearch。
input {
redis {
host => "11.0.1.10" # 这里填写 Redis 服务器的 IP 地址
port => 6379 # Redis 默认端口
password => "123456" # Redis 的密码,如果没有密码认证,可以删除此行
db => 0 # 选择 Redis 数据库,0 为默认数据库
data_type => "list" # Redis 数据类型为 list
key => "filebeat" # 从 Redis 的 list 中读取数据的 key
}
}
filter {
if "nginx-access" in [tags] {
geoip {
source => "clientip"
target => "geoip"
add_field => ["[geoip][coordinates]", "%{[geoip][geo][location][lon]}"]
add_field => ["[geoip][coordinates]", "%{[geoip][geo][location][lat]}"]
}
mutate {
convert => [ "[geoip][coordinates]", "float" ]
}
}
mutate {
convert => ["upstreamtime", "float"]
}
}
output {
# 1. 系统日志
if "syslog" in [tags] {
elasticsearch {
hosts => ["11.0.1.101:9200", "11.0.1.102:9200", "11.0.1.103:9200"]
index => "syslog-%{+YYYY.MM.dd}" # 按天轮转索引
}
}
# 2. Nginx 访问日志
if "nginx-access" in [tags] {
elasticsearch {
hosts => ["11.0.1.101:9200", "11.0.1.102:9200", "11.0.1.103:9200"]
index => "nginxaccess-%{+YYYY.MM.dd}"
template_overwrite => true # 如果索引模板存在则更新
}
stdout { codec => "rubydebug" } # 输出调试信息到控制台
}
# 3. Nginx 错误日志
if "nginx-error" in [tags] {
elasticsearch {
hosts => ["11.0.1.101:9200", "11.0.1.102:9200", "11.0.1.103:9200"]
index => "nginxerrorlog-%{+YYYY.MM.dd}"
template_overwrite => true
}
}
}
测试 Logstash 配置
使用以下命令测试配置文件是否正确,能否正常从 Redis 拉取并处理数据。你将在控制台看到包含 geoip 字段的详细事件输出。
root@logstash:~# /usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/nginx_redis_to_es.conf -r
(此处输出较长,展示了包含地理信息的完整事件结构,确认 geoip.coordinates 字段已生成。)
5. 数据验证
启动完整的流水线后,我们可以在 Elasticsearch 和 Kibana 中验证数据是否成功写入。
通过 Cerebro 查看集群与索引状态

在 Kibana 中创建索引模式并查看数据

实现 IP 访问地图可视化
Kibana 的 Maps 功能可以基于地理空间字段创建炫酷的可视化地图。但首先,我们需要确保数据符合要求。
挑战:geo_point 字段类型
在新版 Kibana(特别是 8.x)中,地图可视化要求地理坐标字段的类型为 geo_point。然而,我们之前在 Logstash 的 mutate 过滤器中仅将坐标转换成了 float 类型,这会导致创建地图图层时出错,提示“数据视图不包含任何地理空间字段”。

解决方案:修改 Elasticsearch 的索引模板,将 geoip.coordinates 字段的映射类型从 float 改为 geo_point。
- 获取并修改索引映射:在 Kibana 的 Dev Tools 中执行以下操作。
GET nginxaccess-2023.02.20
从返回结果中复制完整的 mappings 部分。然后,创建或更新一个索引模板,关键步骤是将 "geoip.properties.coordinates.type" 的值改为 "geo_point"。
PUT /_template/template_nginx_accesslog
{
"index_patterns": ["nginxaccess-*"],
"order": 0,
"aliases": {},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
... // 其他字段映射省略以节省篇幅
"geoip": {
"properties": {
"coordinates": {
"type": "geo_point" // 修改此行,将默认的 float 改为 geo_point
},
... // geoip 的其他子字段
}
},
... // 更多字段映射
}
}
}
注意:上述 JSON 是摘要,实际操作中需要使用从 GET 请求中获取的完整 mappings 对象进行修改。

- 应用模板并验证:模板更新后,新写入的数据将自动使用新映射。你也可以选择删除旧索引(
nginxaccess-*)让数据重新摄入。之后,在 Kibana 的 Discover 中应能正确识别 geoip.coordinates 为地理字段。

在 Kibana Maps 中创建可视化地图
当地理字段准备就绪后,创建地图就非常简单了。
- 进入 Kibana 左侧导航栏的 Analytics > Maps。
- 点击 Create map。
- 添加新的图层,选择数据视图为
nginxaccess-*(或你创建的索引模式)。
- 在图层配置中,设置“地理空间字段”为
geoip.coordinates。
- 根据需要选择图层类型(如热力图、聚类等)并进行样式调整。

完成配置后,你就能得到一张展示 Nginx 访问来源 IP 全球分布的地图,这对于洞察用户地域分布、识别异常访问源非常有帮助。

版本差异说明
- Elasticsearch 7.x:可以直接使用上述步骤,索引模板名称建议以
logstash- 开头。
- Elasticsearch 8.x:必须确保索引模板中的字段类型为
geo_point。如果已有旧数据为 float 类型,通常需要重建索引(删除旧索引后让数据重新流入)才能使模板更改生效。
结语
通过本文的实战演练,我们构建了一套从日志采集、缓冲、处理到可视化分析的完整链路。该架构不仅提升了系统的可靠性和扩展性,还通过 ELK Stack 的强大能力挖掘出了日志中的地理位置价值。如果你在运维监控或日志分析中遇到类似需求,不妨在 云栈社区 的 运维/DevOps/SRE 板块与其他开发者交流,共同探讨更多高级玩法与优化实践。