ElasticStack开篇

6867 字
34 分钟
ElasticStack开篇
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
Tip

由于组件增多(还有Beats家族等),官方将整个技术栈统一命名为Elastic Stack,但大家习惯上仍常称其为ELK

架构图#

架构图解
架构图解

Note

使用 Filebeat 替换了 Fluentd,架构名称就不能再叫 EFK 了 EFK 这个缩写中的 “F” 已经被行业约定俗成地指代 Fluentd,它是一个专有名词

日志系统基于 Elastic Stack,使用 Filebeat 作为节点采集器,将日志传输至 Elasticsearch 集群,并通过 Kibana 进行可视化分析

  • 最重要的是Elasticsearch 集群(数据库)
    • Filebeat即使没了日志也不会丢,Kibana只是用来展示数据的,需要从ES集群(数据库)拿

Elasticsearch下载
Elasticsearch下载

单点部署#

Terminal window
# 下载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:/tmp
elasticsearch-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.deb
root@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.service
root@Elk01 ~# ss -ntl | grep "9[23]00"
LISTEN 0 4096 *:9300 *:*
LISTEN 0 4096 *:9200 *:*
端口协议用途说明
9200HTTP / HTTPS对外服务端口客户端(Kibana、Logstash、应用代码等)通过此端口进行索引、搜索、管理等 REST API 操作
9300TCP(Binary Protocol)集群内部通信端口ES 节点之间数据同步、分片迁移、节点发现与心跳,不对外开放
Tip
  1. 简单记:9200 面向用户,9300 面向节点
  2. ⚠️ 优先启动9300,再启动9200
Terminal window
# 测试验证
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/nodes
10.0.0.6 21 96 1 0.01 0.00 0.00 cdfhilmrstw * Elk01
root@Elk01 ~# curl 10.0.0.6:9200/_cat/nodes?v
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.0.0.6 22 96 0 0.00 0.00 0.00 cdfhilmrstw * Elk01
Tip

_cat 是 ES 提供的一套人类可读的紧凑输出 API,调试和运维时非常常用,?v 参数显示列标题

_cat/nodes 字段说明

字段示例值含义
ip10.0.0.6节点 IP 地址
heap.percent22JVM 堆内存使用率,过高需扩容或查内存泄漏
ram.percent96系统物理内存使用率
cpu0ES 进程 CPU 占用百分比
load_1m / load_5m / load_15m0.00系统 1/5/15 分钟平均负载
node.rolecdfhilmrstw节点角色(字母含义见下表)
master** 表示当前节点是主节点,- 表示不是
nameElk01节点名称

node.role 角色字母含义

字母角色说明
mmaster-eligible候选主节点,可参与主节点选举
ddata数据节点,存储索引数据
iingest摄取节点,数据预处理/管道
hhot热节点,存频繁写入/查询的索引
wwarm温节点,中等访问频率的索引
ccold冷节点,存不常访问的索引
ffrozen冻节点,极少查询的归档数据
lmachine learning机器学习节点
rremote_cluster_client跨集群搜索客户端
scontent内容节点(7.x 默认),数据 + 聚合分析
ttransform数据转换节点
Note

单节点部署时所有角色集中在一台机器上,生产环境一般将 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集群中待处理的任务队列
Terminal window
root@Elk01 ~# curl 10.0.0.6:9200/_cat/master?v
id host ip node
Uh71vV43Qg2bbkjI63mC2w 10.0.0.6 10.0.0.6 Elk01

ES 集群部署#

环境准备#

主机IP角色内存
Elk0110.0.0.6ES + Kibana4G
Elk0210.0.0.7ES + Filebeat + Nginx4G
Elk0310.0.0.8ES4G
Warning
  • 先关机 virsh shutdown Elk01/02/03

  • 扩容内存 virsh setmaxmem Elk01 --size 4G --config ,启动后 virsh setmem Elk01 --size 4G

Important

关键坑点

  • 从单点切集群前,必须 rm -rf /var/{log,lib}/elasticsearch/* 清空旧数据,否则加入集群会失败
  • 三台节点要==同时启动==,否则可能脑裂(brain split)
  • 每台 4G 内存,JVM 堆设 512m 即可,避免 OOM

配置文件#

Terminal window
# JVM 堆内存(每台都要设)
-Xms512m
-Xmx512m
# elasticsearch.yml
root@Elk01 ~# egrep -v "^#|^$" /etc/elasticsearch/elasticsearch.yml
cluster.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首次启动时的主节点候选列表

部署步骤#

Terminal window
1)停止单点 ES
root@Elk01 ~# systemctl stop elasticsearch.service
root@Elk01 ~# ss -lntup | grep "9[23]00" | wc -l
0
# 端口已释放
2)安装 ES(Elk02、Elk03)
root@Elk02 ~# dpkg -i /tmp/elasticsearch-7.17.29-amd64.deb
root@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.service
root@Elk02 ~# systemctl enable --now elasticsearch.service
root@Elk03 ~# systemctl enable --now elasticsearch.service
✅️ 三台同时 start,避免脑裂
7)验证集群
root@Elk01 ~# curl 10.0.0.6:9200/_cat/nodes?v
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.0.0.6 65 80 18 0.59 0.28 0.12 cdfhilmrstw - Elk01
10.0.0.7 31 92 23 0.35 0.20 0.09 cdfhilmrstw * Elk02
10.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?v
epoch timestamp cluster status node.total node.data shards pri relo init unassign active_shards_percent
1785055088 08:38:08 My-Elk green 3 3 6 3 0 0 0 100.0%
# green = 所有主分片和副本分片均正常
Tip

💡 关机拍个快照

Terminal window
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-ready
jiuzhao@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 初体验#

ES集群的常见术语
ES集群的常见术语

Tip

📌 一句话:数据先到达 ==索引==,索引被切分成多个 ==主分片== 分散存储,每个主分片有 ==副本== 提供备份和读负载均衡


分片分配#

3 节点集群,3 个主分片 + 3 个副本分片(7.x 默认)

P0 → R0

P1 → R1

P2 → R2

Elk01

🔹 P0 (rw)

🔹 R2 (ro)

Elk02

🔹 P1 (rw)

🔹 R0 (ro)

Elk03

🔹 P2 (rw)

🔹 R1 (ro)

P0 → R0

P1 → R1

P2 → R2

Elk01

🔹 P0 (rw)

🔹 R2 (ro)

Elk02

🔹 P1 (rw)

🔹 R0 (ro)

Elk03

🔹 P2 (rw)

🔹 R1 (ro)

关键规则说明
==主副分离==主分片和它的副本不能在同一节点上,否则节点宕机主副全丢
==交叉分配==P0 在 Elk01 → R0 在 Elk02,P1 在 Elk02 → R1 在 Elk03
==分布式存储==数据分散在不同节点的不同分片上,每个分片里的数据==各不相同==
==磁盘 I/O==多分片可充分利用多节点磁盘性能并行读写

故障转移#

Master 节点Elk03 (P2 + R1)Elk02 (P1 + R0)Elk01 (P0 + R2)Master 节点Elk03 (P2 + R1)Elk02 (P1 + R0)Elk01 (P0 + R2)💥 Elk02 宕机R1 (ro) → P1' (rw)集群恢复健康检测到 P1 主分片失联R1 是 P1 的副本,在 Elk03 上将 R1 提升为新主分片 P1'新主分片 P1' 开始接收写请求
Master 节点Elk03 (P2 + R1)Elk02 (P1 + R0)Elk01 (P0 + R2)Master 节点Elk03 (P2 + R1)Elk02 (P1 + R0)Elk01 (P0 + R2)💥 Elk02 宕机R1 (ro) → P1' (rw)集群恢复健康检测到 P1 主分片失联R1 是 P1 的副本,在 Elk03 上将 R1 提升为新主分片 P1'新主分片 P1' 开始接收写请求
Important

📌 故障转移流程

  1. 某个节点宕机 → 该节点上的主分片不可用
  2. Master 节点检测到异常 → 找到该主分片的副本(在其他节点上)
  3. 将副本提升为新主分片(==ro → rw==)
  4. 接管后,原来的副本现在是主分片了,所以可读可写!

⚠️ 如果某个主分片没有副本(replica = 0),则节点宕机后该分片数据直接丢失,集群变 red 🟥


读写分离#

📖 读请求

📝 写请求

必须走主分片

路由到

也可路由到

pipeline 同步

用户

主分片 rw

用户

主分片 rw

副本分片 ro

📖 读请求

📝 写请求

必须走主分片

路由到

也可路由到

pipeline 同步

用户

主分片 rw

用户

主分片 rw

副本分片 ro

对比主分片 (Primary)副本分片 (Replica)
写入✅ 是❌ 否
读取✅ 是✅ 是
接管前rw(读写)ro(只读)
接管后—==rw==(提升为新主)
Tip

💡 这和 MySQL 主从复制 + 读写分离 非常类似

  • 主分片 = MySQL Master(读写)
  • 副本分片 = MySQL Slave(只读 + 负载均衡 + 故障接管)
  • replica = 0 相当于没有从库,读写压力全在主库上,还无备份

文档结构#

一个文档 = 元数据 + 源数据

📄 文档 Document

🏷️ 元数据 Metadata

💾 源数据 Source Data

_index: oldboyedu-linux

_type: _doc

_id: Uh71vxxxxC2w

name: 老男孩

hobby: Linux, 写书, 抖音, 下象棋

📄 文档 Document

🏷️ 元数据 Metadata

💾 源数据 Source Data

_index: oldboyedu-linux

_type: _doc

_id: Uh71vxxxxC2w

name: 老男孩

hobby: Linux, 写书, 抖音, 下象棋

部分作用举例
==元数据==描述这个文档属于哪个索引、ID 是什么_index _id _version
==源数据==用户实际存储的业务数据{"name":"孙悟空","hobby":["蟠桃"]}
Note

📌 写入文档时,ES 根据元数据中的 _index 确定文档属于哪个索引 → 索引被切分成多个分片 → 文档被路由存储到某个主分片中 → 主分片通过 pipeline 同步到副本分片


核心术语速查#

术语含义关键点
==索引 (Index)==数据的==逻辑==读写单元分片是其==物理==存储单元
==分片 (Shard)==索引被水平切分,每个分片存不同数据最少 1 个,7.x 默认 1 分片,创建后不可改
==副本 (Replica)==分片的拷贝,数据与主分片==一模一样==7.x 默认 1 副本,可设为 0(无备份),可动态调整
==文档 (Document)==ES 最小数据单元,JSON 格式元数据描述它,源数据是实际内容
Note

📌 索引为什么叫”索引”,却像”表”?

ES 底层是 Lucene,核心是倒排索引引擎 ES 把”建表”和”建索引”合二为一 —— 创建 Index 既是创建存储容器,也自动拥有了搜索能力

MySQLES说明
数据库 (Database)集群 (Cluster)顶层容器
表 (Table)索引 (Index)数据搜索 + 存储单元
行 (Row)文档 (Document)一条数据
列 (Column)字段 (Field)数据属性
Note

📌 水平切分 vs 垂直切分

切分方式维度说明
==水平切分==按行切不同文档分到不同分片 → 每个分片字段齐全,数据不同
垂直切分按列切不同字段分到不同存储 → 一条文档被拆成多份

ES 只用水平切分——你写入 3 条文档(孙悟空、猪八戒、沙和尚),每个分片存不同的文档,但每个分片里该有的字段一个不少

集群颜色#

颜色含义
green 🟩所有主分片 + 副本分片均正常访问
yellow 🟨主分片正常,部分副本分片无法访问(不影响读写)
red 🟥部分主分片不可用,==数据不完整==
Terminal window
1)无表头
root@Elk01 ~# curl localhost:9200/_cat/health
1785068357 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?v
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1785068357 12:19:17 My-Elk green 3 3 6 3 0 0 0 0 - 100.0%
字段值含义
epoch1785068357Unix 时间戳(秒)
timestamp12:19:17时间(HH:MM)
clusterMy-Elk集群名称
statusgreen健康状态:green / yellow / red
node.total3集群节点总数
node.data3数据节点数
shards6全部分片数(主 + 副)
pri3主分片数
relo0正在搬迁的分片数
init0正在初始化的分片数
unassign0==未分配的分片数==,不为 0 说明有问题
pending_tasks0待处理的任务数,持续积压说明集群繁忙
max_task_wait_time-任务最长等待时间,- 表示无排队
active_shards_percent100.0%活跃分片百分比,==100% 才正常==

DSL 语句实操#

Terminal window
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 jq
root@Elk01 ~# curl -s http://10.0.0.6:9200/kpyun/_search | jq
"hits": {
"total": {
"value": 3,
"relation": "eq"
} ...............
# hits.total.value: 3 = 共 3 条文档
Note

用 10.0.0.7 或 10.0.0.8 也一样——集群内任意节点都能接收请求

Terminal window
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: true

Kibana 组件部署#

Kibana下载
Kibana下载

部署步骤#

Terminal window
1)安装 Kibana
root@Elk01 ~# wget -P /tmp/ https://artifacts.elastic.co/downloads/kibana/kibana-7.17.29-amd64.deb
root@Elk01 ~# dpkg -i /tmp/kibana-7.17.29-amd64.deb
2)修改配置
root@Elk01 ~# egrep -v "^#|^$" /etc/kibana/kibana.yml
server.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.host0.0.0.0监听所有网卡,允许外部访问
elasticsearch.hostsES 集群地址列表可配多个,Kibana 自动负载均衡
i18n.localezh-CN界面中文,需先确认支持该语言包
Tip

Kibana 启动较慢(30s+),需要等 ES 集群就绪后才能完全初始化

Terminal window
3)启动服务
root@Elk01 ~# systemctl enable --now kibana.service
root@Elk01 ~# ss -ntl | grep 5601
LISTEN 0 511 0.0.0.0:5601 0.0.0.0:*
# Kibana 监听 5601 端口
4)访问 WebUI
http://10.0.0.6:5601/

Web-UI
Web-UI

Terminal window
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": ["大师兄,师傅被妖怪抓走了","二师兄,师傅被妖怪抓走了"] }
'
⚠️ 末尾还有个'单引号'

索引管理
索引管理

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

Discover
Discover

快速过滤查询
快速过滤查询

Filebeat 环境部署#

Filebeat下载
Filebeat下载

stdin 标准输入采集#

Terminal window
# 这里安装在Elk02
wget -P /tmp/ https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.29-amd64.deb
dpkg -i /tmp/filebeat-7.17.29-amd64.deb
root@Elk02 ~# mkdir -p /etc/filebeat/config
root@Elk02 ~# vim /etc/filebeat/config/01-stdin-to-console.yaml
filebeat.inputs:
- type: stdin # 从标准输入采集
output.console: # 输出到控制台
pretty: true
root@Elk02 ~# echo "Hello" | filebeat -e -c /etc/filebeat/config/01-stdin-to-console.yaml
echo "Hello" # 模拟输入
-e # 打印日志输出到当前终端
-c # 指定配置文件
✅️ 最简单的方式,适合测试
"message": "Hello", # message 字段: "Hello"
"input": {
"type": "stdin"
}

文本日志采集#

Terminal window
root@Elk02 ~# vim /etc/filebeat/config/02-log-to-console.yaml
filebeat.inputs:
- type: log # 从文件采集
paths:
- /tmp/xixi.log
output.console:
pretty: true
root@Elk02 ~# touch /tmp/xixi.log
root@Elk02 ~# filebeat -e -c /etc/filebeat/config/02-log-to-console.yaml
# 在另一个终端:
root@Elk02 ~# echo "hello world" > /tmp/xixi.log
root@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 , 会导致重复采集’
Terminal window
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.json
root@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.log
root@Elk02 ~# cat /tmp/xixi.log
hello world
line two
kpyun2026
xixixi
root@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
# 从头采集
Important

📌 什么时候该删 registry,什么时候不该?

offset 是跟着日志文件走的,不是跟着配置文件走的——换个 config 不换日志文件,offset 依然有效

Terminal window
# 我想做什么决定我删不删
# ✅ 想让旧行也重新处理一遍 → rm -rf /var/lib/filebeat/
# ❌ 旧行无所谓,新行接着往后读就行 → 不删
# ❌ 第一次读一个新日志文件 → 不删,自动从 0 开始
常见意图笔记对应变动需要删?
从头演示一遍采集效果log 重跑—✅
之前只是 console 预览,现在要全部入 ES 给 Kibana 看nginx→ES==换输出==✅
换解析方式(raw→module),旧数据用新规则重过modules→ES==换输入==✅
新日志文件第一次采集nginx→console—❌
验证 offset 生效,只采新行log 暂停继续—❌
生产环境,只管新数据——❌
Tip

判断口诀:想让旧行重新被处理 → 删;只管新行 → 不删


Nginx 日志采集(输出到终端)#

Terminal window
# Filebeat也在Elk02安装的
root@Elk02 ~# apt -y install nginx
root@Elk02 ~# vim /etc/filebeat/config/03-nginx-to-console.yaml
filebeat.inputs:
- type: log
paths:
- /var/log/nginx/access.log
output.console:
pretty: true
# 制造访问日志
root@Elk02 ~# curl 10.0.0.7
root@Elk02 ~# curl 10.0.0.7/NotFound
# 新日志文件,filebeat 从未见过,不需要删 registry
root@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#

架构图#

Elk01 (10.0.0.6)

Elk02 (10.0.0.7)

写入日志

:5601

output.elasticsearch

:9200 _bulk

:9200 查询

ES 集群

Elk01

P0 (rw)

Elk02

P1 (rw)

Elk03

P2 (rw)

Nginx :80

📝 access.log

Filebeat

采集 + 转发

Kibana :5601

可视化查询

👤 浏览器

Elk01 (10.0.0.6)

Elk02 (10.0.0.7)

写入日志

:5601

output.elasticsearch

:9200 _bulk

:9200 查询

ES 集群

Elk01

P0 (rw)

Elk02

P1 (rw)

Elk03

P2 (rw)

Nginx :80

📝 access.log

Filebeat

采集 + 转发

Kibana :5601

可视化查询

👤 浏览器

Filebeat → ES 配置#

Terminal window
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; done
root@Elk02 ~# wc -l /var/log/nginx/access.log
33 /var/log/nginx/access.log
2)编写 Filebeat 写入 ES 的配置
root@Elk02 ~# vim /etc/filebeat/config/04-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-filebeat-nginx-%{+yyyy.MM.dd}" # 按天分割索引
setup.ilm.enabled: false # 禁用 ILM,否则会忽略自定义 index 名称
setup.template.name: "kpyun" # 索引模板名(可自定义)
setup.template.pattern: "kpyun-filebeat*" # 匹配索引名称的模式(根据索引模板初始化)
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/ # 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 nginx
green 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 一致
Caution

⚠️ setup.ilm.enabled: false 必须显式设为 false

否则 ILM(索引生命周期管理)会接管索引命名,忽略你自定义的 index 名称 ‘这是新手最容易踩的坑之一’

自动刷新
自动刷新


Filebeat 模块管理#

Terminal window
1)查看模块列表
root@Elk02 ~# filebeat modules list
Enabled: # 已启用
Disabled: # 已禁用
activemq
apache
...
2)启用 nginx 模块
root@Elk02 ~# filebeat modules enable nginx mysql
Enabled nginx
Enabled mysql
root@Elk02 ~# filebeat modules list | head -4
Enabled:
mysql
nginx # 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 tomcat
root@Elk02 ~# filebeat modules list | head -4
Enabled:
.....tomcat # tomcat 出现在已启用列表
root@Elk02 ~# mv /etc/filebeat/modules.d/mysql.yml{,.disabled}
# 效果等同于 filebeat modules disable mysql
Tip

模块本质是预定义的 Filebeat 配置模板(解析规则 + 字段映射),帮你省去手写正则的麻烦

⚠️ 启用模块只是第一步,实际使用时还需要修改 modules.d/xxx.yml 中的具体路径等参数

基于模块采集nginx日志#

Terminal window
1)准备nginx的访问日志
root@Elk02 ~# vim /var/log/nginx/access.log
123.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"
Terminal window
root@Elk02 ~# wc -l /var/log/nginx/access.log
24 /var/log/nginx/access.log
2)编写Filebeat的模块文件
root@Elk02 ~# mv /etc/filebeat/modules.d/tomcat.yml{,.disabled}
root@Elk02 ~# filebeat modules list | head -3
Enabled:
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.yaml
filebeat.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: false
setup.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.name
log.file.path
source.geo.city_name
source.geo.country_name
url.path
user_agent.os.full
user_agent.device.name
traefik.access.user_agent.name
source.ip
http.response.status_code

Terminal window
# 过滤失败的请求(状态码 404)
http.response.status_code : 404
# 过滤成功且来自美国的请求
http.response.status_code : 200 and source.geo.country_name : "United States"

404
404

200
200

打开搜索
打开搜索


Filebeat 多实例#

Terminal window
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 -l
2 # 2 个 filebeat 进程
root@Elk02 ~# ls /var/lib/filebeat/
filebeat.lock meta.json registry
root@Elk02 ~# ls /tmp/xixi_data/
filebeat.lock meta.json registry
# 两个实例各有独立的元数据目录,互不干扰
Caution

不指定 --path.data 启动第二个实例会报错——默认路径已被第一个进程占用(filebeat.lock)

文章分享

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

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