ElasticStack开篇

ElasticStack开篇
[TOC]
概述
Elastic Stack是Elastic公司推出的一套日志数据分析平台
它的前身是大家熟知的ELK,后来因为组件丰富,官方将其统一更名为Elastic Stack
经典三组件及其作用
- Elasticsearch (ES):核心存储与检索引擎
- 一个分布式、高可用的数据库,负责海量数据的存储、快速检索和分析聚合
- Logstash:数据处理管道
- 功能强大的服务器端工具,负责从各种数据源采集数据,并进行过滤、解析、格式转换等复杂处理,最后将“清洗干净”的数据发送给ES
- Kibana:可视化操作界面
- 连接ES集群,提供图形化Web界面,让用户能通过图表、仪表盘直观地查询、分析和展示数据,极大降低了查询门槛
架构演进与轻量化替代
- 问题:Logstash功能强大但资源消耗较高(较“重量级”),尤其在日志采集端
- 解决方案:业界开始采用更轻量的采集器,如Fluentd或Filebeat
- Filebeat:官方推出的轻量级日志采集器,资源占用小,专门用于读取和转发日志文件,性能优异。
- 架构组合:因此,现在常见架构有ELK(保留Logstash)、EFK(使用Fluentd)、ELFK(Filebeat + Logstash配合)等
- Filebeat常作为Agent部署在服务器上,将日志轻量级地转发给Logstash或直接存入ES
由于组件增多(还有Beats家族等),官方将整个技术栈统一命名为Elastic Stack,但大家习惯上仍常称其为ELK
架构图

使用 Filebeat 替换了 Fluentd,架构名称就不能再叫 EFK 了 EFK 这个缩写中的 “F” 已经被行业约定俗成地指代 Fluentd,它是一个专有名词
日志系统基于 Elastic Stack,使用 Filebeat 作为节点采集器,将日志传输至 Elasticsearch 集群,并通过 Kibana 进行可视化分析
- 最重要的是Elasticsearch 集群(数据库)
- Filebeat即使没了日志也不会丢,Kibana只是用来展示数据的,需要从ES集群(数据库)拿

- 官方链接:Elasticsearch下载
单点部署
# 下载ES软件包wget -P /home/jiuzhao/下载 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.29-amd64.deb
# 拷贝至Elk主机jiuzhao@Ubuntu 下载$ scp elasticsearch-7.17.29-amd64.deb Elk01:/tmpelasticsearch-7.17.29-amd64.deb 100% 310MB 341.0MB/s 00:00
# 安装ES软件包root@Elk01 ~# ls /tmp/elasticsearch-7.17.29-amd64.deb/tmp/elasticsearch-7.17.29-amd64.debroot@Elk01 ~# dpkg -i /tmp/elasticsearch-7.17.29-amd64.deb
# 修改ES的配置文件root@Elk01 ~# vim /etc/elasticsearch/elasticsearch.yml...# 集群的名字(可省略)cluster.name: My-Elk# 对外暴露ES的IP地址network.host: 0.0.0.0# 表示当前ES是一个单点,而非集群discovery.type: single-node
# 启动ES服务root@Elk01 ~# systemctl start elasticsearch.serviceroot@Elk01 ~# ss -ntl | grep "9[23]00"LISTEN 0 4096 *:9300 *:*LISTEN 0 4096 *:9200 *:*| 端口 | 协议 | 用途 | 说明 |
|---|---|---|---|
| 9200 | HTTP / HTTPS | 对外服务端口 | 客户端(Kibana、Logstash、应用代码等)通过此端口进行索引、搜索、管理等 REST API 操作 |
| 9300 | TCP(Binary Protocol) | 集群内部通信端口 | ES 节点之间数据同步、分片迁移、节点发现与心跳,不对外开放 |
- 简单记:9200 面向用户,9300 面向节点
- ⚠️ 优先启动9300,再启动9200
# 测试验证root@Elk01 ~# curl localhost:9200{ "name" : "Elk01", # 节点名称,集群内唯一标识 "cluster_name" : "My-Elk", # 改的集群名称 "cluster_uuid" : "XnJ4HL9cQXaWFfzFH3eOCw", # 集群唯一 ID "version" : { "number" : "7.17.29", # 版本号 "build_flavor" : "default", "build_type" : "deb", # 安装方式(deb 包),决定后续升级/卸载方式 "build_hash" : "580aff1a0064ce4c93293aaab6fcc55e22c10d1c", "build_date" : "2025-06-19T01:37:57.847711500Z", "build_snapshot" : false, "lucene_version" : "8.11.3", "minimum_wire_compatibility_version" : "6.8.0", # 最低集群通信兼容版本,决定能和哪些旧版本组集群 "minimum_index_compatibility_version" : "6.0.0-beta1" # 最低索引兼容版本,旧索引能否被读取 }, "tagline" : "You Know, for Search"}# ES 是 Java 开发的(基于 Lucene)root@Elk01 ~# ls /usr/share/elasticsearch/bin jdk lib modules NOTICE.txt plugins README.asciidoc
root@Elk01 ~# curl 10.0.0.6:9200/_cat/nodes10.0.0.6 21 96 1 0.01 0.00 0.00 cdfhilmrstw * Elk01root@Elk01 ~# curl 10.0.0.6:9200/_cat/nodes?vip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name10.0.0.6 22 96 0 0.00 0.00 0.00 cdfhilmrstw * Elk01_cat 是 ES 提供的一套人类可读的紧凑输出 API,调试和运维时非常常用,?v 参数显示列标题
_cat/nodes 字段说明
| 字段 | 示例值 | 含义 |
|---|---|---|
ip | 10.0.0.6 | 节点 IP 地址 |
heap.percent | 22 | JVM 堆内存使用率,过高需扩容或查内存泄漏 |
ram.percent | 96 | 系统物理内存使用率 |
cpu | 0 | ES 进程 CPU 占用百分比 |
load_1m / load_5m / load_15m | 0.00 | 系统 1/5/15 分钟平均负载 |
node.role | cdfhilmrstw | 节点角色(字母含义见下表) |
master | * | * 表示当前节点是主节点,- 表示不是 |
name | Elk01 | 节点名称 |
node.role 角色字母含义
| 字母 | 角色 | 说明 |
|---|---|---|
m | master-eligible | 候选主节点,可参与主节点选举 |
d | data | 数据节点,存储索引数据 |
i | ingest | 摄取节点,数据预处理/管道 |
h | hot | 热节点,存频繁写入/查询的索引 |
w | warm | 温节点,中等访问频率的索引 |
c | cold | 冷节点,存不常访问的索引 |
f | frozen | 冻节点,极少查询的归档数据 |
l | machine learning | 机器学习节点 |
r | remote_cluster_client | 跨集群搜索客户端 |
s | content | 内容节点(7.x 默认),数据 + 聚合分析 |
t | transform | 数据转换节点 |
单节点部署时所有角色集中在一台机器上,生产环境一般将 master 和数据节点分离
常用 _cat API 速查(按使用频率排序)
| 命令 | 用途 |
|---|---|
_cat/health?v | 集群健康状态(green/yellow/red) |
_cat/indices?v | 索引列表(文档数、大小、健康状态) |
_cat/nodes?v | 节点列表与资源占用 |
_cat/allocation?v | 磁盘空间分配情况 |
_cat/shards?v | 分片分布与状态 |
_cat/recovery?v | 分片恢复进度(迁移/恢复时用) |
_cat/thread_pool?v | 线程池状态(排查性能瓶颈) |
_cat/master?v | 查看当前主节点是谁 |
_cat/plugins?v | 查看各节点已安装的插件 |
_cat/pending_tasks?v | 集群中待处理的任务队列 |
root@Elk01 ~# curl 10.0.0.6:9200/_cat/master?vid host ip nodeUh71vV43Qg2bbkjI63mC2w 10.0.0.6 10.0.0.6 Elk01ES 集群部署
环境准备
| 主机 | IP | 角色 | 内存 |
|---|---|---|---|
| Elk01 | 10.0.0.6 | ES + Kibana | 4G |
| Elk02 | 10.0.0.7 | ES + Filebeat + Nginx | 4G |
| Elk03 | 10.0.0.8 | ES | 4G |
-
先关机
virsh shutdown Elk01/02/03 -
扩容内存
virsh setmaxmem Elk01 --size 4G --config,启动后virsh setmem Elk01 --size 4G
关键坑点
- 从单点切集群前,必须
rm -rf /var/{log,lib}/elasticsearch/*清空旧数据,否则加入集群会失败 - 三台节点要==同时启动==,否则可能脑裂(brain split)
- 每台 4G 内存,JVM 堆设
512m即可,避免 OOM
配置文件
# JVM 堆内存(每台都要设)-Xms512m-Xmx512m
# elasticsearch.ymlroot@Elk01 ~# egrep -v "^#|^$" /etc/elasticsearch/elasticsearch.ymlcluster.name: My-Elk # 集群名称,所有节点必须一致path.data: /var/lib/elasticsearch # 数据存储路径path.logs: /var/log/elasticsearch # 日志存储路径network.host: 0.0.0.0 # 对外监听地址discovery.seed_hosts: ["10.0.0.6", "10.0.0.7", "10.0.0.8"] # 集群节点列表cluster.initial_master_nodes: ["10.0.0.6", "10.0.0.7", "10.0.0.8"] # 初始主节点候选| 参数 | 作用 |
|---|---|
cluster.name | 集群名字,==所有节点必须一致==,否则无法加入集群 |
discovery.seed_hosts | 节点发现列表,新节点通过此列表找到集群 |
cluster.initial_master_nodes | 首次启动时的主节点候选列表 |
部署步骤
1)停止单点 ESroot@Elk01 ~# systemctl stop elasticsearch.serviceroot@Elk01 ~# ss -lntup | grep "9[23]00" | wc -l0# 端口已释放
2)安装 ES(Elk02、Elk03)root@Elk02 ~# dpkg -i /tmp/elasticsearch-7.17.29-amd64.debroot@Elk03 ~# dpkg -i /tmp/elasticsearch-7.17.29-amd64.deb
3)修改配置 + 设置 JVM 堆root@Elk01 ~# mkdir -p /etc/elasticsearch/jvm.options.d⚠️ '每台VM都要创建这个目录'root@Elk01 ~# echo -e "-Xms512m\n-Xmx512m" > /etc/elasticsearch/jvm.options.d/heap.options# 修改 elasticsearch.yml(内容见上方)
4)同步配置到其他节点# 将 Elk01 的配置 scp 到 Elk02/03 的 /etc/elasticsearch/(内容见上方)scp /etc/elasticsearch/elasticsearch.yml 10.0.0.7:/etc/elasticsearch/scp /etc/elasticsearch/jvm.options.d/heap.options 10.0.0.7:/etc/elasticsearch/jvm.options.d/scp /etc/elasticsearch/elasticsearch.yml 10.0.0.8:/etc/elasticsearch/scp /etc/elasticsearch/jvm.options.d/heap.options 10.0.0.8:/etc/elasticsearch/jvm.options.d/
5)清空旧数据(三台都要做)root@Elk01 ~# rm -rf /var/{log,lib}/elasticsearch/*root@Elk02 ~# rm -rf /var/{log,lib}/elasticsearch/*root@Elk03 ~# rm -rf /var/{log,lib}/elasticsearch/*# 不清数据,单点模式的元数据会导致加入集群失败
6)三台同时启动root@Elk01 ~# systemctl restart elasticsearch.serviceroot@Elk02 ~# systemctl enable --now elasticsearch.serviceroot@Elk03 ~# systemctl enable --now elasticsearch.service✅️ 三台同时 start,避免脑裂
7)验证集群root@Elk01 ~# curl 10.0.0.6:9200/_cat/nodes?vip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name10.0.0.6 65 80 18 0.59 0.28 0.12 cdfhilmrstw - Elk0110.0.0.7 31 92 23 0.35 0.20 0.09 cdfhilmrstw * Elk0210.0.0.8 62 93 22 0.58 0.32 0.14 cdfhilmrstw - Elk03✅️ 3 节点在线,Elk02 被选为主节点
root@Elk01 ~# curl 10.0.0.6:9200/_cat/health?vepoch timestamp cluster status node.total node.data shards pri relo init unassign active_shards_percent1785055088 08:38:08 My-Elk green 3 3 6 3 0 0 0 100.0%# green = 所有主分片和副本分片均正常💡 关机拍个快照
jiuzhao@Ubuntu ~$ for i in Elk0{1..3} ;do virsh snapshot-create-as $i Elk-environment-ready ;done已生成域快照 Elk-environment-ready已生成域快照 Elk-environment-ready已生成域快照 Elk-environment-readyjiuzhao@Ubuntu ~$ virsh snapshot-list Elk01 名称 生成时间 状态-------------------------------------------------------------- Elk-environment-ready 2026-07-26 17:33:38 +0800 shutoff init 2026-07-26 14:03:37 +0800 shutoff集群术语 & DSL 初体验

📌 一句话:数据先到达 ==索引==,索引被切分成多个 ==主分片== 分散存储,每个主分片有 ==副本== 提供备份和读负载均衡
分片分配
3 节点集群,3 个主分片 + 3 个副本分片(7.x 默认)
| 关键规则 | 说明 |
|---|---|
| ==主副分离== | 主分片和它的副本不能在同一节点上,否则节点宕机主副全丢 |
| ==交叉分配== | P0 在 Elk01 → R0 在 Elk02,P1 在 Elk02 → R1 在 Elk03 |
| ==分布式存储== | 数据分散在不同节点的不同分片上,每个分片里的数据==各不相同== |
| ==磁盘 I/O== | 多分片可充分利用多节点磁盘性能并行读写 |
故障转移
📌 故障转移流程
- 某个节点宕机 → 该节点上的主分片不可用
- Master 节点检测到异常 → 找到该主分片的副本(在其他节点上)
- 将副本提升为新主分片(==ro → rw==)
- 接管后,原来的副本现在是主分片了,所以可读可写!
⚠️ 如果某个主分片没有副本(replica = 0),则节点宕机后该分片数据直接丢失,集群变 red 🟥
读写分离
| 对比 | 主分片 (Primary) | 副本分片 (Replica) |
|---|---|---|
| 写入 | ✅ 是 | ❌ 否 |
| 读取 | ✅ 是 | ✅ 是 |
| 接管前 | rw(读写) | ro(只读) |
| 接管后 | — | ==rw==(提升为新主) |
💡 这和 MySQL 主从复制 + 读写分离 非常类似
- 主分片 = MySQL Master(读写)
- 副本分片 = MySQL Slave(只读 + 负载均衡 + 故障接管)
replica = 0相当于没有从库,读写压力全在主库上,还无备份
文档结构
一个文档 = 元数据 + 源数据
| 部分 | 作用 | 举例 |
|---|---|---|
| ==元数据== | 描述这个文档属于哪个索引、ID 是什么 | _index _id _version |
| ==源数据== | 用户实际存储的业务数据 | {"name":"孙悟空","hobby":["蟠桃"]} |
📌 写入文档时,ES 根据元数据中的 _index 确定文档属于哪个索引 → 索引被切分成多个分片 → 文档被路由存储到某个主分片中 → 主分片通过 pipeline 同步到副本分片
核心术语速查
| 术语 | 含义 | 关键点 |
|---|---|---|
| ==索引 (Index)== | 数据的==逻辑==读写单元 | 分片是其==物理==存储单元 |
| ==分片 (Shard)== | 索引被水平切分,每个分片存不同数据 | 最少 1 个,7.x 默认 1 分片,创建后不可改 |
| ==副本 (Replica)== | 分片的拷贝,数据与主分片==一模一样== | 7.x 默认 1 副本,可设为 0(无备份),可动态调整 |
| ==文档 (Document)== | ES 最小数据单元,JSON 格式 | 元数据描述它,源数据是实际内容 |
📌 索引为什么叫”索引”,却像”表”?
ES 底层是 Lucene,核心是倒排索引引擎 ES 把”建表”和”建索引”合二为一 —— 创建 Index 既是创建存储容器,也自动拥有了搜索能力
| MySQL | ES | 说明 |
|---|---|---|
| 数据库 (Database) | 集群 (Cluster) | 顶层容器 |
| 表 (Table) | 索引 (Index) | 数据搜索 + 存储单元 |
| 行 (Row) | 文档 (Document) | 一条数据 |
| 列 (Column) | 字段 (Field) | 数据属性 |
📌 水平切分 vs 垂直切分
| 切分方式 | 维度 | 说明 |
|---|---|---|
| ==水平切分== | 按行切 | 不同文档分到不同分片 → 每个分片字段齐全,数据不同 |
| 垂直切分 | 按列切 | 不同字段分到不同存储 → 一条文档被拆成多份 |
ES 只用水平切分——你写入 3 条文档(孙悟空、猪八戒、沙和尚),每个分片存不同的文档,但每个分片里该有的字段一个不少
集群颜色
| 颜色 | 含义 |
|---|---|
green 🟩 | 所有主分片 + 副本分片均正常访问 |
yellow 🟨 | 主分片正常,部分副本分片无法访问(不影响读写) |
red 🟥 | 部分主分片不可用,==数据不完整== |
1)无表头root@Elk01 ~# curl localhost:9200/_cat/health1785068357 12:19:17 My-Elk green 3 3 6 3 0 0 0 0 - 100.0%
2)带表头(?v)root@Elk01 ~# curl localhost:9200/_cat/health?vepoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1785068357 12:19:17 My-Elk green 3 3 6 3 0 0 0 0 - 100.0%| 字段 | 值 | 含义 |
|---|---|---|
epoch | 1785068357 | Unix 时间戳(秒) |
timestamp | 12:19:17 | 时间(HH:MM |
cluster | My-Elk | 集群名称 |
status | green | 健康状态:green / yellow / red |
node.total | 3 | 集群节点总数 |
node.data | 3 | 数据节点数 |
shards | 6 | 全部分片数(主 + 副) |
pri | 3 | 主分片数 |
relo | 0 | 正在搬迁的分片数 |
init | 0 | 正在初始化的分片数 |
unassign | 0 | ==未分配的分片数==,不为 0 说明有问题 |
pending_tasks | 0 | 待处理的任务数,持续积压说明集群繁忙 |
max_task_wait_time | - | 任务最长等待时间,- 表示无排队 |
active_shards_percent | 100.0% | 活跃分片百分比,==100% 才正常== |
DSL 语句实操
1)批量写入数据(_bulk)root@Elk01 ~# curl -s --location --request POST 'http://10.0.0.6:9200/_bulk' \--header 'Content-Type: application/json' \--data-raw '{ "create" : { "_index" : "kpyun", "_id" : "1001" } }{ "name" : "孙悟空","hobby": ["蟠桃","仙丹","紫霞仙子"] }{ "create" : { "_index" : "kpyun", "_id" : "1002" } }{ "name" : "猪八戒","hobby": ["吃","睡","高老庄"] }{ "create" : { "_index" : "kpyun", "_id" : "1003" } }{ "name" : "沙和尚","hobby": ["大师兄,师傅被妖怪抓走了","二师兄,师傅被妖怪抓走了"] }'⚠️ 末尾还有个'单引号'# "errors":false = 全部写入成功
2)查询全部数据root@Elk01 ~# apt -y install jqroot@Elk01 ~# curl -s http://10.0.0.6:9200/kpyun/_search | jq"hits": { "total": { "value": 3, "relation": "eq" } ...............# hits.total.value: 3 = 共 3 条文档用 10.0.0.7 或 10.0.0.8 也一样——集群内任意节点都能接收请求
3)查询指定文档root@Elk01 ~# curl -s http://10.0.0.6:9200/kpyun/_doc/1003 | jq{ "_index": "kpyun", "_type": "_doc", "_id": "1003", "_version": 1, "_seq_no": 2, "_primary_term": 1, "found": true, "_source": { "name": "沙和尚", "hobby": [ "大师兄,师傅被妖怪抓走了", ...................# found: true , _source 中显示沙和尚的数据
4)模糊查询(match)root@Elk01 ~# curl -s --location --request GET '10.0.0.6:9200/kpyun/_search' \--header 'Content-Type: application/json' \--data-raw '{"query":{"match":{"hobby":"蟠桃"}}}' | jq# 搜"蟠桃" → 命中孙悟空
5)修改文档(POST 覆盖)root@Elk01 ~# curl -s --location --request POST 'http://10.0.0.6:9200/kpyun/_doc/1003' \--header 'Content-Type: application/json' \--data-raw '{"docs":{"hobby":["念经","拜佛","女儿国"],"name":"唐僧"}}' | jq# _version: 2 = 版本号递增,result: "updated"root@Elk01 ~# curl -s http://10.0.0.6:9200/kpyun/_doc/1003 | jq# 再次查询{ "_index": "kpyun", "_type": "_doc", "_id": "1003", "_version": 2, "_seq_no": 3, "_primary_term": 1, "found": true, "_source": { "docs": { "hobby": [ "念经", "拜佛", "女儿国" ], "name": "唐僧" } }}
6)删除文档root@Elk01 ~# curl -s -X DELETE http://10.0.0.6:9200/kpyun/_doc/1003 | jq# result: "deleted" , _version: 3
7)删除索引(索引下所有文档一并删除)root@Elk01 ~# curl -s -X DELETE http://10.0.0.6:9200/kpyun/ | jq# acknowledged: trueKibana 组件部署

- 官方链接:kibana下载
部署步骤
1)安装 Kibanaroot@Elk01 ~# wget -P /tmp/ https://artifacts.elastic.co/downloads/kibana/kibana-7.17.29-amd64.debroot@Elk01 ~# dpkg -i /tmp/kibana-7.17.29-amd64.deb
2)修改配置root@Elk01 ~# egrep -v "^#|^$" /etc/kibana/kibana.ymlserver.host: "0.0.0.0" # 监听地址elasticsearch.hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] # ES 集群地址i18n.locale: "zh-CN" # 中文界面| 配置项 | 值 | 说明 |
|---|---|---|
server.host | 0.0.0.0 | 监听所有网卡,允许外部访问 |
elasticsearch.hosts | ES 集群地址列表 | 可配多个,Kibana 自动负载均衡 |
i18n.locale | zh-CN | 界面中文,需先确认支持该语言包 |
Kibana 启动较慢(30s+),需要等 ES 集群就绪后才能完全初始化
3)启动服务root@Elk01 ~# systemctl enable --now kibana.serviceroot@Elk01 ~# ss -ntl | grep 5601LISTEN 0 511 0.0.0.0:5601 0.0.0.0:*# Kibana 监听 5601 端口
4)访问 WebUIhttp://10.0.0.6:5601/
root@Elk01 ~# curl -s --location --request POST 'http://10.0.0.6:9200/_bulk' \--header 'Content-Type: application/json' \--data-raw '{ "create" : { "_index" : "kpyun", "_id" : "1001" } }{ "name" : "孙悟空","hobby": ["蟠桃","仙丹","紫霞仙子"] }{ "create" : { "_index" : "kpyun", "_id" : "1002" } }{ "name" : "猪八戒","hobby": ["吃","睡","高老庄"] }{ "create" : { "_index" : "kpyun", "_id" : "1003" } }{ "name" : "沙和尚","hobby": ["大师兄,师傅被妖怪抓走了","二师兄,师傅被妖怪抓走了"] }'⚠️ 末尾还有个'单引号'



Filebeat 环境部署

- 官方链接:filebeat下载
stdin 标准输入采集
# 这里安装在Elk02wget -P /tmp/ https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.29-amd64.debdpkg -i /tmp/filebeat-7.17.29-amd64.deb
root@Elk02 ~# mkdir -p /etc/filebeat/configroot@Elk02 ~# vim /etc/filebeat/config/01-stdin-to-console.yamlfilebeat.inputs:- type: stdin # 从标准输入采集
output.console: # 输出到控制台 pretty: true
root@Elk02 ~# echo "Hello" | filebeat -e -c /etc/filebeat/config/01-stdin-to-console.yamlecho "Hello" # 模拟输入-e # 打印日志输出到当前终端-c # 指定配置文件✅️ 最简单的方式,适合测试"message": "Hello", # message 字段: "Hello" "input": { "type": "stdin" }文本日志采集
root@Elk02 ~# vim /etc/filebeat/config/02-log-to-console.yamlfilebeat.inputs:- type: log # 从文件采集 paths: - /tmp/xixi.log
output.console: pretty: true
root@Elk02 ~# touch /tmp/xixi.logroot@Elk02 ~# filebeat -e -c /etc/filebeat/config/02-log-to-console.yaml# 在另一个终端:root@Elk02 ~# echo "hello world" > /tmp/xixi.logroot@Elk02 ~# echo "line two" >> /tmp/xixi.log# filebeat 输出 JSON,message 字段即为日志行内容
root@Elk02 ~# echo "abc"abc # 正常输出(有换行)root@Elk02 ~# echo -n "efg"efg⚠️root@Elk02 ~## echo -n 就是不输出末尾的 \n(没有换行)| 现象 | 结论 |
|---|---|
| Filebeat 默认==按行采集==文本数据 | 每次 \n 触发一条事件 |
Registry 记录采集位置(/var/lib/filebeat/registry/) | 重启不会重复采集 |
rm -rf /var/lib/filebeat/ 后重新采集 | 会从头读取所有已有数据 |
| ‘生产环境中慎用 rm -rf , 会导致重复采集’ |
root@Elk02 ~# echo -n "kpyun" >> /tmp/xixi.log'没有换行符,不进行采集'root@Elk02 ~# echo "2026" >> /tmp/xixi.log'Filebeat 按 \n 分隔事件,所以这两行被当成一条日志采集了'# "message": "kpyun2026"
root@Elk02 ~# tree /var/lib/filebeat/registry//var/lib/filebeat/registry/└── filebeat ├── log.json # 日志位置 └── meta.jsonroot@Elk02 ~# tail /var/lib/filebeat/registry/filebeat/log.json# 里面有日志文件的inode号,关键字段offset'offset 就是 filebeat 读到文件的哪个字节位置了'# log.json 把每个文件的最后读取位置记下来# 重启时恢复 --> 读 log.json 里存的 offset,从那个位置继续采集,不重复
# 先暂停正在采集的终端instance/beat.go:462 filebeat stopped.root@Elk02 ~# echo "xixixi" >> /tmp/xixi.logroot@Elk02 ~# cat /tmp/xixi.loghello worldline twokpyun2026xixixiroot@Elk02 ~# filebeat -e -c /etc/filebeat/config/02-log-to-console.yaml# 继续采集 "message": "xixixi"
'再次暂停'root@Elk02 ~# rm -rf /var/lib/filebeat/ # 清除采集位置点# offset 归零了root@Elk02 ~# filebeat -e -c /etc/filebeat/config/02-log-to-console.yaml# 从头采集📌 什么时候该删 registry,什么时候不该?
offset 是跟着日志文件走的,不是跟着配置文件走的——换个 config 不换日志文件,offset 依然有效
# 我想做什么决定我删不删# ✅ 想让旧行也重新处理一遍 → rm -rf /var/lib/filebeat/# ❌ 旧行无所谓,新行接着往后读就行 → 不删# ❌ 第一次读一个新日志文件 → 不删,自动从 0 开始| 常见意图 | 笔记对应 | 变动 | 需要删? |
|---|---|---|---|
| 从头演示一遍采集效果 | log 重跑 | — | ✅ |
| 之前只是 console 预览,现在要全部入 ES 给 Kibana 看 | nginx→ES | ==换输出== | ✅ |
| 换解析方式(raw→module),旧数据用新规则重过 | modules→ES | ==换输入== | ✅ |
| 新日志文件第一次采集 | nginx→console | — | ❌ |
| 验证 offset 生效,只采新行 | log 暂停继续 | — | ❌ |
| 生产环境,只管新数据 | — | — | ❌ |
判断口诀:想让旧行重新被处理 → 删;只管新行 → 不删
Nginx 日志采集(输出到终端)
# Filebeat也在Elk02安装的root@Elk02 ~# apt -y install nginx
root@Elk02 ~# vim /etc/filebeat/config/03-nginx-to-console.yamlfilebeat.inputs:- type: log paths: - /var/log/nginx/access.log
output.console: pretty: true
# 制造访问日志root@Elk02 ~# curl 10.0.0.7root@Elk02 ~# curl 10.0.0.7/NotFound
# 新日志文件,filebeat 从未见过,不需要删 registryroot@Elk02 ~# filebeat -e -c /etc/filebeat/config/03-nginx-to-console.yaml# message 字段: "10.0.0.7 - - [26/Jul/2026:16:45:38 +0800] \"GET / HTTP/1.1\" 200 615 ..."✅️ 成功采集 nginx 访问日志EFK 架构采集 nginx
架构图
Filebeat → ES 配置
1)安装 nginx + 制造日志root@Elk02 ~# apt -y install nginx# 多 curl 几次制造访问日志root@Elk02 ~# for i in $(seq 1 15); do curl -s 10.0.0.7 > /dev/null; doneroot@Elk02 ~# wc -l /var/log/nginx/access.log33 /var/log/nginx/access.log
2)编写 Filebeat 写入 ES 的配置root@Elk02 ~# vim /etc/filebeat/config/04-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-filebeat-nginx-%{+yyyy.MM.dd}" # 按天分割索引
setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称setup.template.name: "kpyun" # 索引模板名(可自定义)setup.template.pattern: "kpyun-filebeat*" # 匹配索引名称的模式(根据索引模板初始化)setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 3 # 分片数 index.number_of_replicas: 0 # 副本数
3)启动 Filebeatroot@Elk02 ~# rm -rf /var/lib/filebeat/ # console 只是预览,现在要把已有日志全部发到 ES,从头重采root@Elk02 ~# filebeat -e -c /etc/filebeat/config/04-nginx-to-es.yaml# 日志显示 Connecting to backoff(elasticsearch(http://10.0.0.x:9200)) established✅️ 三台 ES 全部连接成功
4)ES 中验证数据root@Elk01 ~# curl -s 10.0.0.6:9200/_cat/indices?v | grep nginxgreen open kpyun-filebeat-nginx-2026.07.27 ... 3 0 33 0 48.1kb# 索引已创建,33 条文档,3 个主分片,0 个副本
root@Elk01 ~# curl -s 'http://10.0.0.6:9200/kpyun-filebeat-nginx-2026.07.27/_search?size=1' | jq '.hits.hits[0]._source.message'"10.0.0.7 - - [26/Jul/2026:16:45:38 +0800] \"GET / HTTP/1.1\" 200 615 \"-\" \"curl/8.5.0\""# 数据内容与 nginx access.log 一致⚠️ setup.ilm.enabled: false 必须显式设为 false
否则 ILM(索引生命周期管理)会接管索引命名,忽略你自定义的 index 名称
‘这是新手最容易踩的坑之一’



Filebeat 模块管理
1)查看模块列表root@Elk02 ~# filebeat modules listEnabled: # 已启用
Disabled: # 已禁用activemqapache...
2)启用 nginx 模块root@Elk02 ~# filebeat modules enable nginx mysqlEnabled nginxEnabled mysqlroot@Elk02 ~# filebeat modules list | head -4Enabled:mysqlnginx # nginx 出现在已启用列表
3)底层逻辑就是文件重命名💡root@Elk02 ~# ls -la /etc/filebeat/modules.d/nginx*-rw-r--r-- 1 root root 784 Jun 19 2025 /etc/filebeat/modules.d/nginx.yml# enable = *.yml.disabled 改名 *.yml# disable = *.yml 改名 *.yml.disabled
4)手动模拟root@Elk02 ~# mv /etc/filebeat/modules.d/tomcat.yml{.disabled,}# 效果等同于 filebeat modules enable tomcatroot@Elk02 ~# filebeat modules list | head -4Enabled:.....tomcat # tomcat 出现在已启用列表root@Elk02 ~# mv /etc/filebeat/modules.d/mysql.yml{,.disabled}# 效果等同于 filebeat modules disable mysql模块本质是预定义的 Filebeat 配置模板(解析规则 + 字段映射),帮你省去手写正则的麻烦
⚠️ 启用模块只是第一步,实际使用时还需要修改 modules.d/xxx.yml 中的具体路径等参数
基于模块采集nginx日志
1)准备nginx的访问日志root@Elk02 ~# vim /var/log/nginx/access.log123.113.31.55 - - [27/Jul/2026:14:30:00 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"123.113.31.55 - - [27/Jul/2026:14:30:01 +0800] "GET /favicon.ico HTTP/1.1" 404 197 "http://10.0.0.92/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"23.13.31.55 - - [27/Jul/2026:14:30:05 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"33.33.31.55 - - [27/Jul/2026:14:30:06 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"43.113.31.55 - - [27/Jul/2026:14:30:07 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"53.93.31.55 - - [27/Jul/2026:14:30:08 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"63.83.31.55 - - [27/Jul/2026:14:30:09 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"73.73.31.55 - - [27/Jul/2026:14:30:10 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"83.63.31.55 - - [27/Jul/2026:14:30:11 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"93.53.31.55 - - [27/Jul/2026:14:30:12 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"103.43.31.55 - - [27/Jul/2026:14:30:13 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"113.33.31.55 - - [27/Jul/2026:14:30:14 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"123.22.31.55 - - [27/Jul/2026:14:30:15 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.81.0"3.22.31.55 - - [27/Jul/2026:14:31:00 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"4.22.31.55 - - [27/Jul/2026:14:31:01 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"5.22.31.55 - - [27/Jul/2026:14:31:02 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"3.22.31.55 - - [27/Jul/2026:14:31:03 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"31.22.31.55 - - [27/Jul/2026:14:31:04 +0800] "GET /favicon.ico HTTP/1.1" 404 134 "http://10.0.0.92/" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"32.22.31.55 - - [27/Jul/2026:14:31:30 +0800] "GET /oldboyedu.html HTTP/1.1" 404 134 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"33.33.31.55 - - [27/Jul/2026:14:32:00 +0800] "GET /linux100.html HTTP/1.1" 404 134 "-" "Mozilla/5.0 (iPad; CPU OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"55.33.31.55 - - [27/Jul/2026:14:33:00 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36"5.33.31.55 - - [27/Jul/2026:14:33:01 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36"6.33.31.55 - - [27/Jul/2026:14:33:02 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36"7.33.31.55 - - [27/Jul/2026:14:33:03 +0800] "GET / HTTP/1.1" 200 396 "-" "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36"root@Elk02 ~# wc -l /var/log/nginx/access.log24 /var/log/nginx/access.log
2)编写Filebeat的模块文件root@Elk02 ~# mv /etc/filebeat/modules.d/tomcat.yml{,.disabled}root@Elk02 ~# filebeat modules list | head -3Enabled:nginx # 只需要开启nginx模块即可!root@Elk02 ~# ll /etc/filebeat/modules.d/*.yml-rw-r--r-- 1 root root 784 Jun 19 2025 /etc/filebeat/modules.d/nginx.yml# 查看已启用模块
3)修改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 不需要)
4)编写Filebeat的配置文件root@Elk02 ~# vim /etc/filebeat/config/05-modules_nginx-to-es.yamlfilebeat.config.modules: # 启用模块配置 path: ${path.config}/modules.d/*.yml # 指定模块的路径(找所有已启用的模块) # 此处的'${path.config}'会找到模块的配置文件路径,启动Filebeat时会定义该参数 reload.enabled: true # 是否支持热加载配置文件
# 变一下索引(filebeat-modules)output.elasticsearch: hosts: ["http://10.0.0.6:9200","http://10.0.0.7:9200","http://10.0.0.8:9200"] index: "kpyun-filebeat-modules-nginx-%{+yyyy.MM.dd}" # 按天分割索引
# 下面的不改变setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称setup.template.name: "kpyun" # 索引模板名(可自定义)setup.template.pattern: "kpyun-filebeat*" # 匹配索引名称的模式(根据索引模板初始化)setup.template.overwrite: falsesetup.template.settings: index.number_of_shards: 3 # 分片数 index.number_of_replicas: 0 # 副本数
4)启动Filebeat实例root@Elk02 ~# rm -rf /var/lib/filebeat/# 换输入 raw→module,且修改为新测试数据,新老日志全部重过模块管道root@Elk02 ~# filebeat -e -c /etc/filebeat/config/05-modules_nginx-to-es.yaml
5)kibana查询相关的字段host.namelog.file.pathsource.geo.city_namesource.geo.country_nameurl.pathuser_agent.os.fulluser_agent.device.nametraefik.access.user_agent.namesource.iphttp.response.status_code
# 过滤失败的请求(状态码 404)http.response.status_code : 404
# 过滤成功且来自美国的请求http.response.status_code : 200 and source.geo.country_name : "United States"


Filebeat 多实例
1)什么是多实例# 一台服务器上运行多个 Filebeat 进程,共用同一套程序文件# 关键是每个实例必须指定独立的 --path.data,否则锁冲突'data path already locked by another beat.'root@Elk02 ~# tree -L 1 /var/lib/filebeat//var/lib/filebeat/├── filebeat.lock # 锁文件├── meta.json└── registry(目录)
2)启动两个实例root@Elk02 ~# killall -9 filebeat # 清理旧进程root@Elk02 ~# rm -rf /var/lib/filebeat/ /tmp/xixi_data/ # 清理旧数据,准备多实例演示
# 实例 1:默认数据路径 /var/lib/filebeat/root@Elk02 ~# nohup filebeat -e -c /etc/filebeat/config/01-stdin-to-console.yaml &> /tmp/inst1.log &
# 实例 2:自定义数据路径 --path.data /tmp/xixi_data💡 'filebeat 会自动创建 --path.data 指定的目录,不需要提前 mkdir'root@Elk02 ~# nohup filebeat -e -c /etc/filebeat/config/02-log-to-console.yaml --path.data /tmp/xixi_data &> /tmp/inst2.log &
3)验证root@Elk02 ~# root@Elk02 ~# ps -ef | grep filebeat | grep -v grep | wc -l2 # 2 个 filebeat 进程
root@Elk02 ~# ls /var/lib/filebeat/filebeat.lock meta.json registry
root@Elk02 ~# ls /tmp/xixi_data/filebeat.lock meta.json registry# 两个实例各有独立的元数据目录,互不干扰不指定 --path.data 启动第二个实例会报错——默认路径已被第一个进程占用(filebeat.lock)
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!















