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

8540 字
43 分钟
Kibana数据可视化与ELFK架构实战
Kibana数据可视化与ELFK架构实战

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

[TOC]


kibana出图展示#

打开搜索
打开搜索

  • 昨天的成果

统计PV#

image-20260727150706299
image-20260727150706299

Terminal window
# 进入到可视化库界面
基于聚合 --> 指标 --> 选择索引J(kpyun-filebeat-modules-nginx...) --> 24
root@Elk02 ~# wc -l /var/log/nginx/access.log
24 /var/log/nginx/access.log
# 共 24 条访问记录
==============================
# 停掉所有 filebeat
killall -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 条

Terminal window
PV: 点击一次页面、刷新一次页面被网站记录一次PV
# 页面的访问量
UV: 一台主机就是一个UV、
# 独立访客数量
IP: 独立的公网IP地址
DAU:每天的活跃用户数量(日活)
MAU:每月的活跃用户数量(月活)
举个例子:
假设公司有一座大厦,大厦有100人,每个人有一台电脑和一部手机,上网都是通过nat转换出口,每个人每台设备点击网站2次, 请问对应的pv,uv,ip分别是多少?
PV: 100X2X2=400
UV: 100X2=200
IP: 1
'很多时候,访问到不了后台,缓存就解决了'

PV统计
PV统计

分析IP#

基于聚合 --> 指标 --> 选择索引(kpyun-filebeat-modules-nginx...) --> 指定字段(related.ip)

IP统计
IP统计

设备类型#

创建+可视化 --> 选择Lens --> 选择索引 --> 选择字段(user_agent.device.name)

Lens
Lens

设备类型分析
设备类型分析

操作系统用户占比#

创建+可视化 --> 选择Lens --> 选择索引 --> 选择特定字段(user_agent.os.full)

操作系统用户占比
操作系统用户占比

# 查看现有的图列表
如下图所示,我们已经画了4张图了!

可视化库
可视化库

分析流量带宽#

创建+可视化 --> 选择Lens --> 选择索引 --> 选择特定字段(http.response.body.bytes)
# 搜索bytes(字节)

带宽
带宽

Terminal window
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 | bc
51200
# 增加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.log
root@Elk02 ~# wc -l /var/log/nginx/access.log
25 /var/log/nginx/access.log

同步更新带宽
同步更新带宽

全球用户分布图#

创建+可视化 --> 选择maps --> 添加图层 --> 文档 --> 选择索引 --> 地理空间字段(source.geo.location)

用户分布图
用户分布图

作Dashboard展示数据#

新建仪表盘 --> 从库中添加 --> 展示数据并保存Dashboard

新建仪表盘
新建仪表盘

仪表盘
仪表盘

EFK架构采集web集群日志#

EFK分析web集群架构图解
EFK分析web集群架构图解

需求说明: - 在Elk01和Elk03部署tomcat,要求首页内容不一样; - 在Elk02节点部署nginx服务作为统一的访问入口,要求如下: - 1.访问路径为: /oldboyedu时请求转发到Elk01节点; - 2.访问路径为: /linux时请求转发到Elk03节点; - 使用EFK架构分析整个web集群的访问日志;

Tomcat部署#

Terminal window
1)JDk版本(ES自带)
root@Elk03 ~# /usr/share/elasticsearch/jdk/bin/java -version
openjdk version "22.0.2" 2024-07-16
OpenJDK 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)部署tomcat
root@Elk03 ~# mkdir /soft
root@Elk03 ~# tar xf /tmp/apache-tomcat-11.0.24.tar.gz -C /soft/
# 解压到/soft
root@Elk03 ~# ll /soft/
total 12
drwxr-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/bash
export JAVA_HOME=/usr/share/elasticsearch/jdk
export TOMCAT_HOME=/soft/tomcat
export PATH=$PATH:$JAVA_HOME/bin:$TOMCAT_HOME/bin
root@Elk03 ~# source /etc/profile.d/tomcat.sh
root@Elk03 ~# java --version
openjdk 22.0.2 2024-07-16
OpenJDK Runtime Environment (build 22.0.2+9-70)
OpenJDK 64-Bit Server VM (build 22.0.2+9-70, mixed mode, sharing)
4)运行Tomcat
root@Elk03 ~# startup.sh
......Tomcat started.
# /soft/tomcat/bin/startup.sh --> 绝对路径启动Tomcat
root@Elk03 ~# ss -lntup |grep 8080
tcp LISTEN 0 100 *:8080 (("java",pid=2679,fd=44))
5)访问web UI
http://10.0.0.8:8080/
6)访问日志
root@Elk03 ~# cat /soft/tomcat/logs/localhost_access_log.2026-07-27.txt
10.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET / HTTP/1.1" 200 11237
10.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /tomcat.css HTTP/1.1" 200 5584
10.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /tomcat.svg HTTP/1.1" 200 67795
10.0.0.1 - - [27/Jul/2026:21:27:36 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 7089
10.0.0.1 - - [27/Jul/2026:21:27:42 +0800] "GET /host-manager/html HTTP/1.1" 403 3104
10.0.0.1 - - [27/Jul/2026:21:27:42 +0800] "GET /host-manager/images/favicon.ico HTTP/1.1" 403 3104
10.0.0.1 - - [27/Jul/2026:21:27:46 +0800] "GET /manager/html HTTP/1.1" 403 3523
10.0.0.1 - - [27/Jul/2026:21:27:46 +0800] "GET /manager/images/favicon.ico HTTP/1.1" 403 3523
10.0.0.1 - - [27/Jul/2026:21:46:39 +0800] "GET /w3er HTTP/1.1" 404 721
10.0.0.1 - - [27/Jul/2026:21:47:01 +0800] "GET /docs/ HTTP/1.1" 403 877
10.0.0.1 - - [27/Jul/2026:21:47:05 +0800] "GET /docs/ HTTP/1.1" 403 877
10.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.txt
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET / HTTP/1.1" 200 11237
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /tomcat.svg HTTP/1.1" 200 67795
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /tomcat.css HTTP/1.1" 200 5584
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-nav.png HTTP/1.1" 200 1401
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-middle.png HTTP/1.1" 200 1918
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 7089
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-upper.png HTTP/1.1" 200 3103
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /bg-button.png HTTP/1.1" 200 713
10.0.0.1 - - [27/Jul/2026:21:51:01 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
10.0.0.1 - - [27/Jul/2026:21:51:04 +0800] "GET /manager/status HTTP/1.1" 403 3523
10.0.0.1 - - [27/Jul/2026:21:51:04 +0800] "GET /manager/images/favicon.ico HTTP/1.1" 403 3523
10.0.0.1 - - [27/Jul/2026:21:51:16 +0800] "GET / HTTP/1.1" 200 11237
10.0.0.1 - - [27/Jul/2026:21:51:29 +0800] "GET /test HTTP/1.1" 404 721

准备tomcat首页内容#

Terminal window
root@Elk01 ~# rm -rf /soft/tomcat/webapps/ROOT/*
root@Elk01 ~# echo "<h1 style='color: red;'>Elk01~~~</h1>" > /soft/tomcat/webapps/ROOT/index.html
root@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.html
root@Elk03 ~# curl localhost:8080
<h1 style='color: red;'>Elk03...</h1>

nginx代理tomcat应用#

Terminal window
root@Elk02 ~# vim /etc/nginx/conf.d/lb-tomcat.conf
server {
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/default
root@Elk02 ~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@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集群日志#

Terminal window
1)Filebeat采集nginx访问日志(Elk02)
root@Elk02 ~# rm -rf /var/lib/filebeat/
root@Elk02 ~# vim /etc/filebeat/config/06-efk-nginx-to-es.yaml
filebeat.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: false
setup.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 &
Tip

关于 setup.template.overwrite:

  • false(推荐):模板已存在时不做任何修改,安全默认值
  • true:用当前配置(settings)覆盖已存在的同名模板

调整分片/副本数的推荐做法: 不改 overwrite 去覆盖旧模板;而是新建模板 ✅

setup.template.name: "efk-new"
setup.template.pattern: "kpyun-efk-new*"
setup.template.overwrite: false
setup.template.settings:
index.number_of_shards: 9
index.number_of_replicas: 0

💡 新模板名 + 新 pattern → ES 中不存在 → overwrite: false 照样创建成功 老模板不受影响,新数据按新规则建索引

Terminal window
2)Elk01的tomcat数据
root@Elk01 ~# dpkg -i /tmp/filebeat-7.17.29-amd64.deb
root@Elk01 ~# vim /tmp/tomcat-to-es.yaml
filebeat.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: false
setup.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.deb
root@Elk03 ~# vim /tmp/tomcat-to-es.yaml
filebeat.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: false
setup.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 &
Note

我这里把 tomcat 的日志放在一起了,索引名都是kpyun-efk-tomcat-%{+yyyy.MM.dd}

新建了一个索引模板 efk 和匹配索引名称 kpyun-efk*;5个主分片,0个副本分片 💡 rm -rf /var/lib/filebeat/ 没有出效果就删除了,重新来一遍

索引
索引

索引模板
索引模板

创建索引模式
创建索引模式

Terminal window
# 如下图所示,我们可以分析数据指标
1)查看相关字段:
- message
- host.name
2)KQL语句:
- message : 200 and host.name : "Elk02"
- message : 404

KQL语句
KQL语句


Filebeat tag 路由到不同索引#

Note

一个 Filebeat 实例可以定义多个 input,每个 input 打上不同 tags 输出到 ES 时,用 indices + when.contains 实现==按 tag 路由到不同索引==

Terminal window
1)配置文件
root@Elk02 ~# vim /etc/filebeat/config/multiple_input-to-es.yaml
filebeat.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: false
setup.template.name: "kpyun-fb-tags"
setup.template.pattern: "kpyun-filebeat-tags*"
setup.template.overwrite: false
setup.template.settings:
index.number_of_shards: 3
index.number_of_replicas: 0
# 注意:如果已有模板的 pattern 与该模板重叠,需要调整优先级或删除冲突模板
'⚠️ 模板 pattern 冲突会导致 Filebeat 连接 ES 失败(400 Bad Request)'
2)启动 Filebeat
root@Elk02 ~# filebeat -e -c /etc/filebeat/config/multiple_input-to-es.yaml
Input 'filestream' starting
Started listening for TCP connection
root@Elk02 ~# ss -ntl | grep 9000
LISTEN 0 4096 *:9000 *:*
3)模板冲突
# ⚠️ 启动 Filebeat 时反复报 400 错误:
ERROR Failed to connect, error loading template: 400 Bad Request: ......

📊 Elasticsearch 模板冲突场景

📁 新模板(待创建)

📁 已存在的模板

✅匹配

✅匹配

拒绝创建

🔵 kpyun

pattern: kpyun-filebeat*

priority: 150

🟡 kpyun-fb-tags

pattern: kpyun-filebeat-tags*

priority: 150

📄 待创建的索引

kpyun-filebeat-tags-logs-2026.07.31

⚖️ 冲突检测

两个模板同时命中

优先级相同(150)

❌ 400 Bad Request

multiple index templates may not match

📊 Elasticsearch 模板冲突场景

📁 新模板(待创建)

📁 已存在的模板

✅匹配

✅匹配

拒绝创建

🔵 kpyun

pattern: kpyun-filebeat*

priority: 150

🟡 kpyun-fb-tags

pattern: kpyun-filebeat-tags*

priority: 150

📄 待创建的索引

kpyun-filebeat-tags-logs-2026.07.31

⚖️ 冲突检测

两个模板同时命中

优先级相同(150)

❌ 400 Bad Request

multiple index templates may not match

Caution

==索引模板冲突==:现有模板 kpyun(来自之前实验)的 pattern kpyun-filebeat* 会==包含==新模板 kpyun-fb-tags 的 pattern kpyun-filebeat-tags* 同一个索引同时命中两个同优先级(150)的模板 → ES 拒绝创建

📌 实验环境:直接删 kpyun 模板最快 📌 ==生产环境==:新建模板时避开重叠 pattern,或调高 priority(如 200),不影响已有索引

Terminal window
# 解决方案:删除冲突的旧模板
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.log
root@Elk02 ~# echo 'log message: first test' > /tmp/tag-test.log
root@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.count
green open kpyun-filebeat-tags-logs-2026.07.31 3 0 2
green open kpyun-filebeat-tags-tcp-2026.07.31 3 0 1
# logs 索引 2 条(文件的两行),tcp 索引 1 条(nc 的一条消息)✅️


Filebeat多行匹配#

Tip

filestream 完全可以替代 log 类型,功能上 log 能做的 filestream 都能做

而且从 7.16.x 开始官方已不再推荐 log 写法,建议统一用 filestream

初体验#

Terminal window
1)编写配置文件
root@Elk01 ~# killall -9 filebeat
# 新的日志文件,不需要 rm -rf /var/lib/filebeat/
root@Elk01 ~# vim /tmp/filestream_tomcat-to-es.yaml
filebeat.inputs:
- type: filestream
paths:
- /soft/tomcat/logs/catalina.out
# tomcat的系统日志
Important
  • tomcat/logs/catalina.out 是 Tomcat 的标准输出/标准错误重定向日志
    • JVM 启动过程、部署状态、异常堆栈全部写在这里
  • 💡 判断 Tomcat 是否启动成功、报了什么错,第一时间看这个文件就行
Terminal window
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: false
setup.template.name: "filestream" # 模板名字更换
setup.template.pattern: "kpyun-filestream*" # 匹配的索引名称更换
setup.template.overwrite: false
setup.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 &
Tip
  • 过滤 filebeat 进程时,用 grep [f]ilebeat 代替 grep filebeat,可以免去 -v grep 的麻烦

  • [f] 作为正则只匹配单字符 f,grep 自身的命令行是字面量 [f]ilebeat

    • 正则匹配不了它,所以 grep 不会命中自身
    • 比 -v grep 更简练——从源头避免,而非事后排除
Terminal window
3)进程验证
# 过滤出两条(默认自带grep)
root@Elk01 ~# ps -ef | grep filebeat
root 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 grep
root 1803 1552 0 10:36 pts/0 /usr/share/filebeat/bin/filebeat...
root@Elk01 ~# ps -ef | grep [f]ilebeat
root 1803 1552 3 10:36 pts/0 /usr/share/filebeat/bin/filebeat...
4)kibana查看数据

kpyun-filestream*
kpyun-filestream*

多行匹配案#

Terminal window
1)停止tomcat服务
root@Elk01 ~# wc -l /soft/tomcat/logs/catalina.out
92 /soft/tomcat/logs/catalina.out
root@Elk01 ~# shutdown.sh
root@Elk01 ~# wc -l /soft/tomcat/logs/catalina.out
98 /soft/tomcat/logs/catalina.out
root@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.out
28-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)启动tomcat
root@Elk01 ~# startup.sh
.....
Tomcat started.
# 表面起来了,实则没起来
root@Elk01 ~# ss -lntup | grep 8080 | wc -l
0
4)查看日志
root@Elk01 ~# tail -13 /soft/tomcat/logs/catalina.out
28-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"
}
Tip

为什么需要多行匹配?

  • Filebeat 默认按 \n 逐行采集,一条堆栈异常被拆成 N 条文档

核心参数: negate + match 决定哪些行往哪个方向合并

negatematch效果
falseafter匹配的行追加到上一行
falsebefore匹配的行提前到下一行
trueafter不匹配的行追加到上一行(堆栈专用🦄)
truebefore不匹配的行提前到下一行

💡 堆栈场景:negate: true + match: after + pattern: '^\d'

  • 以数字开头的是新日志,其余续行全部合并到上一条
  • 官方文档参考:Multiline examples
Terminal window
1)环境清理
root@Elk01 ~# killall -9 filebeat
root@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.yaml
filebeat.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: false
setup.template.name: "filestream"
setup.template.pattern: "kpyun-filestream*"
setup.template.overwrite: false
setup.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"
}
# 堆栈异常已合并为单条文档,不再是逐行拆散的碎片

新的索引
新的索引

Terminal window
# KQL语句
message :at

多行匹配
多行匹配

ELFK架构✨#

ELFK架构图解
ELFK架构图解

部署logstash环境#

Terminal window
1)安装logstash
# Elk03节点
root@Elk03 ~# wget -P /tmp/ https://artifacts.elastic.co/downloads/logstash/logstash-7.17.29-amd64.deb
root@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/jdk
export JAVA_HOME=/usr/share/logstash/jdk
export TOMCAT_HOME=/soft/tomcat
export PATH=$PATH:$JAVA_HOME/bin:$TOMCAT_HOME/bin
root@Elk03 ~# unset JAVA_HOME
# 清除掉之前的java环境变量
root@Elk03 ~# source /etc/profile.d/tomcat.sh
root@Elk03 ~# exit
# 退出当前会话重新SSH连接 ✅(这个生效)
jiuzhao@Ubuntu ~$ ssh Elk03
root@Elk03 ~# java --version
openjdk 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.conf
input {
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.conf
The stdin plugin is now waiting for input:
......
Hello # json格式输出
{"message":"Hello","host":"Elk03","@version":"1","@timestamp":"2026-07-28T12:37:59.541Z"}

采集文本案例#

Terminal window
1)准备配置文件
root@Elk03 ~# vim /etc/logstash/conf.d/02-file-to-es.conf
input {
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)

Terminal window
# 默认数据目录的内容
root@Elk03 ~# ls -la /usr/share/logstash/data/
total 20
drwxr-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 .lock
drwxr-xr-x 2 root root 4096 Jul 30 16:29 queue
-rw-r--r-- 1 root root 36 Jul 30 16:29 uuid
drwxr-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
Note

💡 plugins/inputs/file/ 目录是 file input ==专属的==,beats input 不产生,无需关心

也就是只有通过文件读取的日志才有这个目录,如果 logstash 是监听的端口,则不会产生数据

Tip

.sincedb 记录了每个文件读取到的位置(offset),logstash 重启后从上次位置继续,不会重复采集

如果改完配置想全量重采,删掉这个文件即可

自研apps日志案例#

非结构化日志#

1)模拟APP生成测试日志
# Elk01节点执行即可
root@Elk01 ~# vim generate_log.py
#!/usr/bin/env python3
import datetime
import random
import logging
import time
import 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
# 非结构化数据 --> logstash
INFO 2026-07-29 15:26:20 [com.kpyun.generate_log] - DAU|2738|查看订单|1|29612.77
INFO 2026-07-29 15:26:25 [com.kpyun.generate_log] - DAU|3043|领取优惠券|1|28290.65
INFO 2026-07-29 15:26:29 [com.kpyun.generate_log] - DAU|4096|清空购物车|2|25508.63
INFO 2026-07-29 15:26:30 [com.kpyun.generate_log] - DAU|6544|查看订单|0|25883.05
INFO 2026-07-29 15:26:32 [com.kpyun.generate_log] - DAU|3719|搜索|1|16282.12
INFO 2026-07-29 15:26:36 [com.kpyun.generate_log] - DAU|462|浏览页面|1|20617.06
INFO 2026-07-29 15:26:38 [com.kpyun.generate_log] - DAU|5175|提交订单|0|19346.11
INFO 2026-07-29 15:26:42 [com.kpyun.generate_log] - DAU|8737|提交订单|2|16317.69
INFO 2026-07-29 15:26:43 [com.kpyun.generate_log] - DAU|7504|付款|0|28776.08
INFO 2026-07-29 15:26:44 [com.kpyun.generate_log] - DAU|8776|浏览页面|1|16240.77

Filebeat写入数据到logstash#

beats protocol

:6666

App日志

/tmp/apps.log

Filebeat

Elk01

Logstash

Elk03

ES 集群

10.0.0.6/7/8:9200

Kibana

出图展示

beats protocol

:6666

App日志

/tmp/apps.log

Filebeat

Elk01

Logstash

Elk03

ES 集群

10.0.0.6/7/8:9200

Kibana

出图展示

Terminal window
1)编写logstash的配置文件
'logstash安装在Elk03'
root@Elk03 ~# vim /etc/logstash/conf.d/03-beats-to-es.conf
input {
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.yaml
filebeat.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过滤插件
Important

为什么用 Filebeat → Logstash → ES 三级架构?

  • ==Filebeat== 只负责采集——轻量、不占资源,放在每个需要采集的机器上
  • ==Logstash== 负责加工——字段拆分、类型转换、日期格式化、数据清洗
  • ==ES== 负责入库 + 检索——数据落盘、全文搜索、聚合计算,Kibana 每张图背后都是它在算

💡 一条日志的旅程:采集 → 加工 → 存储,各干各的,谁也别越俎代庖


使用mutate处理数据案例#

mutate 处理链

① split by |

add_field

② split by ' '

提取date · 清理字段

③ convert

userid→integer

price→float

✅ 干净的结构化文档

mutate 处理链

① split by |

add_field

② split by ' '

提取date · 清理字段

③ convert

userid→integer

price→float

✅ 干净的结构化文档

Terminal window
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 的解析器字节 --> 偏移算歪
Terminal window
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" }
}
Terminal window
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字段才是)
Tip

mutate 是 logstash 的瑞士军刀——split、add_field、remove_field、convert 4 个操作一组,处理非结构化日志的标准套路:

切分 → 映射 → 清理 → 转换

遇到新的日志格式,套这个模板就行

官方文档
官方文档

split分割
split分割


使用date插件处理日期#

Terminal window
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
}
}
............
Terminal window
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}"
}
}
Terminal window
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 logstash
logstash: 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.log
root@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

创建索引模式
创建索引模式

Important

date 插件的作用就一句话:

把 @timestamp(filebeat 的”我什么时候采集到的”)替换成日志里写的真实时间(“这件事什么时候发生的”)

💡 没替换的后果:Kibana 时间轴上显示的会偏后几秒到几分钟——不影响查数据,但 ==出图不准确==

效果图
效果图


分析自定义程序的业务指标#

Note

🧱 前置条件:ES 索引 kpyun-logstash-apps-* 中已有结构化数据

字段清单:userid(integer) | svip(keyword) | price(float) | action(keyword) | @timestamp(date)

以下操作均在 Kibana Web UI 中完成——用鼠标点点就行

访问量PV统计#

Terminal window
'统计每天的访问总量'
kibana: 创建可视化 → 基于聚合 → 指标 → 选择索引 → 聚合(默认计数)→ 定制标签(PV)

PV统计
PV统计

分析用户的数量#

Terminal window
'去重后 userid 的简单计数'
kibana: 唯一计数 → 字段(userid)→ 定制标签(用户数分析)# 另存为新的视图

用户数
用户数

分析平台的交易额#

Terminal window
'GMV——总成交额'
kibana: 求和 → 字段(price)→ 定制标签(平台交易额)# 另存为新的视图
# 注意 price 必须在 logstash mutate → convert 中转为 float/integer
# 否则这里看到的 price 会显示为字符串,没法做数学聚合 ❌
✅️ 数据类型转换的重要性在此体现!

平台交易额
平台交易额

分析SVIP用户占比#

Terminal window
'看看会员等级分布'
kibana: 创建可视化 → Lens → 索引 → 字段(svip) → 圆环图
# svip=0 普通用户; svip=1 银卡; svip=2 金卡
# 能看出来什么群体在平台上最活跃

SVIP占比
SVIP占比

用户行为分析#

Terminal window
'用户都在干什么?'
kibana: 创建可视化 → Lens → 索引 → 字段(action) → 圆环图
# 浏览页面、搜索、加入购物车、提交订单、付款...
# CEO要的就是这个图——知道哪一步流失最多

用户行为
用户行为

Tip

按 SVIP 等级拆分的用户行为分布(svip + action交叉分析)

多字段饼图
多字段饼图

制作Dashboard#

Terminal window
'把所有图汇总到一张仪表盘——给老板看'
kibana: Dashboard → 新建仪表盘 → 从库中添加 → 拖拽排列 → 保存
# 一张图展示 PV/用户数/交易额/SVIP占比/用户行为 → 业务全貌一目了然

Dashboard
Dashboard


ElasticStack故障排查技巧指南#

Important

🧱 面试高频——ELK 出问题了你怎么排查?

核心口诀:跟着数据走,从源头往下查——源 → 采集 → 加工 → 索引 → 展示,一层一层确认,哪环断了问题就在哪

EFK架构排查技巧#

EFK架构故障排查技巧
EFK架构故障排查技巧

Terminal window
排查口诀(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`
Tip

💡 EFK 排查一句话: 三条腿折了哪一条?(源 / 采集 / 存储)

别看日志先看进程——filebeat 看 ps,ES / Kibana 看 systemctl status,确认三个东西都活着,已经排掉一半的坑


ELFK架构排查技巧#

ELFK架构故障排查技巧
ELFK架构故障排查技巧

Terminal window
排查口诀(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 排查清单
Note

📌 ELFK 对比 EFK 的核心差异:

多了一个 ==Logstash==,排查就多了一环——“filebeat 已经把数据送出去了,logstash 到底收没收到?”

验证方法:logstash 配置里开 stdout { codec => rubydebug } 是最快的定位手段 ✅


课后作业#

Warning

🦄 编写程序模拟 nginx 访问日志 10w 条记录,并使用 EFK 架构分析全球用户分布图、设备类型等信息

# ===== /root/generate_nginx_log.py =====
#!/usr/bin/env python3
import 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 条日志")
Terminal window
# 只有Elk02有Nginx
'操作流程'
1)生成日志
root@Elk02 ~# vim /root/generate_nginx_log.py
root@Elk02 ~# python3 /root/generate_nginx_log.py
# 10w条大约需要2-3分钟
2)Filebeat配置文件
root@Elk02 ~# filebeat modules list | head -3
Enabled:
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.yaml
filebeat.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: false
setup.template.name: "nginx-10w"
setup.template.pattern: "kpyun-nginx-10w*"
setup.template.overwrite: false
setup.template.settings:
index.number_of_shards: 3
index.number_of_replicas: 0
3)启动Filebeat
root@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... 同上面的操作思路

Warning

裸文本采集(type: log)不会做 GeoIP 解析——source.geo.location nginx 模块才会 ✅ (IP 不能只嵌在 message 字符串里)

用户分布图
用户分布图

设备类型分析
设备类型分析

操作系统占比
操作系统占比

文章分享

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

Profile Image of the Author
久棹
不是先学好了再干,而是先干起来再学习,干中学!
分类
站点统计
文章
115
分类
15
标签
272
总字数
306,562
运行时长
0 天
最后活动
0 天前
文章目录