Elastic Stack 工具手册
先确认你需要的是字段索引
Elastic Stack 适合这样的日志:团队需要按结构化字段精确过滤、聚合和关联,查询方式会持续变化,而且愿意承担 mapping、分片、生命周期、备份和升级。它的优势不只是“能搜索文本”。Elasticsearch 会把 document 的字段写入倒排索引、doc values 等数据结构,Kibana 再用 data view、Discover 和可视化访问这些结构。字段越多、分片越碎、保留越久,这套能力的内存、磁盘和集群状态成本就越高。
如果主要查询方式是先按少量低基数标签圈定日志流,再扫描正文,Grafana Loki 往往更贴近问题;日志只服务于单一云账号内的资源运维时,也应先看对应云日志产品。Elastic Stack 不该因为界面熟悉而成为所有日志的默认落点。
本文实验固定使用不含真实用户信息的 JSONL 事件。trace_id 是验证身份,不是为了模拟事故:
{"@timestamp":"<EVENT_TIMESTAMP>","service":"checkout","environment":"dev","level":"ERROR","trace_id":"TE-ES-001","duration_ms":812,"message":"payment timeout"}用一条 data stream 跑通写入
以下 Compose 把 Elasticsearch、Kibana 和 Logstash 固定在同一 9.4.2 Stack 版本。这个数字是本文可复核的实验基线,不是允许自动漂移的 latest。升级前应重新核对 Elastic release notes、插件兼容性和目标版本升级说明。
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.4.2
environment:
discovery.type: single-node
xpack.security.enabled: "false"
ES_JAVA_OPTS: "-Xms1g -Xmx1g"
ports:
- "127.0.0.1:9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:9200/_cluster/health >/dev/null"]
interval: 10s
timeout: 5s
retries: 30
kibana:
image: docker.elastic.co/kibana/kibana:9.4.2
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
ports:
- "127.0.0.1:5601:5601"
depends_on:
elasticsearch:
condition: service_healthy
logstash:
image: docker.elastic.co/logstash/logstash:9.4.2
environment:
XPACK_MONITORING_ENABLED: "false"
volumes:
- ./pipeline:/usr/share/logstash/pipeline:ro
- ./logs:/logs:ro
- ls-data:/usr/share/logstash/data
depends_on:
elasticsearch:
condition: service_healthy
volumes:
es-data:
ls-data:关闭认证只适用于端口绑定回环地址、数据完全合成的空实验。只要端口跨主机开放,就要恢复 TLS 与认证,用 CA 校验证书,并给采集器签发只允许写目标 data stream 的 API key。把这份 Compose 暴露到局域网并不因为“只是开发环境”而安全。
Logstash 的文件输入要保留读取位置。sincedb_path 放在具名卷中,容器重建后才能续读;删除该卷会让 Logstash 忘记 offset,接下来是重读还是跳过还取决于文件身份与轮转状态。
input {
file {
id => "checkout-jsonl"
path => "/logs/*.jsonl"
start_position => "beginning"
sincedb_path => "/usr/share/logstash/data/checkout.sincedb"
codec => json
}
}
filter {
mutate {
add_field => {
"[data_stream][type]" => "logs"
"[data_stream][dataset]" => "checkout"
"[data_stream][namespace]" => "dev"
}
remove_field => ["@version", "host", "path", "log", "event", "tags"]
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
data_stream => true
data_stream_auto_routing => true
}
stdout { codec => rubydebug }
}启动时先证明进程和集群可用,不急着打开 Kibana:
mkdir -p pipeline logs
: > logs/app.jsonl
docker compose up -d
docker compose ps
curl -fsS http://localhost:9200/_cluster/health?pretty单节点出现 yellow 往往表示副本无处分配,并不等于主分片不可写。开发实验可以接受这个已解释的状态;生产集群则必须查清副本为什么没有被分配。
index template 决定新数据怎样出生
data stream 提供稳定写入名,数据实际落在隐藏的 backing indices 中。匹配它的 index template 必须声明 data_stream,每条事件也要有 @timestamp。模板应该先于第一条业务事件创建,否则动态 mapping 可能先接受错误字段类型。
curl -fsS -X PUT http://localhost:9200/_index_template/logs-checkout-template \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["logs-checkout-*"],
"priority": 500,
"data_stream": {},
"template": {
"settings": {
"index.number_of_shards": 1,
"index.number_of_replicas": 0,
"mapping.total_fields.limit": 500
},
"mappings": {
"dynamic": "strict",
"properties": {
"@timestamp": {"type": "date"},
"service": {"type": "keyword"},
"environment": {"type": "keyword"},
"level": {"type": "keyword"},
"trace_id": {"type": "keyword"},
"duration_ms": {"type": "long"},
"message": {"type": "text"},
"data_stream": {
"properties": {
"type": {"type": "constant_keyword"},
"dataset": {"type": "constant_keyword"},
"namespace": {"type": "constant_keyword"}
}
}
}
}
}
}'keyword 适合精确过滤和聚合,text 适合全文检索。dynamic: strict 会把未知字段变成明确写入失败;它适合暴露 schema 漂移,但团队必须同步建立字段变更流程。只打开 strict 而不给新增字段留发布路径,会把正常版本发布变成采集故障。
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '%s\n' "{\"@timestamp\":\"$NOW\",\"service\":\"checkout\",\"environment\":\"dev\",\"level\":\"ERROR\",\"trace_id\":\"TE-ES-001\",\"duration_ms\":812,\"message\":\"payment timeout\"}" >> logs/app.jsonl
sleep 3
curl -fsS 'http://localhost:9200/logs-checkout-dev/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{"query":{"term":{"trace_id":"TE-ES-001"}}}'结果中的 hits.total.value 应为 1。data stream 不存在时先看 docker compose logs logstash:没有匹配模板、strict mapping 拒绝、认证失败和连接拒绝分别属于不同层。此时改 Kibana 时间范围不会让尚未写入的 document 出现。
失败事件必须留下可观察出口
把 duration_ms 写成 "slow",可以稳定制造类型冲突。Logstash 已读取文件不代表 Elasticsearch 接受了 document;后端返回的 400 才是这一层的直接证据。
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '%s\n' "{\"@timestamp\":\"$NOW\",\"service\":\"checkout\",\"environment\":\"dev\",\"level\":\"ERROR\",\"trace_id\":\"TE-ES-BAD\",\"duration_ms\":\"slow\",\"message\":\"bad field type\"}" >> logs/app.jsonl
sleep 3
docker compose logs --since=1m logstash
curl -fsS 'http://localhost:9200/logs-checkout-dev/_count' \
-H 'Content-Type: application/json' \
-d '{"query":{"term":{"trace_id":"TE-ES-BAD"}}}'永久错误不应无限重试。生产管道需要一个有容量上限、保留期限和重放流程的失败出口,并对拒绝数量与最老失败年龄告警。否则一次字段变更既会丢日志,也可能用重试吃满本地队列和磁盘。
分片、滚动、保留与备份各管一件事
backing index 被切成 primary shard,replica 是额外副本。分片太大拖慢恢复,太小则让固定开销和集群状态压垮节点。按天建索引常会产生小分片;应根据写入字节、文档数、查询窗口、恢复目标和磁盘水位决定 rollover,而不是把日历当容量模型。
curl -fsS 'http://localhost:9200/_data_stream/logs-checkout-dev?pretty'
curl -fsS 'http://localhost:9200/_cat/indices/.ds-logs-checkout-dev-*?v&expand_wildcards=all'
curl -fsS 'http://localhost:9200/_cat/shards/logs-checkout-dev?v'简单保留可以交给 data stream lifecycle;需要 hot、warm、cold、frozen、searchable snapshot 或 force merge 等动作时再选择 ILM。两套生命周期不要同时管理同一 data stream。
curl -fsS -X PUT http://localhost:9200/_data_stream/logs-checkout-dev/_lifecycle \
-H 'Content-Type: application/json' \
-d '{"data_retention":"7d"}'
curl -fsS http://localhost:9200/_data_stream/logs-checkout-dev/_lifecycle?pretty保留不是备份。误删、集群损坏和区域故障仍要靠 snapshot repository、独立恢复权限和实际恢复演练。更新 index template 也只影响未来 backing indices;不兼容 mapping 变更应写入新 data stream,经过双写或 reindex 核对后再切查询入口。
从第一份证据定位“搜不到”
Logstash 没读取记录时查路径、权限、inode、轮转和 sincedb。Logstash 已读但持续重试时查 mapping、API key、CA、429、磁盘水位和 data stream 权限。写入成功而查询为空时,才轮到 @timestamp、时区、目标 data stream、Kibana data view 以及 keyword/text 差异。
集群因磁盘水位把索引置为只读时,先释放并确认空间、检查分片分配,再解除 block。只执行解锁 API 会让磁盘继续写满。真正可交接的排障记录应包含固定 trace_id、Logstash offset 或错误、Elasticsearch 写入响应、目标 backing index 和查询条件,而不是一张 Discover 空白截图。
实验清理前先停采集器,避免删除期间继续写入:
docker compose stop logstash
curl -fsS -X DELETE http://localhost:9200/_data_stream/logs-checkout-dev
curl -fsS -X DELETE http://localhost:9200/_index_template/logs-checkout-template
docker compose down -vdown -v 只属于确认名称和数据均为合成内容的空实验。生产退出要先冻结新写入、验证 snapshot 可恢复、切换查询者与告警,再按法律保留和 owner 审批清理旧 data stream。Elastic 的长期成本也应落到这些对象上:摄取字节、字段数、分片数、查询扫描量、snapshot 体积和恢复时间必须能由明确 owner 解释。
