Kibana数据可视化与ELFK架构实战

Kibana数据可视化与ELFK架构实战
[TOC]
kibana出图展示

- 昨天的成果
统计PV

# 进入到可视化库界面基于聚合 --> 指标 --> 选择索引J(kpyun-filebeat-modules-nginx...) --> 24root@Elk02 ~# wc -l /var/log/nginx/access.log24 /var/log/nginx/access.log# 共 24 条访问记录==============================# 停掉所有 filebeatkillall -9 filebeat# 删索引curl -X DELETE 'http://10.0.0.6:9200/kpyun-filebeat-modules-nginx-2026.07.27'# 清 registry + 重采rm -rf /var/lib/filebeat/filebeat -e -c /etc/filebeat/config/05-modules_nginx-to-es.yaml# 等 10 秒,验证应该只有 24 条
PV: 点击一次页面、刷新一次页面被网站记录一次PV# 页面的访问量
UV: 一台主机就是一个UV、# 独立访客数量
IP: 独立的公网IP地址DAU:每天的活跃用户数量(日活)MAU:每月的活跃用户数量(月活)
举个例子:假设公司有一座大厦,大厦有100人,每个人有一台电脑和一部手机,上网都是通过nat转换出口,每个人每台设备点击网站2次, 请问对应的pv,uv,ip分别是多少?PV: 100X2X2=400UV: 100X2=200IP: 1'很多时候,访问到不了后台,缓存就解决了'
分析IP
基于聚合 --> 指标 --> 选择索引(kpyun-filebeat-modules-nginx...) --> 指定字段(related.ip)
设备类型
创建+可视化 --> 选择Lens --> 选择索引 --> 选择字段(user_agent.device.name)

操作系统用户占比
创建+可视化 --> 选择Lens --> 选择索引 --> 选择特定字段(user_agent.os.full)
# 查看现有的图列表如下图所示,我们已经画了4张图了!
分析流量带宽
创建+可视化 --> 选择Lens --> 选择索引 --> 选择特定字段(http.response.body.bytes)# 搜索bytes(字节)
root@Elk02 ~# nohup filebeat -e -c /etc/filebeat/config/05-modules_nginx-to-es.yaml &> /tmp/es01.log &# 启动filebeat'我们可以尝试增加一条nginx的访问日志,修改带宽的大小,而后由Filebeat采集数据到ES,kibana出图展示'========================================💡 '修改nginx访问日志观察数据是否监控'root@Elk02 ~# echo 50*1024 | bc51200# 增加50KB带宽(加到状态码后面)root@Elk02 ~# echo '123.113.31.55 - - [27/Jul/2026:14:35:00 +0800] "GET /api/health HTTP/1.1" 200 51200 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"' >> /var/log/nginx/access.logroot@Elk02 ~# wc -l /var/log/nginx/access.log25 /var/log/nginx/access.log
全球用户分布图
创建+可视化 --> 选择maps --> 添加图层 --> 文档 --> 选择索引 --> 地理空间字段(source.geo.location)
作Dashboard展示数据
新建仪表盘 --> 从库中添加 --> 展示数据并保存Dashboard

EFK架构采集web集群日志

需求说明: - 在Elk01和Elk03部署tomcat,要求首页内容不一样; - 在Elk02节点部署nginx服务作为统一的访问入口,要求如下: - 1.访问路径为: /oldboyedu时请求转发到Elk01节点; - 2.访问路径为: /linux时请求转发到Elk03节点; - 使用EFK架构分析整个web集群的访问日志;
Tomcat部署
1)JDk版本(ES自带)root@Elk03 ~# /usr/share/elasticsearch/jdk/bin/java -versionopenjdk version "22.0.2" 2024-07-16OpenJDK Runtime Environment (build 22.0.2+9-70)OpenJDK 64-Bit Server VM (build 22.0.2+9-70, mixed mode, sharing)# 7.17.29(2025.06 发布)已经换成 JDK 22
2)部署tomcatroot@Elk03 ~# mkdir /softroot@Elk03 ~# tar xf /tmp/apache-tomcat-11.0.24.tar.gz -C /soft/# 解压到/softroot@Elk03 ~# ll /soft/total 12drwxr-xr-x 3 root root 4096 Jul 27 21:11 ./drwxr-xr-x 25 root root 4096 Jul 27 21:11 ../drwxr-xr-x 9 root root 4096 Jul 27 21:11 apache-tomcat-11.0.24/root@Elk03 ~# ln -s /soft/apache-tomcat-11.0.24/ /soft/tomcat# 第二个是软连接
3)环境变量root@Elk03 ~# vim /etc/profile.d/tomcat.sh#!/bin/bashexport JAVA_HOME=/usr/share/elasticsearch/jdkexport TOMCAT_HOME=/soft/tomcatexport PATH=$PATH:$JAVA_HOME/bin:$TOMCAT_HOME/binroot@Elk03 ~# source /etc/profile.d/tomcat.shroot@Elk03 ~# java --versionopenjdk 22.0.2 2024-07-16OpenJDK Runtime Environment (build 22.0.2+9-70)OpenJDK 64-Bit Server VM (build 22.0.2+9-70, mixed mode, sharing)
4)运行Tomcatroot@Elk03 ~# startup.sh......Tomcat started.# /soft/tomcat/bin/startup.sh --> 绝对路径启动Tomcatroot@Elk03 ~# ss -lntup |grep 8080tcp LISTEN 0 100 *:8080 (("java",pid=2679,fd=44))
5)访问web UIhttp://10.0.0.8:8080/
6)访问日志root@Elk03 ~# cat /soft/tomcat/logs/localhost_access_log.2026-07-27.txt10.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET / HTTP/1.1" 200 1123710.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /tomcat.css HTTP/1.1" 200 558410.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /tomcat.svg HTTP/1.1" 200 6779510.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 708910.0.0.1 - - [27/Jul/2026:21:27:42 +0800] "GET /host-manager/html HTTP/1.1" 403 310410.0.0.1 - - [27/Jul/2026:21:27:42 +0800] "GET /host-manager/images/favicon.ico HTTP/1.1" 403 310410.0.0.1 - - [27/Jul/2026:21:27:46 +0800] "GET /manager/html HTTP/1.1" 403 352310.0.0.1 - - [27/Jul/2026:21:27:46 +0800] "GET /manager/images/favicon.ico HTTP/1.1" 403 352310.0.0.1 - - [27/Jul/2026:21:46:39 +0800] "GET /w3er HTTP/1.1" 404 72110.0.0.1 - - [27/Jul/2026:21:47:01 +0800] "GET /docs/ HTTP/1.1" 403 87710.0.0.1 - - [27/Jul/2026:21:47:05 +0800] "GET /docs/ HTTP/1.1" 403 87710.0.0.1 - - [27/Jul/2026:21:47:05 +0800] "GET /docs/ HTTP/1.1" 403 877
'Elk01节点重复上述操作'root@Elk01 ~# cat /soft/tomcat/logs/localhost_access_log.2026-07-27.txt10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET / HTTP/1.1" 200 1123710.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /tomcat.svg HTTP/1.1" 200 6779510.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /tomcat.css HTTP/1.1" 200 558410.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-nav.png HTTP/1.1" 200 140110.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-middle.png HTTP/1.1" 200 191810.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 708910.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-upper.png HTTP/1.1" 200 310310.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-button.png HTTP/1.1" 200 71310.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /favicon.ico HTTP/1.1" 200 2163010.0.0.1 - - [27/Jul/2026:21:51:04 +0800] "GET /manager/status HTTP/1.1" 403 352310.0.0.1 - - [27/Jul/2026:21:51:04 +0800] "GET /manager/images/favicon.ico HTTP/1.1" 403 352310.0.0.1 - - [27/Jul/2026:21:51:16 +0800] "GET / HTTP/1.1" 200 1123710.0.0.1 - - [27/Jul/2026:21:51:29 +0800] "GET /test HTTP/1.1" 404 721准备tomcat首页内容
root@Elk01 ~# rm -rf /soft/tomcat/webapps/ROOT/*root@Elk01 ~# echo "<h1 style='color: red;'>Elk01~~~</h1>" > /soft/tomcat/webapps/ROOT/index.htmlroot@Elk01 ~# curl localhost:8080<h1 style='color: red;'>Elk01~~~</h1>
root@Elk03 ~# rm -rf /soft/tomcat/webapps/ROOT/*root@Elk03 ~# echo "<h1 style='color: red;'>Elk03...</h1>" > /soft/tomcat/webapps/ROOT/index.htmlroot@Elk03 ~# curl localhost:8080<h1 style='color: red;'>Elk03...</h1>nginx代理tomcat应用
root@Elk02 ~# vim /etc/nginx/conf.d/lb-tomcat.confserver { listen 80 default_server; server_name _;
location /oldboyedu/ { proxy_pass http://10.0.0.6:8080/; } location /linux/ { proxy_pass http://10.0.0.8:8080/; } location / { return 404; }}root@Elk02 ~# rm -f /etc/nginx/sites-enabled/default# Ubuntu的默认站点(两个)root@Elk02 ~# rm -f /etc/nginx/sites-available/defaultroot@Elk02 ~# nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successfulroot@Elk02 ~# systemctl reload nginx
# 测试验证root@Elk02 nginx# curl localhost<center><h1>404 Not Found</h1></center>root@Elk02 nginx# curl -L localhost/oldboyedu<h1 style='color: red;'>Elk01~~~</h1>root@Elk02 nginx# curl -L localhost/linux<h1 style='color: red;'>Elk03...</h1>采集web集群日志
1)Filebeat采集nginx访问日志(Elk02)root@Elk02 ~# rm -rf /var/lib/filebeat/root@Elk02 ~# vim /etc/filebeat/config/06-efk-nginx-to-es.yamlfilebeat.inputs:- type: log paths: - /var/log/nginx/access.log
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-efk-nginx-%{+yyyy.MM.dd}" # 按天分割索引
setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称setup.template.name: "efk" # 索引模板名(可自定义)setup.template.pattern: "kpyun-efk*" # 匹配索引名称的模式(根据索引模板初始化)setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 5 # 分片数 index.number_of_replicas: 0 # 副本数root@Elk02 ~# nohup filebeat -e -c /etc/filebeat/config/06-efk-nginx-to-es.yaml &>/tmp/elk01.log &关于 setup.template.overwrite:
false(推荐):模板已存在时不做任何修改,安全默认值true:用当前配置(settings)覆盖已存在的同名模板
调整分片/副本数的推荐做法: 不改 overwrite 去覆盖旧模板;而是新建模板 ✅
setup.template.name: "efk-new"setup.template.pattern: "kpyun-efk-new*"setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 9 index.number_of_replicas: 0💡 新模板名 + 新 pattern → ES 中不存在 → overwrite: false 照样创建成功
老模板不受影响,新数据按新规则建索引
2)Elk01的tomcat数据root@Elk01 ~# dpkg -i /tmp/filebeat-7.17.29-amd64.debroot@Elk01 ~# vim /tmp/tomcat-to-es.yamlfilebeat.inputs:- type: log paths: - /soft/tomcat/logs/*.txt
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-efk-tomcat-%{+yyyy.MM.dd}" # 按天分割索引
setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称setup.template.name: "efk" # 索引模板名(可自定义)setup.template.pattern: "kpyun-efk*" # 匹配索引名称的模式(根据索引模板初始化)setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 5 # 分片数 index.number_of_replicas: 0 # 副本数root@Elk01 ~# nohup filebeat -e -c /tmp/tomcat-to-es.yaml &>/tmp/elk01.log &
2)Elk03的tomcat数据root@Elk03 ~# dpkg -i /tmp/filebeat-7.17.29-amd64.debroot@Elk03 ~# vim /tmp/tomcat-to-es.yamlfilebeat.inputs:- type: log paths: - /soft/tomcat/logs/*.txt
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-efk-tomcat-%{+yyyy.MM.dd}" # 按天分割索引
setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称setup.template.name: "efk" # 索引模板名(可自定义)setup.template.pattern: "kpyun-efk*" # 匹配索引名称的模式(根据索引模板初始化)setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 5 # 分片数 index.number_of_replicas: 0 # 副本数root@Elk03 ~# nohup filebeat -e -c /tmp/tomcat-to-es.yaml &>/tmp/elk01.log &我这里把 tomcat 的日志放在一起了,索引名都是kpyun-efk-tomcat-%{+yyyy.MM.dd}
新建了一个索引模板 efk 和匹配索引名称 kpyun-efk*;5个主分片,0个副本分片
💡 rm -rf /var/lib/filebeat/ 没有出效果就删除了,重新来一遍



# 如下图所示,我们可以分析数据指标1)查看相关字段: - message - host.name
2)KQL语句: - message : 200 and host.name : "Elk02" - message : 404
Filebeat tag 路由到不同索引
一个 Filebeat 实例可以定义多个 input,每个 input 打上不同 tags
输出到 ES 时,用 indices + when.contains 实现==按 tag 路由到不同索引==
1)配置文件root@Elk02 ~# vim /etc/filebeat/config/multiple_input-to-es.yamlfilebeat.inputs:- type: filestream id: tag-log paths: - /tmp/tag-test.log tags: kpyun-log# 给文件采集事件的标签
- type: tcp host: "0.0.0.0:9000" # Filebeat 就在 9000 端口起了一个 TCP 服务端,等你 nc 往里灌数据 tags: kpyun-tcp# 给 TCP 采集事件的标签
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] indices: - index: "kpyun-filebeat-tags-logs-%{+yyyy.MM.dd}" when.contains: tags: "kpyun-log"# 包含 kpyun-log 标签的事件 → 写入 logs 索引 - index: "kpyun-filebeat-tags-tcp-%{+yyyy.MM.dd}" when.contains: tags: "kpyun-tcp"# 包含 kpyun-tcp 标签的事件 → 写入 tcp 索引
setup.ilm.enabled: falsesetup.template.name: "kpyun-fb-tags"setup.template.pattern: "kpyun-filebeat-tags*"setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 3 index.number_of_replicas: 0# 注意:如果已有模板的 pattern 与该模板重叠,需要调整优先级或删除冲突模板'⚠️ 模板 pattern 冲突会导致 Filebeat 连接 ES 失败(400 Bad Request)'
2)启动 Filebeatroot@Elk02 ~# filebeat -e -c /etc/filebeat/config/multiple_input-to-es.yamlInput 'filestream' startingStarted listening for TCP connectionroot@Elk02 ~# ss -ntl | grep 9000LISTEN 0 4096 *:9000 *:*
3)模板冲突# ⚠️ 启动 Filebeat 时反复报 400 错误:ERROR Failed to connect, error loading template: 400 Bad Request: ......==索引模板冲突==:现有模板 kpyun(来自之前实验)的 pattern kpyun-filebeat* 会==包含==新模板 kpyun-fb-tags 的 pattern kpyun-filebeat-tags*
同一个索引同时命中两个同优先级(150)的模板 → ES 拒绝创建
📌 实验环境:直接删 kpyun 模板最快
📌 ==生产环境==:新建模板时避开重叠 pattern,或调高 priority(如 200),不影响已有索引
# 解决方案:删除冲突的旧模板root@Elk01 ~# curl -s -X DELETE "10.0.0.6:9200/_index_template/kpyun"{"acknowledged":true} # 也可通过 WebUI 删除
# 重启 Filebeat,新模板顺利加载root@Elk02 ~# killall -9 filebeat; rm -rf /var/lib/filebeat/root@Elk02 ~# filebeat -e -c /etc/filebeat/config/multiple_input-to-es.yaml
4)发送测试数据# 文件输入 — 写行到 /tmp/tag-test.logroot@Elk02 ~# echo 'log message: first test' > /tmp/tag-test.logroot@Elk02 ~# echo 'line2' >> /tmp/tag-test.log
# TCP 输入 — 通过 nc 发送root@Elk02 ~# echo 'tcp message from netcat' | nc -w 1 10.0.0.7 9000# nc(netcat): TCP/UDP 数据传输工具,这里充当 TCP 客户端,向 Filebeat 端口灌测试数据# nc -w 1: 发送完等1秒自动断开,防止 nc 挂在那不退出
5)验证两个索引分别收到数据root@Elk01 ~# curl -s '10.0.0.6:9200/_cat/indices/kpyun-filebeat-tags*?v'health status index pri rep docs.countgreen open kpyun-filebeat-tags-logs-2026.07.31 3 0 2green open kpyun-filebeat-tags-tcp-2026.07.31 3 0 1# logs 索引 2 条(文件的两行),tcp 索引 1 条(nc 的一条消息)✅️
Filebeat多行匹配
filestream 完全可以替代 log 类型,功能上 log 能做的 filestream 都能做
而且从 7.16.x 开始官方已不再推荐 log 写法,建议统一用 filestream
初体验
1)编写配置文件root@Elk01 ~# killall -9 filebeat# 新的日志文件,不需要 rm -rf /var/lib/filebeat/root@Elk01 ~# vim /tmp/filestream_tomcat-to-es.yamlfilebeat.inputs:- type: filestream paths: - /soft/tomcat/logs/catalina.out # tomcat的系统日志tomcat/logs/catalina.out是Tomcat的标准输出/标准错误重定向日志- JVM 启动过程、部署状态、异常堆栈全部写在这里
- 💡 判断 Tomcat 是否启动成功、报了什么错,第一时间看这个文件就行
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-filestream-tomcat-%{+yyyy.MM.dd}" # 索引名字更换
setup.ilm.enabled: falsesetup.template.name: "filestream" # 模板名字更换setup.template.pattern: "kpyun-filestream*" # 匹配的索引名称更换setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 9 # 模板配置更换 index.number_of_replicas: 0
2)启动实例root@Elk03 ~# nohup filebeat -e -c /tmp/filestream_tomcat-to-es.yaml &>/tmp/elk01.log &-
过滤 filebeat 进程时,用
grep [f]ilebeat代替grep filebeat,可以免去-v grep的麻烦 -
[f]作为正则只匹配单字符f,grep 自身的命令行是字面量[f]ilebeat- 正则匹配不了它,所以 grep 不会命中自身
- 比
-v grep更简练——从源头避免,而非事后排除
3)进程验证# 过滤出两条(默认自带grep)root@Elk01 ~# ps -ef | grep filebeatroot 1803 1552 8 10:36 pts/0 /usr/share/filebeat/bin/filebeat...root 1811 1552 0 10:36 pts/0 grep --color=auto filebeat
# 只过滤出一条root@Elk01 ~# ps -ef | grep filebeat | grep -v greproot 1803 1552 0 10:36 pts/0 /usr/share/filebeat/bin/filebeat...root@Elk01 ~# ps -ef | grep [f]ilebeatroot 1803 1552 3 10:36 pts/0 /usr/share/filebeat/bin/filebeat...
4)kibana查看数据
多行匹配案
1)停止tomcat服务root@Elk01 ~# wc -l /soft/tomcat/logs/catalina.out92 /soft/tomcat/logs/catalina.outroot@Elk01 ~# shutdown.shroot@Elk01 ~# wc -l /soft/tomcat/logs/catalina.out98 /soft/tomcat/logs/catalina.outroot@Elk01 ~# curl -s http://localhost:9200/kpyun-filestream-tomcat-2026.07.28/_search | jq '.hits.total'{ "value": 98, "relation": "eq"}# hits.total.value: 98 = 共 98 条文档(默认按行 \n 采集数据)root@Elk01 ~# tail /soft/tomcat/logs/catalina.out28-Jul-2026 07:52:03.608 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/soft/apache-tomcat-11.0.24/webapps/manager]28-Jul-2026 07:52:03.632 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/soft/apache-tomcat-11.0.24/webapps/manager] has finished in [23] ms
2)修改tomcat的配置文件(模拟报错)root@Elk01 ~# vim +151 /soft/tomcat/conf/server.xml # 主配置文件...151 </Host666666666>....
3)启动tomcatroot@Elk01 ~# startup.sh.....Tomcat started.# 表面起来了,实则没起来root@Elk01 ~# ss -lntup | grep 8080 | wc -l0
4)查看日志root@Elk01 ~# tail -13 /soft/tomcat/logs/catalina.out28-Jul-2026 15:54:53.849 WARNING [main] org.apache.catalina.startup.Catalina.parseServerXml Unable to load server configuration from [/soft/apache-tomcat-11.0.24/conf/server.xml] org.xml.sax.SAXParseException; systemId: ⚠️ file:/soft/apache-tomcat-11.0.24/conf/server.xml⚠️; lineNumber: ⚠️151; columnNumber: 13; The end-tag for element type "Host" must end with a '>' delimiter. ⚠️ at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1252) at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643) at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1621) at org.apache.catalina.startup.Catalina.parseServerXml(Catalina.java:718) at org.apache.catalina.startup.Catalina.load(Catalina.java:817) at org.apache.catalina.startup.Catalina.load(Catalina.java:857) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:302) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:481)28-Jul-2026 15:54:53.850 SEVERE [main] org.apache.catalina.startup.Catalina.start Cannot start server, server instance is not configured
5)文档数# 多行才应该是一条日志root@Elk01 ~# curl -s http://localhost:9200/kpyun-filestream-tomcat-2026.07.28/_search | jq '.hits.total'{ "value": 135, "relation": "eq"}为什么需要多行匹配?
- Filebeat 默认按
\n逐行采集,一条堆栈异常被拆成 N 条文档
核心参数: negate + match 决定哪些行往哪个方向合并
| negate | match | 效果 |
|---|---|---|
| false | after | 匹配的行追加到上一行 |
| false | before | 匹配的行提前到下一行 |
| true | after | 不匹配的行追加到上一行(堆栈专用🦄) |
| true | before | 不匹配的行提前到下一行 |
💡 堆栈场景:negate: true + match: after + pattern: '^\d'
- 以数字开头的是新日志,其余续行全部合并到上一条
- 官方文档参考:Multiline examples
1)环境清理root@Elk01 ~# killall -9 filebeatroot@Elk01 ~# curl -s -X DELETE http://10.0.0.6:9200/kpyun-filestream-tomcat-2026.07.28/ | jq{ "acknowledged": true # 删除之前的索引}
2)准备配置文件root@Elk01 ~# vim /tmp/tomcat-to-es.yamlfilebeat.inputs:- type: filestream paths: - /soft/tomcat/logs/catalina.out # 定义解析器 parsers: # 配置多行匹配 - multiline: # 指定多行匹配的模式,有效值为:pattern,count type: pattern # pattern = 正则匹配合并行,count = 每N行合并为一条文档 # 如果类型是pattern,则需要指定正则表达式 pattern: '^\d' # 以数字开头 → 视为新日志的起始行 negate: true match: after
# 复用上面的索引模板(无需重新创建)output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-filestream-tomcat-%{+yyyy.MM.dd}"
setup.ilm.enabled: falsesetup.template.name: "filestream"setup.template.pattern: "kpyun-filestream*"setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 9 index.number_of_replicas: 0
3)启动实例root@Elk01 ~# rm -rf /var/lib/filebeat/root@Elk01 ~# nohup filebeat -e -c /tmp/tomcat-to-es.yaml &>/tmp/elk01.log &
4)kibana查看数据root@Elk01 ~# curl -s http://localhost:9200/kpyun-filestream-tomcat-2026.07.28/_search | jq '.hits.total'{ "value": 101, ✅ # "value": 135 ❌ "relation": "eq"}# 堆栈异常已合并为单条文档,不再是逐行拆散的碎片
# KQL语句message :at
ELFK架构✨

部署logstash环境

- 官方链接:logstash下载
1)安装logstash# Elk03节点root@Elk03 ~# wget -P /tmp/ https://artifacts.elastic.co/downloads/logstash/logstash-7.17.29-amd64.debroot@Elk03 ~# dpkg -i /tmp/logstash-7.17.29-amd64.deb
2)添加符号链接root@Elk03 ~# ln -svf /usr/share/logstash/bin/logstash /usr/local/bin/-v # 显示创建过程的详细信息(打印出链接名称)-f # 强制覆盖已存在的目标文件'/usr/local/bin/logstash' -> '/usr/share/logstash/bin/logstash'
3)在命令行中测试启动root@Elk03 ~# logstash -e "input { stdin {} } output { stdout {} }"-e # 直接在命令行中写入配置,而不是从配置文件(.conf 文件)中读取# 从键盘读取输入,并在屏幕上打印输出...[ERROR] 2026-07-xxx] jvm - Unknown garbage collector name {:name=>"G1 Concurrent GC"}-
系统设置了
JAVA_HOME,指向了/usr/share/elasticsearch/jdk(Elasticsearch 自带的 JDK)- 这个 JDK 版本(OpenJDK 22.0.2),版本太新了,导致监控接口不兼容
- Logstash 7.17 官方推荐的 JDK 是 Java 11 或 Java 17
4)更改环境变量# 可以直接指向logstash的JDK环境root@Elk03 ~# vim /etc/profile.d/tomcat.sh#!/bin/bash
# export JAVA_HOME=/usr/share/elasticsearch/jdkexport JAVA_HOME=/usr/share/logstash/jdkexport TOMCAT_HOME=/soft/tomcatexport PATH=$PATH:$JAVA_HOME/bin:$TOMCAT_HOME/binroot@Elk03 ~# unset JAVA_HOME# 清除掉之前的java环境变量root@Elk03 ~# source /etc/profile.d/tomcat.shroot@Elk03 ~# exit# 退出当前会话重新SSH连接 ✅(这个生效)jiuzhao@Ubuntu ~$ ssh Elk03root@Elk03 ~# java --versionopenjdk 11.0.26 2025-01-21'Tomcat 11.0.24 只支持 Java 17+'# Tomcat 11 启动错误,根本原因是 JDK 版本与 Tomcat 不兼容root@Elk03 ~# echo $JAVA_HOME/usr/share/logstash/jdk
5)重新启动(命令行)root@Elk03 ~# logstash -e "input { stdin {} } output { stdout {} }"-e # 直接在命令行中写入配置,而不是从配置文件(.conf 文件)中读取......The stdin plugin is now waiting for input:www.kpyun.com # 这是输入的数据{ "message" => "www.kpyun.com", # 这是采集到的数据 "host" => "Elk03", "@timestamp" => 2026-07-28T11:48:32.852Z, "@version" => "1"} # 这里不是json格式
6)在配置文件中测试启动root@Elk03 ~# vim /etc/logstash/conf.d/01-stdin-to-stdout.confinput { stdin {}}
output { stdout { # 指定输出的数据格式,如果不指定,则默认值为: rubydebug codec => "json" }}root@Elk03 ~# logstash -f /etc/logstash/conf.d/01-stdin-to-stdout.conf --config.test_and_exit-f # 指定配置文件--config.test_and_exit # 干跑Configuration OK ✅ 配置文件正确root@Elk03 ~# logstash -f /etc/logstash/conf.d/01-stdin-to-stdout.confThe stdin plugin is now waiting for input:......Hello # json格式输出{"message":"Hello","host":"Elk03","@version":"1","@timestamp":"2026-07-28T12:37:59.541Z"}采集文本案例
1)准备配置文件root@Elk03 ~# vim /etc/logstash/conf.d/02-file-to-es.confinput { file { # 指定文件的路径 path => ["/tmp/*.log"]
# 指定第一次采集时的起始位置,有效值为: beginning, end start_position => "beginning" }}
output { # stdout { # codec => "json" # }
elasticsearch { # 指定ES集群的地址 hosts => ["10.0.0.6:9200","10.0.0.7:9200","10.0.0.8:9200"] # 指定ES集群的索引名称 index => "kpyun-test-logstash-%{+YYYY.MM.dd}" }}
2)启动logstash--config.test_and_exit # 先干跑一遍root@Elk03 ~# logstash -rf /etc/logstash/conf.d/02-file-to-es.conf-r # --reload(自动重载配置)-f # --config(指定配置文件)# 关键日志:# Elasticsearch version determined (7.17.29) → 已确定版本(7.17.29)# Pipeline started → 管道启动成功# Pipelines running {:count=>1} → 1 条管道在跑
3)发送测试数据# Elk01远程执行root@Elk01 ~# ssh 10.0.0.8 "echo 666666666666666666 >> /tmp/haha.log"
4)kibana出图展示# 如下图所示创建+索引模式(kpyun-test*)--> Discover --> KQL语句(message: 666666666666666666)
# 默认数据目录的内容root@Elk03 ~# ls -la /usr/share/logstash/data/total 20drwxr-xr-x 4 root root 4096 Jul 30 16:29 .drwxr-xr-x 12 root root 4096 Jul 30 16:29 ..drwxr-xr-x 2 root root 4096 Jul 30 16:29 dead_letter_queue-rw-r--r-- 1 root root 0 Jul 30 16:29 .lockdrwxr-xr-x 2 root root 4096 Jul 30 16:29 queue-rw-r--r-- 1 root root 36 Jul 30 16:29 uuiddrwxr-xr-x 3 root root 4.0K Jul 31 16:36 plugins
# 温馨提示:logstash的 `file input(从文件中读取)` 是有缓存的,也是在相应的数据目录中的".sincedb"文件中记录;root@Elk03 ~# ll /usr/share/logstash/data/plugins/inputs/file/total 12-rw-r--r-- 1 root root 106 Jul 28 21:12 .sincedb_f673f6cfb25c903a69af206b740195d3💡 plugins/inputs/file/ 目录是 file input ==专属的==,beats input 不产生,无需关心
也就是只有通过文件读取的日志才有这个目录,如果 logstash 是监听的端口,则不会产生数据
.sincedb 记录了每个文件读取到的位置(offset),logstash 重启后从上次位置继续,不会重复采集
如果改完配置想全量重采,删掉这个文件即可
自研apps日志案例
非结构化日志
1)模拟APP生成测试日志# Elk01节点执行即可root@Elk01 ~# vim generate_log.py#!/usr/bin/env python3
import datetimeimport randomimport loggingimport timeimport sys
LOG_FORMAT = "%(levelname)s %(asctime)s [com.kpyun.%(module)s] - %(message)s "DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# 配置root的logging.Logger实例的基本配置logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT, filename=sys.argv[1], filemode='a',)actions = ["浏览页面", "评论商品", "加入收藏", "加入购物车", "提交订单", "使用优惠券", "领取优惠券", "搜索", "查看订单", "付款", "清空购物车"]
while True: time.sleep(random.randint(1, 5)) user_id = random.randint(1, 10000) # 对生成的浮点数保留2位有效数字. price = round(random.uniform(15000, 30000),2) action = random.choice(actions) svip = random.choice([0,1,2]) logging.info("DAU|{0}|{1}|{2}|{3}".format(user_id, action,svip,price))
2)查看测试数据root@Elk01 ~# python3 generate_log.py /tmp/apps.log# 需要传参数(文件路径参数)root@Elk01 ~# tail /tmp/apps.log# 非结构化数据 --> logstashINFO 2026-07-29 15:26:20 [com.kpyun.generate_log] - DAU|2738|查看订单|1|29612.77INFO 2026-07-29 15:26:25 [com.kpyun.generate_log] - DAU|3043|领取优惠券|1|28290.65INFO 2026-07-29 15:26:29 [com.kpyun.generate_log] - DAU|4096|清空购物车|2|25508.63INFO 2026-07-29 15:26:30 [com.kpyun.generate_log] - DAU|6544|查看订单|0|25883.05INFO 2026-07-29 15:26:32 [com.kpyun.generate_log] - DAU|3719|搜索|1|16282.12INFO 2026-07-29 15:26:36 [com.kpyun.generate_log] - DAU|462|浏览页面|1|20617.06INFO 2026-07-29 15:26:38 [com.kpyun.generate_log] - DAU|5175|提交订单|0|19346.11INFO 2026-07-29 15:26:42 [com.kpyun.generate_log] - DAU|8737|提交订单|2|16317.69INFO 2026-07-29 15:26:43 [com.kpyun.generate_log] - DAU|7504|付款|0|28776.08INFO 2026-07-29 15:26:44 [com.kpyun.generate_log] - DAU|8776|浏览页面|1|16240.77Filebeat写入数据到logstash
1)编写logstash的配置文件'logstash安装在Elk03'root@Elk03 ~# vim /etc/logstash/conf.d/03-beats-to-es.confinput { beats { port => "6666" # logstash作为服务端监听此端口 }}
# 过滤插件filter { mutate { remove_field => [ "@version","agent","log","ecs","tags","input" ] } # 先过滤掉无关字段,保持数据干净}
output { stdout { # 终端输出——调试用,确认数据过来了 codec => "rubydebug" }}# 写入ES# elasticsearch {# hosts => ["10.0.0.6:9200","10.0.0.7:9200","10.0.0.8:9200"]# index => "kpyun-logstash-apps-%{+YYYY.MM.dd}"# }'先把ES注释掉,stdout看数据过来没有'
2)启动logstash# 启动慢(重量级)root@Elk03 ~# logstash -rf /etc/logstash/conf.d/03-beats-to-es.conf --config.test_and_exit# 先干跑一遍root@Elk03 ~# logstash -rf /etc/logstash/conf.d/03-beats-to-es.conf# Pipeline started ✅# Pipelines running {:count=>1}
3)编写Filebeat的配置文件'切到Elk01'root@Elk01 ~# mkdir /etc/filebeat/config/root@Elk01 ~# vim /etc/filebeat/config/apps-to-logstash.yamlfilebeat.inputs:- type: filestream paths: - /tmp/apps.log # 采集自研APP的日志
output.logstash: hosts: ["10.0.0.8:6666"] # 发往logstash,不再是直接发ES# ✅️ filebeat → logstash → ES 的三级架构
4)启动Filebeat实例root@Elk01 ~# filebeat -e -c /etc/filebeat/config/apps-to-logstash.yaml# Connection to backoff(async(tcp://10.0.0.8:6666)) established
5)查看logstash终端输出'logstash这边能看到rubydebug格式的数据就说明通了'{ "message" => "INFO 2026-07-29 09:11:43 [com.kpyun.generate_log] - DAU|7173|清空购物车|0|24648.72 ", "@timestamp" => 2026-07-29T01:11:43.446Z, "host" => { "name" => "Elk01" }}# 字段非常干净,只有必要的被保留下来的,源于我们的filter过滤插件为什么用 Filebeat → Logstash → ES 三级架构?
- ==Filebeat== 只负责采集——轻量、不占资源,放在每个需要采集的机器上
- ==Logstash== 负责加工——字段拆分、类型转换、日期格式化、数据清洗
- ==ES== 负责入库 + 检索——数据落盘、全文搜索、聚合计算,Kibana 每张图背后都是它在算
💡 一条日志的旅程:采集 → 加工 → 存储,各干各的,谁也别越俎代庖
使用mutate处理数据案例
1)编写配置文件root@Elk03 ~# vim /etc/logstash/conf.d/03-beats-to-es.conf...........filter {
mutate { split => { "message" => "|" } # 以竖线为分隔符切割
add_field => { # 把切割后的数组元素映射为独立字段 "other" => "%{[message][0]}" # 数组下标从0开始——other = "INFO 2026-07-29..." "userid" => "%{[message][1]}" "action" => "%{[message][2]}" "svip" => "%{[message][3]}" "price" => "%{[message][4]}" } }
mutate { split => { "other" => " " } # 再次切割,"INFO" = [0], "2026-07-29" = [1], "08:21:55" = [2]
add_field => { "date" => "%{[other][1]} %{[other][2]}" # 拼接为 "2026-07-29 08:21:55" }}
mutate { convert => { # 类型转换——确保数字类型可以去kibana做数学聚合 "userid" => "integer" # 整型 "price" => "float" # 浮点型 # 💡 不转换的话在ES里是text类型,没法做sum/avg }remove_field => [ "@version","agent","log","ecs","tags","input","message","host","other" ] # 清理掉所有中间字段,只保留业务字段 }}...........'这就是logstash的核心价值——把脏数据洗干净再入库'# 配置文件中不要有中文注释,会导致 Logstash 的解析器字节 --> 偏移算歪input { beats { port => "6666" }}
filter {
mutate { split => { "message" => "|" }
add_field => { "other" => "%{[message][0]}" "userid" => "%{[message][1]}" "action" => "%{[message][2]}" "svip" => "%{[message][3]}" "price" => "%{[message][4]}" } }
mutate { split => { "other" => " " }
add_field => { "date" => "%{[other][1]} %{[other][2]}" }}
mutate { convert => { "userid" => "integer" "price" => "float" }remove_field => [ "@version","agent","log","ecs","tags","input","message","host","other" ] }}
output { stdout { codec => "rubydebug" }}2)启动实例root@Elk03 ~# logstash -rf /etc/logstash/conf.d/03-beats-to-es.conf --config.test_and_exit# 干跑root@Elk03 ~# logstash -rf /etc/logstash/conf.d/03-beats-to-es.conf'预期输出'{ "@timestamp" => 2026-07-29T06:52:34.942Z, "action" => "搜索", "svip" => "0", "userid" => 2880, "price" => 28736.94, "date" => "2026-07-29 14:52:34"}✅️ 字段拆分成功# 注意此时 @timestamp 是采集时间,不是日志的真实时间 👇(date字段才是)mutate 是 logstash 的瑞士军刀——split、add_field、remove_field、convert 4 个操作一组,处理非结构化日志的标准套路:
切分 → 映射 → 清理 → 转换
遇到新的日志格式,套这个模板就行

-
链接:官方文档
-
搜索
Logstash—> other versions —> filter过滤插件 —> mutate字段过滤插件


使用date插件处理日期

- 官网链接:date字段过滤插件
1)为什么需要date插件?# 上面的数据中 @timestamp 显示的是 "filebeat采集这条日志的时刻"# 而日志中有真实的时间: "2026-07-29 14:52:34"# ❌ 这两个可能差几秒甚至几分钟——业务分析要以日志里的真实时间为准!
2)编写配置文件(在之前的filter块中追加date段)root@Elk03 ~# vim /etc/logstash/conf.d/03-beats-to-es.conf............ date { # "2026-07-29 14:52:34" → @timestamp match => [ "date", "yyyy-MM-dd HH:mm:ss" ] # 格式必须与date字段的实际格式严格一致 target => "@timestamp" # 🦄 亮点:用日志里的时间覆盖 @timestamp }}............input { beats { port => "6666" }}
filter { mutate { split => { "message" => "|" } add_field => { "other" => "%{[message][0]}" "userid" => "%{[message][1]}" "action" => "%{[message][2]}" "svip" => "%{[message][3]}" "price" => "%{[message][4]}" } }
mutate { split => { "other" => " " } add_field => { "date" => "%{[other][1]} %{[other][2]}" } remove_field => [ "@version","agent","log","ecs","tags","input","message","host","other" ] }
mutate { convert => { "userid" => "integer" "price" => "float" } }
date { match => [ "date", "yyyy-MM-dd HH:mm:ss" ] target => "@timestamp" }}
output { stdout { codec => "rubydebug" }
elasticsearch { hosts => ["10.0.0.6:9200","10.0.0.7:9200","10.0.0.8:9200"] index => "kpyun-logstash-apps-%{+YYYY.MM.dd}" }}3)预创建模板和索引匹配模式curl -X PUT "10.0.0.6:9200/_index_template/kpyun-apps" -H 'Content-Type: application/json' -d'{ "index_patterns": ["kpyun-logstash-apps-*"], "template": { "settings": { "number_of_shards": 6, "number_of_replicas": 0 } }}'# 6个主分片,0个副本分片
4)清除logstash环境并启动实例root@Elk03 ~# killall -9 logstashlogstash: no process found# logstash 的进程名是 java,killall 匹配不到# ⚠️ killall -9 java 会误杀 ES 集群!# 只能用 pkill -f 模糊匹配命令行,而非进程名(宽泛匹配,容易误杀,谨慎使用)root@Elk03 ~# pkill -f -9 logstash` rm -rf /usr/share/logstash/data/ `# 上一轮用的 file input,清理 sincedb;用 beats input 则不需要清理# beats input 监听6666端口,数据从 Filebeat 流入,filebeat清理即可root@Elk03 ~# logstash -rf /etc/logstash/conf.d/03-beats-to-es.conf
5)日志准备并启动filebeat实例root@Elk01 ~# > /tmp/apps.logroot@Elk01 ~# python3 generate_log.py /tmp/apps.log# 再开一个终端root@Elk01 ~# rm -rf /var/lib/filebeat/root@Elk01 ~# filebeat -e -c /etc/filebeat/config/apps-to-logstash.yaml
6)验证ES数据root@Elk01 ~# curl -s 'http://localhost:9200/kpyun-logstash-apps-2026.07.29/_search?pretty' | jq '.hits.total,.hits.hits[0]._source'{ "value": 38, # 数据已入库 ✅ "relation": "eq"}{ # ✅ @timestamp 已替换为日志里的真实时间 "@timestamp": "2026-07-29T08:21:51.000Z", "action": "清空购物车", "svip": "2", "userid": 2686, "date": "2026-07-29 16:21:51", "price": 20555.2}
7)kibana创建索引模式'Management → Stack Management → Index Patterns → Create'# 模式:kpyun-logstash-apps-*# 时间字段:@timestamp
date 插件的作用就一句话:
把 @timestamp(filebeat 的”我什么时候采集到的”)替换成日志里写的真实时间(“这件事什么时候发生的”)
💡 没替换的后果:Kibana 时间轴上显示的会偏后几秒到几分钟——不影响查数据,但 ==出图不准确==

分析自定义程序的业务指标
🧱 前置条件:ES 索引 kpyun-logstash-apps-* 中已有结构化数据
字段清单:userid(integer) | svip(keyword) | price(float) | action(keyword) | @timestamp(date)
以下操作均在 Kibana Web UI 中完成——用鼠标点点就行
访问量PV统计
'统计每天的访问总量'kibana: 创建可视化 → 基于聚合 → 指标 → 选择索引 → 聚合(默认计数)→ 定制标签(PV)
分析用户的数量
'去重后 userid 的简单计数'kibana: 唯一计数 → 字段(userid)→ 定制标签(用户数分析)# 另存为新的视图
分析平台的交易额
'GMV——总成交额'kibana: 求和 → 字段(price)→ 定制标签(平台交易额)# 另存为新的视图# 注意 price 必须在 logstash mutate → convert 中转为 float/integer# 否则这里看到的 price 会显示为字符串,没法做数学聚合 ❌✅️ 数据类型转换的重要性在此体现!
分析SVIP用户占比
'看看会员等级分布'kibana: 创建可视化 → Lens → 索引 → 字段(svip) → 圆环图# svip=0 普通用户; svip=1 银卡; svip=2 金卡# 能看出来什么群体在平台上最活跃
用户行为分析
'用户都在干什么?'kibana: 创建可视化 → Lens → 索引 → 字段(action) → 圆环图# 浏览页面、搜索、加入购物车、提交订单、付款...# CEO要的就是这个图——知道哪一步流失最多
按 SVIP 等级拆分的用户行为分布(svip + action交叉分析)

制作Dashboard
'把所有图汇总到一张仪表盘——给老板看'kibana: Dashboard → 新建仪表盘 → 从库中添加 → 拖拽排列 → 保存# 一张图展示 PV/用户数/交易额/SVIP占比/用户行为 → 业务全貌一目了然
ElasticStack故障排查技巧指南
🧱 面试高频——ELK 出问题了你怎么排查?
核心口诀:跟着数据走,从源头往下查——源 → 采集 → 加工 → 索引 → 展示,一层一层确认,哪环断了问题就在哪
EFK架构排查技巧

排查口诀(EFK 四步走):
① 源:app 到底有没有打出日志?→ 去对应机器上 cat / tail 日志文件② 采:filebeat 还活着吗?→ ps -ef | grep [f]ilebeat③ 采:filebeat 采的是你要的文件吗?→ 确认配置文件里 paths 路径没写错 # 需要重采时 `rm -rf /var/lib/filebeat/` 清掉 registry④ 存:ES 本身是否正常?→ `ss -lntup | grep 9200` 看端口 / `curl localhost:9200` 看响应 # Kibana 查 `systemctl status kibana`,ES也可以查 `systemctl status elasticsearch` # ES 和 Kibana 是 systemd 服务,Filebeat 是裸进程
排查清单(7 条,从可能到不可能):
1. 源数据文件本身就空 → `wc -l /path/to/log` 先看一眼 2. filebeat 进程没跑 → `ps -ef | grep [f]ilebeat` 3. filebeat 配错了 → paths 路径写错 / output 写错 IP / 索引名写错 4. ES 集群故障 → `ss -lntup | grep 9200` 看端口 / `curl localhost:9200/_cluster/health` 看集群状态,Kibana 查 `systemctl status kibana`,ES 和 Kibana 是 service,filebeat 是裸进程 5. filebeat registry 记录 offset → `rm -rf /var/lib/filebeat/` 重置 6. kibana 时间范围不对 → "Last 15 minutes" 改成 "Today" 7. ES 脑裂 → filebeat 连的节点和 kibana 连的不是同一个 → `GET /_cat/nodes`💡 EFK 排查一句话: 三条腿折了哪一条?(源 / 采集 / 存储)
别看日志先看进程——filebeat 看 ps,ES / Kibana 看 systemctl status,确认三个东西都活着,已经排掉一半的坑
ELFK架构排查技巧

排查口诀(ELFK 五步走——多了一个 Logstash):
① 源:日志打了没有?(同 EFK,tail 源文件)② 采:Filebeat 活着 + 采对了?(同 EFK,ps + 看 paths + 看 output.logstash)③ 加:Logstash 活着吗?→ `ps -ef | grep [l]ogstash` # 端口开了没?→ `ss -lntup | grep 6666`,防火墙拦了?→ `iptables -L`④ 加:Logstash 配置对吗?→ 先 `--config.test_and_exit` 干跑,再 stdout rubydebug 看数据过来没⑤ 存:ES / Kibana 正常吗?(同 EFK,curl + systemctl status elasticsearch/kibana)
排查清单(ELFK 比 EFK 多出来的坑):
1. logstash 进程挂 → `ps -ef | grep [l]ogstash` 2. logstash 端口没监听 → `ss -lntup | grep 6666`(防火墙?`iptables -L`) 3. logstash 配置语法错 → `logstash -f xxx.conf --config.test_and_exit` 干跑 4. filebeat → logstash 路没通 → filebeat 日志看 output 连接状态 5. logstash output 忘了配 ES → stdout 开着,ES 段还注释着 6. logstash filter 处理异常 → stdout rubydebug 看字段长什么样 7. ES 有数据但 kibana 出不了图 → 字段类型错了(price 应该是数值,不是string)
💡 源 / 采 / 存的共性问题 → 见上方 EFK 排查清单📌 ELFK 对比 EFK 的核心差异:
多了一个 ==Logstash==,排查就多了一环——“filebeat 已经把数据送出去了,logstash 到底收没收到?”
验证方法:logstash 配置里开 stdout { codec => rubydebug } 是最快的定位手段 ✅
课后作业
🦄 编写程序模拟 nginx 访问日志 10w 条记录,并使用 EFK 架构分析全球用户分布图、设备类型等信息
# ===== /root/generate_nginx_log.py =====#!/usr/bin/env python3import random, time
# 伪造IP池——覆盖多个国家的地址段(用于 GeoIP 出图)IPS = [ "1.2.3.4", # 澳大利亚 "8.8.8.8", # 美国 "13.107.42.14", # 美国(微软) "31.13.24.12", # 爱尔兰 "47.96.0.1", # 中国杭州 "52.84.0.1", # 美国 "59.24.3.1", # 韩国 "77.88.55.88", # 俄罗斯 "103.235.46.39", # 日本 "110.242.68.66", # 中国北京 "151.101.1.1", # 美国(Fastly) "185.199.108.153", # 德国 "202.108.22.5", # 中国北京 "203.119.24.1", # 越南 "216.58.200.4", # 美国(Google)]STATUS = [200]*70 + [301]*5 + [304]*10 + [404]*10 + [500]*5# 200 占70%,模拟正常流量
URLS = [ "/", "/index.html", "/api/health", "/api/products", "/api/users", "/api/orders", "/about", "/contact", "/images/logo.png", "/css/main.css", "/js/app.js", "/docs/api-reference", "/blog/post/2024", "/products/list", "/login", "/register", "/cart", "/checkout"]
UAS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1', 'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.792.99 Mobile Safari/537.36', 'Mozilla/5.0 (iPad; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/135.0',]
with open("/var/log/nginx/access.log", "a") as f: for i in range(100000): ip = random.choice(IPS) url = random.choice(URLS) status = random.choice(STATUS) body_size = random.randint(100, 51200) # 模拟不同大小的请求体 ua = random.choice(UAS) f.write(f'{ip} - - [29/Jul/2026:08:00:00 +0800] "GET {url} HTTP/1.1" {status} {body_size} "-" "{ua}"\n') if (i + 1) % 10000 == 0: print(f"已生成 {i + 1}/100000 条...")print("✅️ 完成! 共 100000 条日志")# 只有Elk02有Nginx'操作流程'1)生成日志root@Elk02 ~# vim /root/generate_nginx_log.pyroot@Elk02 ~# python3 /root/generate_nginx_log.py# 10w条大约需要2-3分钟
2)Filebeat配置文件root@Elk02 ~# filebeat modules list | head -3Enabled:nginx# 启用nginx模块采集root@Elk02 ~# egrep -v "^.*#|^$" /etc/filebeat/modules.d/nginx.yml- module: nginx access: enabled: true var.paths: ["/var/log/nginx/access.log*"] # 启用访问日志并指定路经 error: enabled: false # 错误日志采集(关闭) ingress_controller: enabled: false # K8s Ingress 日志(普通 nginx 不需要)
3)编写Filebeat的配置文件root@Elk02 ~# vim /etc/filebeat/config/nginx-10w-to-es.yamlfilebeat.config.modules: # 启用模块配置 path: ${path.config}/modules.d/*.yml # 指定模块的路径(找所有已启用的模块) # 此处的'${path.config}'会找到模块的配置文件路径,启动Filebeat时会定义该参数 reload.enabled: true # 是否支持热加载配置文件
output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-nginx-10w-%{+yyyy.MM.dd}"
setup.ilm.enabled: falsesetup.template.name: "nginx-10w"setup.template.pattern: "kpyun-nginx-10w*"setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 3 index.number_of_replicas: 0
3)启动Filebeatroot@Elk02 ~# rm -rf /var/lib/filebeat/root@Elk02 ~# nohup filebeat -e -c /etc/filebeat/config/nginx-10w-to-es.yaml &>/tmp/es01.log &
4)Kibana出图展示(Web UI操作)✅️ 创建索引模式: kpyun-nginx-10w-*✅️ 全球用户分布图: Maps → 添加图层 → 文档 → 选择索引 → 地理空间字段(source.geo.location)✅️ 设备类型分析: Lens → user_agent.device.name✅️ 操作系统占比: Lens → user_agent.os.full✅️ PV/带宽/Dashboard... 同上面的操作思路
裸文本采集(type: log)不会做 GeoIP 解析——source.geo.location nginx 模块才会 ✅ (IP 不能只嵌在 message 字符串里)



文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!















