
在分布式系统、微服务架构成为主流的今天,日志已经不再是简单的“程序运行记录”,而是定位问题、监控系统、分析用户行为的核心数据资产。但面对成百上千台服务器的日志散落在不同节点、格式杂乱无章、排查问题时需要逐台登录服务器翻找日志的痛点,绝大多数研发和运维人员都苦不堪言。
ELK(Elasticsearch + Logstash + Kibana)作为目前最主流的开源日志分析平台,完美解决了日志“收集-存储-检索-分析-可视化”全链路的问题。它能将分散的日志统一收集、结构化处理后存储,再通过可视化界面实现秒级检索和多维度分析,让原本耗时几小时的日志排查工作缩短到几分钟。
本文摒弃所有冗余的理论堆砌,全程以“实战”为核心,从ELK核心组件的底层逻辑讲起,到单机版ELK的完整搭建,再到真实的Nginx日志收集分析案例,最后补充性能调优和常见排错方案,确保你看完就能上手,既能理解底层原理,又能解决实际工作中的日志分析问题。
想要搭建好ELK,首先要搞懂每个组件的核心作用和底层逻辑,而不是单纯“照抄配置”。
Elasticsearch(简称ES)是ELK的核心存储和检索组件,底层基于Lucene搜索引擎实现,专为分布式、高可用、近实时的全文检索设计。
Logstash是日志的“ETL工具”(抽取-转换-加载),负责从不同数据源收集日志,对日志进行过滤、结构化处理,再将处理后的日志发送到ES。
Logstash的核心是“管道(Pipeline)”,一个管道包含三个不可缺少的部分:
A[Input(输入)]-->B[Filter(过滤/转换)]
B-->C[Output(输出)]
Kibana是ES的可视化前端工具,相当于ELK的“操作面板”,所有对日志的检索、分析、监控都通过Kibana完成。
Kibana直接对接ES的API,将ES的检索结果转换成可视化图表(柱状图、折线图、饼图)、仪表盘、告警规则等。核心功能模块包括:
ELK对系统环境有明确要求,提前配置好环境参数,能避免后续启动失败、性能低下等问题。本文以CentOS 7.9为例(CentOS 8/9、Ubuntu操作逻辑一致),所有操作均基于64位系统。
ES、Logstash、Kibana需要占用9200、5044、5601等端口,防火墙会拦截端口访问;SELinux会限制文件权限,导致组件启动失败。
# 关闭防火墙并设置开机禁用
systemctl stop firewalld && systemctl disable firewalld
# 临时关闭SELinux(立即生效)
setenforce 0
# 永久关闭SELinux(重启后生效)
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
ES需要大量的文件描述符和线程数,默认的系统限制远不足够,必须调整:
# 编辑资源限制配置文件
vi /etc/security/limits.conf
# 添加以下内容(直接追加到文件末尾)
* soft nofile 65535
* hard nofile 65535
* soft nproc 4096
* hard nproc 4096
# 生效配置(无需重启,重新登录会话即可)
ulimit -n 65535 && ulimit -u 4096
ES需要调整虚拟内存映射限制,否则启动会报错:
# 编辑内核参数配置文件
vi /etc/sysctl.conf
# 添加以下内容
vm.max_map_count=262144
# 生效配置
sysctl -p
ELK三个组件的版本必须完全一致(比如都用8.15.0),版本不一致会导致组件间通信失败(比如Kibana无法连接ES)。本文选用2026年最新稳定版:8.15.0。
# 下载ES安装包(官方地址,速度慢可换国内镜像)
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.15.0-linux-x86_64.tar.gz
# 解压到/usr/local目录
tar -zxvf elasticsearch-8.15.0-linux-x86_64.tar.gz -C /usr/local/
# 重命名目录(简化后续操作)
mv /usr/local/elasticsearch-8.15.0 /usr/local/elasticsearch
ES出于安全考虑,不允许使用root用户启动,必须创建普通用户:
# 创建es用户
useradd es
# 赋予es用户ES目录的所有权
chown -R es:es /usr/local/elasticsearch
ES的配置文件位于/usr/local/elasticsearch/config/elasticsearch.yml,编辑该文件:
# 集群名称(单机版也需配置,自定义即可)
cluster.name:elk-cluster
# 节点名称(自定义)
node.name:node-1
# 绑定IP(0.0.0.0允许所有IP访问,仅用于测试环境)
network.host:0.0.0.0
# ES默认端口
http.port:9200
# 单机版模式(无需集群发现)
discovery.type:single-node
# 关闭安全认证(入门阶段简化操作,生产环境需开启)
xpack.security.enabled:false
# 关闭HTTPS(测试环境简化)
xpack.security.http.ssl.enabled:false
ES的JVM内存配置文件位于/usr/local/elasticsearch/config/jvm.options,默认是-Xms2g -Xmx2g(堆内存初始2GB,最大2GB),根据服务器内存调整:
vi /usr/local/elasticsearch/config/jvm.options
# 修改以下两行(内存不超过物理内存的50%,且不超过32GB)
-Xms2g
-Xmx2g
# 切换到es用户启动ES(后台运行)
su - es -c "/usr/local/elasticsearch/bin/elasticsearch -d"
# 等待30秒(ES启动需要时间),验证是否启动成功
curl http://localhost:9200
成功输出示例:
{
"name" : "node-1",
"cluster_name" : "elk-cluster",
"cluster_uuid" : "xxxxxxxxx",
"version" : {
"number" : "8.15.0",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "xxxxxxxxx",
"build_date" : "2026-01-01T00:00:00.000Z",
"build_snapshot" : false,
"lucene_version" : "9.10.0",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
如果返回上述JSON数据,说明ES启动成功;若失败,查看/usr/local/elasticsearch/logs/elk-cluster.log日志排查问题。
# 下载Logstash安装包
wget https://artifacts.elastic.co/downloads/logstash/logstash-8.15.0-linux-x86_64.tar.gz
# 解压到/usr/local目录
tar -zxvf logstash-8.15.0-linux-x86_64.tar.gz -C /usr/local/
# 重命名目录
mv /usr/local/logstash-8.15.0 /usr/local/logstash
Logstash的配置文件建议放在/etc/logstash/conf.d/目录(需手动创建),创建Nginx日志收集配置文件:
# 创建配置目录
mkdir -p /etc/logstash/conf.d
# 编辑Nginx日志收集配置
vi /etc/logstash/conf.d/nginx.conf
配置文件内容(核心:输入-过滤-输出):
# Input:收集Nginx访问日志
input {
file {
# Nginx访问日志路径(默认路径,可根据实际修改)
path => "/var/log/nginx/access.log"
# 从文件末尾开始读取(避免重复读取历史日志)
start_position => "end"
# 每隔1秒检查一次文件变化
stat_interval => 1
# 给日志打标签,方便后续筛选
tags => ["nginx-access"]
# 编码格式
codec => "plain"
}
}
# Filter:清洗并结构化Nginx日志
filter {
# 只处理nginx-access标签的日志
if"nginx-access"in [tags] {
# Grok:解析Nginx默认访问日志格式
grok {
match => {
"message" => "%{IPORHOST:remote_addr} - %{USERNAME:remote_user} \[%{HTTPDATE:time_local}\] \"%{METHOD:request_method} %{URIPATH:request_uri}(?:\?%{QUERYSTRING:request_args})? %{HTTPVERSION:http_version}\" %{NUMBER:status:int} %{NUMBER:body_bytes_sent:int} \"%{DATA:http_referer}\" \"%{DATA:http_user_agent}\""
}
# 解析失败时保留原始日志
tag_on_failure => ["grok_parse_failure"]
}
# 转换时间格式(适配ES的时间字段)
date {
match => ["time_local", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
# 删除无用的message字段(解析后已拆分,无需保留)
mutate {
remove_field => ["message"]
}
}
}
# Output:将处理后的日志发送到ES
output {
# 只处理nginx-access标签的日志
if"nginx-access"in [tags] {
elasticsearch {
# ES地址(单机版)
hosts => ["http://localhost:9200"]
# ES索引名称(按天拆分,便于管理)
index => "nginx-access-%{+YYYY.MM.dd}"
}
# 同时输出到控制台(测试用,生产环境可注释)
stdout {
codec => rubydebug
}
}
}
# 启动Logstash(后台运行)
nohup /usr/local/logstash/bin/logstash -f /etc/logstash/conf.d/nginx.conf > /var/log/logstash.log 2>&1 &
# 查看Logstash日志,确认无报错
tail -f /var/log/logstash.log
成功标志:日志中无“error”关键词,出现“Pipeline started successfully”提示。
# 下载Kibana安装包
wget https://artifacts.elastic.co/downloads/kibana/kibana-8.15.0-linux-x86_64.tar.gz
# 解压到/usr/local目录
tar -zxvf kibana-8.15.0-linux-x86_64.tar.gz -C /usr/local/
# 重命名目录
mv /usr/local/kibana-8.15.0 /usr/local/kibana
# 赋予kibana目录权限(避免启动权限不足)
chown -R es:es /usr/local/kibana
Kibana的配置文件位于/usr/local/kibana/config/kibana.yml:
# Kibana监听端口
server.port: 5601
# 允许所有IP访问(测试环境)
server.host: "0.0.0.0"
# ES地址(与Logstash配置一致)
elasticsearch.hosts: ["http://localhost:9200"]
# Kibana界面语言(中文)
i18n.locale: "zh-CN"
# 切换到es用户启动Kibana(后台运行)
su - es -c "nohup /usr/local/kibana/bin/kibana > /var/log/kibana.log 2>&1 &"
# 等待60秒(Kibana启动较慢),查看日志确认无报错
tail -f /var/log/kibana.log
成功验证:打开浏览器,访问http://服务器IP:5601,能看到Kibana的中文界面,说明启动成功。
搭建完ELK后,我们通过实际的Nginx日志收集案例,验证整个流程的有效性。
# 安装Nginx
yum install nginx -y
# 启动Nginx
systemctl start nginx && systemctl enable nginx
# 生成测试日志(访问Nginx首页10次)
for i in {1..10}; do curl http://localhost; done
nginx-access-*,点击“下一步”;@timestamp(Logstash中转换后的时间字段),点击“创建索引模式”。remote_addr(客户端IP)、status(响应状态码)、request_uri(请求路径)等结构化字段。以“统计不同响应状态码的访问次数”为例:
nginx-access-*;status(响应状态码);搭建完成后,针对入门场景的性能调优,能避免小量日志就出现卡顿的问题。
JVM堆内存:物理内存4GB时,设置-Xms2g -Xmx2g;8GB内存时,设置-Xms4g -Xmx4g(不超过物理内存的50%);
索引分片:日志索引按天拆分,每个分片大小控制在50-100GB(单机版默认1个分片即可);
关闭索引刷新间隔:测试环境可将index.refresh_interval设为30s(默认1s),减少IO压力:
# 临时设置(重启ES失效)
curl -XPUT http://localhost:9200/nginx-access-*/_settings -H "Content-Type: application/json" -d '{"index":{"refresh_interval":"30s"}}'
批量处理:在output的elasticsearch配置中添加批量参数,提升写入效率:
elasticsearch {
hosts => ["http://localhost:9200"]
index => "nginx-access-%{+YYYY.MM.dd}"
# 批量大小
bulk_size => 500
# 批量延迟
flush_size => 500
}
减少不必要的输出:生产环境注释掉stdout输出,减少资源消耗。
kibana.yml,添加xpack.fleet.enabled: false(关闭Fleet插件,入门用不到);elasticsearch.requestTimeout: 30000(请求超时时间,默认15s,日志量大时延长)。错误现象 | 原因 | 解决方案 |
|---|---|---|
日志提示“max file descriptors [4096] for elasticsearch process is too low” | 文件描述符不足 | 按2.2.2节调整ulimit参数,重启ES |
日志提示“max virtual memory areas vm.max_map_count [65530] is too low” | 内核参数vm.max_map_count不足 | 按2.2.3节调整内核参数,执行sysctl -p后重启ES |
端口9200被占用 | 其他程序占用9200端口 | 执行`netstat -tlnp |
/usr/local/logstash/bin/logstash -f /etc/logstash/conf.d/nginx.conf --config.test_and_exit,语法错误会直接提示;/var/log/nginx/access.log(执行chmod 644 /var/log/nginx/access.log);curl http://localhost:9200,确认ES能正常响应;elasticsearch.hosts是否正确:确保地址和端口与ES一致;