Nginx作为目前使用最广泛的反向代理和Web服务器之一,每天会产生大量的访问日志和错误日志。当业务部署在多台服务器上时,传统的SSH登录加grep的方式排查问题既低效又容易遗漏。ELK是一套由Elasticsearch、Logstash、Kibana组成的日志处理方案,配合轻量级的采集器Filebeat,可以把分散在各个节点的Nginx日志集中起来,实现统一检索、统计分析和可视化监控。本文将从日志格式定制开始,完整讲解整套系统的搭建与调优过程。

一、整体架构与日志格式定制
一套典型的日志采集链路是:Nginx产生日志,Filebeat负责读取日志文件并转发,Logstash负责解析过滤,Elasticsearch负责存储与检索,Kibana负责查询和可视化展示。相比直接让Logstash跑在应用服务器上,Filebeat更加轻量,资源占用极小,还支持断点续传和背压机制,不会因为下游处理不过来而丢日志。
为了让Logstash解析方便,强烈建议先把Nginx的日志格式改成JSON。默认的combined格式用正则解析既慢又容易出错,而JSON格式可以直接用Logstash的json插件反序列化,性能和准确率都高得多。在nginx.conf中定义如下:
log_format json_log escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":"$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"upstream_response_time":"$upstream_response_time",'
'"http_user_agent":"$http_user_agent",'
'"http_referer":"$http_referer",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_log;
注意escape=json参数,它会自动转义日志中的特殊字符,避免用户请求中携带的引号或换行符破坏JSON结构,这是一个经常被忽略但非常关键的细节。另外,$request_time和upstream_response_time这两个字段对性能分析非常重要,前者记录Nginx处理请求的总耗时,后者记录上游应用的处理耗时,两者的差值可以帮我们判断瓶颈在Nginx自身还是后端服务。
二、Filebeat采集与Logstash解析配置
Filebeat的配置非常简单,只需要指定要监控的日志路径和输出目标。编辑filebeat.yml:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/access.log
fields:
log_type: nginx-access
fields_under_root: true
output.logstash:
hosts: ["192.168.0.10:5044"]
这里通过fields添加了一个自定义字段log_type,方便Logstash区分不同类型的日志。如果集群机器很多,建议给每个节点再添加一个hostname字段,查询时可以快速定位问题机器。日志被Filebeat发送到Logstash的5044端口后,由Logstash完成解析、类型转换和字段补充:
input {
beats {
port => 5044
}
}
filter {
if [log_type] == "nginx-access" {
json {
source => "message"
remove_field => ["message"]
}
date {
match => ["time", "ISO8601"]
target => "@timestamp"
remove_field => ["time"]
}
mutate {
convert => {
"status" => "integer"
"body_bytes_sent" => "integer"
"request_time" => "float"
}
}
geoip {
source => "remote_addr"
target => "geoip"
}
useragent {
source => "http_user_agent"
target => "ua"
}
}
}
output {
elasticsearch {
hosts => ["http://127.0.0.1:9200"]
index => "nginx-access-%{+YYYY.MM.dd}"
}
}
这段配置中有几个值得注意的点。date插件把日志中的时间字段写入@timestamp,保证后续按时间范围查询和生成时间序列图表时使用的是日志产生的真实时间,而不是Logstash收到日志的时间,两者在日志延迟时可能相差数秒甚至更多。mutate把status等字段转换成数值类型,否则它们会被当成字符串,Kibana里做平均值、最大值统计时结果会不正确。
geoip插件根据客户端IP解析出地理位置信息,配合Kibana的坐标地图可以直观看到访问来源的分布,对分析异常流量和攻击来源非常实用。useragent插件则把User-Agent拆解成操作系统、浏览器、设备类型等结构化字段,便于统计客户端构成。索引名按天分割是ELK的最佳实践,配合索引生命周期管理可以方便地实现日志的滚动删除,避免磁盘被撑爆。
三、Kibana可视化分析与常见问题处理
日志进入Elasticsearch后,在Kibana中创建索引模式(匹配nginx-access-*,时间字段选@timestamp),就可以在Discover页面检索日志了。基于这些结构化字段,可以搭建一系列实用的仪表盘:用柱状图按状态码统计请求量,快速发现5xx激增的时间点;用表格列出request_time排名前20的慢请求,配合request_uri定位性能瓶颈接口;用饼图展示流量来源IP或地域分布,识别爬虫和攻击行为。
实际使用中常遇到几个问题。第一是日志重复,通常是Logstash和Filebeat之间网络抖动导致的重发,属于正常现象,Elasticsearch写入时可以指定document_id为日志指纹去重。第二是字段映射冲突,比如某个字段在旧索引中是字符串、新索引中变成了数值,导致索引创建失败,解决办法是定义索引模板,提前固定各字段的类型:
PUT _template/nginx-access
{
"index_patterns": ["nginx-access-*"],
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
第三是磁盘与性能问题。日志量大时,建议给Elasticsearch节点配置SSD磁盘,并合理设置分片数量,单个分片控制在30GB左右比较合适。对于超过保留期限的日志,用索引生命周期策略自动删除,例如只保留30天,既满足排查需求又不浪费存储。
最后提醒一点,生产环境务必给Kibana和Elasticsearch加上访问控制,可以通过X-Pack的 Security功能设置账号密码,或者在Nginx中再做一层反向代理加 Basic 认证,避免日志中的敏感信息被未授权访问。整套系统搭好之后,排查线上问题从几分钟缩短到几秒钟,配合告警规则还能在错误率异常时第一时间收到通知,投入产出比非常高。