日志是系统的眼睛。没有日志,运维就是盲人摸象。本文从日志基础讲起,覆盖 ELK/EFK 全栈搭建、架构选型、成本控制,到一个完整的实战项目,带你从零构建企业级日志平台。


一、日志基础:先搞清楚你在看什么

1.1 日志级别

别小看日志级别,乱用 ERRORDEBUG 是新手最常见的坑。

级别

含义

典型场景

FATAL

系统即将崩溃

OOM、磁盘满、核心服务不可用

ERROR

操作失败,需要关注

数据库连接超时、支付回调失败

WARN

潜在问题,暂不影响业务

接口响应慢、重试成功

INFO

关键业务事件

用户登录、订单创建、服务启动

DEBUG

调试信息,生产环境关闭

方法入参出参、SQL 语句

TRACE

最细粒度,几乎不开

框架内部调用链

经验法则: 生产环境开 INFO,排查问题时临时开 DEBUG(用动态日志级别),永远别在生产写 DEBUG 日志到磁盘——你会后悔的。

1.2 结构化日志

别再写 "User login success" 这种裸字符串了。结构化日志 = 可搜索 + 可聚合 + 可告警。

{
  "timestamp": "2026-06-10T14:05:23.456Z",
  "level": "INFO",
  "service": "user-service",
  "trace_id": "abc123def456",
  "span_id": "span-789",
  "message": "User login success",
  "user_id": "u_10086",
  "ip": "10.0.1.55",
  "latency_ms": 42
}

关键字段:

  • timestamp — ISO 8601 格式,带时区,别用 Unix 时间戳(人看不懂)

  • trace_id — 分布式链路追踪的命根子,全链路必须透传

  • service — 哪个服务写的,日志聚合时按服务分组

1.3 日志格式规范

给团队定一个日志规范,省得后面吵架:

# 推荐的日志行格式(非 JSON,适合 syslog 类场景)
2026-06-10T14:05:23.456+08:00 [INFO] [user-service] [trace:abc123] User login success user_id=u_10086 ip=10.0.1.55 latency=42ms

# 禁止的写法
logger.info("something happened")           # 没有上下文
logger.error(e.toString())                   # 没有堆栈
logger.info("user=" + userId + " login")     # 拼接地狱,用参数化

Java 示例(SLF4J + Logback):

// ✅ 正确
log.info("User login success, userId={}, ip={}, latency={}ms", userId, ip, latency);

// ❌ 错误
log.info("User " + userId + " login from " + ip);

二、ELK / EFK 栈概述

2.1 两个栈,一个家族

组件

ELK

EFK

收集

Logstash

Fluentd / Fluent Bit

存储

Elasticsearch

Elasticsearch

展示

Kibana

Kibana

特点

功能强大,资源占用高

轻量级,K8s 友好

选型建议:

  • 传统服务器 / 虚拟机 → ELK(Logstash 功能全面)

  • Kubernetes / 容器化 → EFK(Fluent Bit 是 DaemonSet 天然适配)

  • 预算紧张 / 只要日志查询 → Grafana Loki(下面单独讲)


三、Elasticsearch:日志存储引擎

3.1 安装(Docker 单节点,快速上手)

# 创建数据目录
mkdir -p /opt/elasticsearch/data
chown -R 1000:1000 /opt/elasticsearch

# 启动
docker run -d \
  --name elasticsearch \
  -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "ES_JAVA_OPTS=-Xms2g -Xmx2g" \
  -v /opt/elasticsearch/data:/usr/share/elasticsearch/data \
  docker.elastic.co/elasticsearch/elasticsearch:8.13.0

验证:

curl -s http://localhost:9200 | jq .
# 返回 cluster_name、version 等信息即成功

3.2 索引管理

# 创建索引(带 settings)
curl -X PUT "http://localhost:9200/app-logs-2026.06.10" -H 'Content-Type: application/json' -d '{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "5s"
  },
  "mappings": {
    "properties": {
      "timestamp":   { "type": "date" },
      "level":       { "type": "keyword" },
      "service":     { "type": "keyword" },
      "trace_id":    { "type": "keyword" },
      "message":     { "type": "text", "analyzer": "standard" },
      "user_id":     { "type": "keyword" },
      "ip":          { "type": "ip" },
      "latency_ms":  { "type": "integer" }
    }
  }
}'

# 查询日志
curl -X GET "http://localhost:9200/app-logs-*/_search" -H 'Content-Type: application/json' -d '{
  "query": {
    "bool": {
      "must": [
        { "match": { "message": "login" } },
        { "term":  { "level": "ERROR" } }
      ],
      "filter": [
        { "range": { "timestamp": { "gte": "now-1h" } } }
      ]
    }
  },
  "sort": [{ "timestamp": "desc" }],
  "size": 20
}'

3.3 映射设计要点

keyword vs text:
- keyword → 精确匹配、聚合、排序(level、service、user_id)
- text    → 全文检索(message、description)
- 两者可同时设:{ "type": "text", "fields": { "keyword": { "type": "keyword" } } }

日期格式:
- 统一用 ISO 8601:"2026-06-10T14:05:23.456+08:00"
- ES 自动识别,但显式指定 format 更稳

IP 类型:
- "type": "ip" 支持 CIDR 查询:10.0.0.0/8

3.4 生产集群(3 节点最小可用)

# docker-compose.yml — 3 节点集群
version: "3.8"
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - node.name=es01
      - cluster.name=prod-logs
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms4g -Xmx4g"
      - xpack.security.enabled=true
    volumes:
      - es01-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    deploy:
      resources:
        limits:
          memory: 8G

  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - node.name=es02
      - cluster.name=prod-logs
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms4g -Xmx4g"
      - xpack.security.enabled=true
    volumes:
      - es02-data:/usr/share/elasticsearch/data

  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - node.name=es03
      - cluster.name=prod-logs
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms4g -Xmx4g"
      - xpack.security.enabled=true
    volumes:
      - es03-data:/usr/share/elasticsearch/data

volumes:
  es01-data:
  es02-data:
  es03-data:

四、Logstash:日志处理管道

4.1 三大组件

Input → Filter → Output
  │        │        │
  │        │        └─ Elasticsearch / Kafka / S3
  │        └─ Grok / Mutate / GeoIP / Drop
  └─ File / Beats / Kafka / Syslog / HTTP

4.2 Input 配置

# logstash.conf
input {
  # 从文件读取
  file {
    path => "/var/log/nginx/access.log"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_nginx"
    codec => "json"
  }

  # 接收 Beats 输入
  beats {
    port => 5044
  }

  # 接收 Syslog
  syslog {
    port => 5514
    type => "syslog"
  }

  # 从 Kafka 消费
  kafka {
    bootstrap_servers => "kafka1:9092,kafka2:9092"
    topics => ["app-logs"]
    group_id => "logstash-consumer"
    codec => json
  }
}

4.3 Filter:Grok 解析

Grok 是 Logstash 的灵魂——用正则把非结构化文本变成结构化字段。

filter {
  # 解析 Nginx 访问日志
  if [type] == "nginx-access" {
    grok {
      match => {
        "message" => '%{IPORHOST:client_ip} - %{DATA:user} \[%{HTTPDATE:timestamp}\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status:int} %{NUMBER:bytes:int} "%{DATA:referrer}" "%{DATA:user_agent}" %{NUMBER:latency_ms:float}'
      }
    }

    # 解析时间戳
    date {
      match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
      target => "@timestamp"
    }

    # GeoIP 解析
    geoip {
      source => "client_ip"
      target => "geo"
    }

    # User-Agent 解析
    useragent {
      source => "user_agent"
      target => "ua"
    }

    # 条件丢弃健康检查日志
    if [request] =~ /^\/health/ {
      drop {}
    }
  }

  # 解析 Java 异常堆栈(多行合并后)
  if [type] == "java-exception" {
    grok {
      match => {
        "message" => '(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[(?<thread>[\w\-\.]+)\]\s+(?<level>\w+)\s+(?<logger>[\w\.]+)\s*-\s*(?<log_message>.*)'
      }
    }

    # 提取异常类名
    if [log_message] =~ /Exception/ {
      grok {
        match => {
          "log_message" => '(?<exception_class>[\w\.]*Exception[\w]*):\s*(?<exception_message>.*)'
        }
      }
    }
  }
}

4.4 Output 配置

output {
  # 写入 Elasticsearch
  elasticsearch {
    hosts => ["https://es01:9200", "https://es02:9200"]
    user => "logstash_writer"
    password => "${ES_PASSWORD}"
    index => "app-logs-%{+YYYY.MM.dd}"
    ssl => true
    cacert => "/etc/logstash/certs/ca.crt"
  }

  # 错误日志同时写入告警主题
  if [level] == "ERROR" {
    kafka {
      bootstrap_servers => "kafka1:9092"
      topic_id => "alert-logs"
      codec => json
    }
  }

  # 调试用:输出到 stdout
  # stdout { codec => rubydebug }
}

五、Kibana:可视化与查询

5.1 安装

docker run -d \
  --name kibana \
  -p 5601:5601 \
  -e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \
  -e "xpack.security.enabled=false" \
  docker.elastic.co/kibana/kibana:8.13.0

5.2 KQL 查询语法

KQL(Kibana Query Language)比 Lucene 更直观:

# 精确匹配
level: "ERROR"

# 全文搜索
message: "timeout" and service: "order-service"

# 范围查询
latency_ms > 1000

# 通配符
service: user*

# 嵌套字段
geo.country: "China"

# 组合查询
(level: "ERROR" or level: "FATAL") and service: "payment-service" and latency_ms > 500

# 排除
not message: "health check"

5.3 Dashboard 实战

创建一个 服务健康大盘,包含以下可视化:

1. 日志量趋势(Line Chart)
   - X 轴:@timestamp(按 1 分钟聚合)
   - Y 轴:Count
   - 分组:按 level 着色

2. 错误率(Gauge)
   - 公式:ERROR 数 / 总日志数 × 100%
   - 阈值:> 5% 红色,> 1% 黄色

3. Top 10 慢请求(Data Table)
   - 过滤:latency_ms > 500
   - 列:timestamp, service, request, latency_ms
   - 排序:latency_ms desc

4. 错误分布(Pie Chart)
   - 聚合:按 exception_class 分组
   - 过滤:level: "ERROR"

5. 地理分布(Map)
   - 字段:geo.location
   - 过滤:最近 24 小时

六、Filebeat:轻量级日志采集

6.1 为什么用 Filebeat?

Logstash 吃内存(512MB+),Filebeat 只要几十 MB。在每台服务器上放一个 Filebeat 做采集,Logstash 集中做处理——这是经典架构。

6.2 安装与基础配置

# filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/access.log
    json.keys_under_root: true
    json.overwrite_keys: true

  - type: log
    enabled: true
    paths:
      - /var/log/app/*.log
    fields:
      service: my-app
      env: production
    fields_under_root: true

output.logstash:
  hosts: ["logstash1:5044", "logstash2:5044"]
  loadbalance: true

# 或直接输出到 Elasticsearch
# output.elasticsearch:
#   hosts: ["es01:9200"]
#   index: "filebeat-%{+yyyy.MM.dd}"

logging.level: info
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat.log
  keepfiles: 7

6.3 Module 系统

Filebeat 的 module 把常见日志的采集、解析、Dashboard 一键搞定:

# 启用 Nginx module
filebeat modules enable nginx

# 启用 System module(syslog + auth)
filebeat modules enable system

# 启用 MySQL 慢查询
filebeat modules enable mysql
# modules.d/nginx.yml
- module: nginx
  access:
    enabled: true
    var.paths: ["/var/log/nginx/access.log"]
  error:
    enabled: true
    var.paths: [/var/log/nginx/error.log"]

# modules.d/system.yml
- module: system
  syslog:
    enabled: true
    var.paths: ["/var/log/syslog", "/var/log/messages"]
  auth:
    enabled: true
    var.paths: ["/var/log/auth.log", "/var/log/secure"]

6.4 多行日志处理

Java 异常堆栈是典型的多行日志,不处理的话每一行都会变成一条独立日志——等于废了。

filebeat.inputs:
  - type: log
    paths:
      - /var/log/app/*.log
    multiline.type: pattern
    multiline.pattern: '^\d{4}-\d{2}-\d{2}'    # 以日期开头的是新日志
    multiline.negate: true                       # 不匹配的行归入上一条
    multiline.match: after                       # 归入上一条之后
    multiline.max_lines: 100                     # 最多合并 100 行

    # 或者更精确地匹配 Java 异常
    # multiline.pattern: '^\s+(at |\.{3}\s|Caused by|java\.|javax\.)'
    # multiline.negate: false
    # multiline.match: after

七、Fluentd / Fluent Bit:K8s 时代的日志方案

7.1 Fluentd vs Fluent Bit

特性

Fluentd

Fluent Bit

语言

Ruby + C

纯 C

内存

~40MB

~1MB

插件

500+

100+

定位

统一日志层

轻量采集器

典型用法

集中聚合层

DaemonSet 采集

经典组合: Fluent Bit(DaemonSet 采集)→ Fluentd(聚合层)→ Elasticsearch

7.2 Fluent Bit 配置示例

# fluent-bit.conf
[SERVICE]
    Flush        5
    Daemon       Off
    Log_Level    info
    Parsers_File parsers.conf

[INPUT]
    Name              tail
    Tag               kube.*
    Path              /var/log/containers/*.log
    Parser            docker
    DB                /var/log/flb_kube.db
    Mem_Buf_Limit     5MB
    Skip_Long_Lines   On
    Refresh_Interval  10

[FILTER]
    Name                kubernetes
    Match               kube.*
    Kube_URL            https://kubernetes.default.svc:443
    Kube_CA_File        /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    Kube_Token_File     /var/run/secrets/kubernetes.io/serviceaccount/token
    Kube_Tag_Prefix     kube.var.log.containers.
    Merge_Log           On
    K8S-Logging.Parser  On
    K8S-Logging.Exclude Off

[OUTPUT]
    Name            es
    Match           kube.*
    Host            elasticsearch.logging.svc
    Port            9200
    Index           k8s-logs
    Type            _doc
    Logstash_Format On
    Logstash_Prefix k8s
    Retry_Limit     5
# parsers.conf
[PARSER]
    Name        docker
    Format      json
    Time_Key    time
    Time_Format %Y-%m-%dT%H:%M:%S.%L
    Time_Keep   On

7.3 K8s DaemonSet 部署

# fluent-bit-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      serviceAccountName: fluent-bit
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:2.2
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 256Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: containers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: config
              mountPath: /fluent-bit/etc/
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: containers
          hostPath:
            path: /var/lib/docker/containers
        - name: config
          configMap:
            name: fluent-bit-config

八、架构选型:ELK vs EFK vs Loki

8.1 三种方案对比

维度

ELK

EFK

Loki + Grafana

全文检索

⭐⭐⭐ 强

⭐⭐⭐ 强

⭐⭐ 只索引标签

资源消耗

高(ES 很吃内存)

低(1/10 ES 资源)

运维复杂度

生态

最成熟

K8s 原生

Grafana 生态

适合场景

大规模全文检索

K8s 容器日志

成本敏感 / 标签查询够用

存储成本

低(对象存储)

8.2 决策树

你的日志需要全文检索吗?
├─ 是 → 预算充足?
│   ├─ 是 → ELK / EFK
│   └─ 否 → 考虑 ES 冷热架构 + ILM
└─ 否 → 在 K8s 上?
    ├─ 是 → Loki + Grafana(便宜好用)
    └─ 否 → Loki 也行,或者 EF(只用 Fluentd + ES)

九、日志管理最佳实践

9.1 ILM(Index Lifecycle Management)

别让索引无限增长,ES 会先慢后挂。

# 创建 ILM 策略
curl -X PUT "http://localhost:9200/_ilm/policy/logs-lifecycle" -H 'Content-Type: application/json' -d '{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "30gb",
            "max_age": "1d"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "3d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 },
          "set_priority": { "priority": 50 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "set_priority": { "priority": 0 },
          "freeze": {}
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'

# 创建索引模板,绑定 ILM
curl -X PUT "http://localhost:9200/_index_template/logs-template" -H 'Content-Type: application/json' -d '{
  "index_patterns": ["app-logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs-lifecycle",
      "index.lifecycle.rollover_alias": "app-logs"
    }
  }
}'

9.2 成本控制清单

✅ 开启 ILM,自动清理过期索引
✅ Warm/Cold 节点用便宜磁盘(HDD / 低频 OSS)
✅ 设置 max_docvalue_fields_search 限制
✅ 关闭不需要的 _source(谨慎!影响 reindex)
✅ 用 Frozen 索引节省 60%+ 内存
✅ 日志采样:DEBUG 日志采样 10%,不是全量
✅ 索引别名 + Rollover,避免巨型单索引
✅ 压缩传输:Logstash → ES 开启 gzip
❌ 不要把 ES 当长期存储(> 90 天用对象存储归档)
❌ 不要所有字段都用 text(keyword 省空间)

9.3 安全加固

# ES 安全配置要点
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.http.ssl.enabled: true

# 最小权限角色
# logstash_writer: 只有 create_index + write
# kibana_reader: 只有 read
# admin: 全权限

十、实战:从零搭建完整日志平台

10.1 架构图

┌──────────────────────────────────────────────────────────────┐
│                        应用服务器                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                    │
│  │ App Logs │  │Nginx Logs│  │  Syslog  │                    │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                    │
│       └──────────────┼────────────┘                           │
│              ┌───────▼────────┐                               │
│              │   Filebeat     │  (每台服务器一个)              │
│              └───────┬────────┘                               │
└──────────────────────┼───────────────────────────────────────┘
                       │ :5044
              ┌────────▼─────────┐
              │    Logstash      │  (2 节点,负载均衡)
              │  Filter + Grok   │
              └────────┬─────────┘
                       │
         ┌─────────────┼─────────────┐
         │             │             │
    ┌────▼────┐  ┌─────▼────┐  ┌────▼─────┐
    │ES Hot   │  │ES Warm   │  │Kafka     │
    │(SSD)    │  │(HDD)     │  │(缓冲层)  │
    └────┬────┘  └────┬─────┘  └──────────┘
         └─────────────┘
                │
         ┌──────▼──────┐
         │   Kibana    │
         │ Dashboard   │
         └─────────────┘

10.2 一键部署(docker-compose)

# docker-compose-logging.yml
version: "3.8"

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5

  logstash:
    image: docker.elastic.co/logstash/logstash:8.13.0
    container_name: logstash
    ports:
      - "5044:5044"
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    depends_on:
      elasticsearch:
        condition: service_healthy
    environment:
      - "LS_JAVA_OPTS=-Xms512m -Xmx512m"

  kibana:
    image: docker.elastic.co/kibana/kibana:8.13.0
    container_name: kibana
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      elasticsearch:
        condition: service_healthy

  filebeat:
    image: docker.elastic.co/beats/filebeat:8.13.0
    container_name: filebeat
    user: root
    volumes:
      - ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/log:/var/log:ro
      - filebeat-data:/usr/share/filebeat/data
    depends_on:
      - logstash

volumes:
  es-data:
  filebeat-data:
# logstash/pipeline/logstash.conf
input {
  beats {
    port => 5044
  }
}

filter {
  if [fields][service] == "nginx" {
    grok {
      match => {
        "message" => '%{IPORHOST:client_ip} - %{DATA:user} \[%{HTTPDATE:access_time}\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status:int} %{NUMBER:bytes:int}'
      }
    }
    date {
      match => ["access_time", "dd/MMM/yyyy:HH:mm:ss Z"]
    }
    mutate {
      remove_field => ["access_time", "message"]
    }
  }
}

output {
  elasticsearch {
    hosts => ["http://elasticsearch:9200"]
    index => "%{[fields][service]}-%{+YYYY.MM.dd}"
  }
}
# filebeat/filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/*.log
    fields:
      service: nginx
    fields_under_root: false

output.logstash:
  hosts: ["logstash:5044"]

logging.level: warning

10.3 启动与验证

# 启动
docker-compose -f docker-compose-logging.yml up -d

# 等待 ES 就绪(约 30 秒)
until curl -sf http://localhost:9200/_cluster/health; do sleep 2; done

# 验证 Logstash
curl -s http://localhost:9600 | jq .   # Logstash API

# 验证 Kibana
open http://localhost:5601              # 浏览器打开

# 生成测试日志
for i in $(seq 1 100); do
  echo "192.168.1.$((RANDOM%255)) - - [$(date '+%d/%b/%Y:%H:%M:%S %z')] \"GET /api/test HTTP/1.1\" 200 $((RANDOM%5000))" >> /var/log/nginx/access.log
done

# 在 Kibana Discover 中查看索引
curl -s "http://localhost:9200/_cat/indices?v&h=index,docs.count,store.size"

10.4 生产环境 Checklist

□ ES 集群至少 3 节点,开启 xpack.security
□ Logstash 至少 2 节点,前面加负载均衡
□ Filebeat 每台服务器部署,配置 at-least-once 语义
□ Kafka 作为缓冲层(日志量 > 10K/s 时必须加)
□ ILM 策略配置完毕,Hot/Warm/Cold/Delete
□ Kibana Dashboard 创建:错误率、延迟分布、日志量趋势
□ 告警规则:ERROR 率突增、日志断流、ES 集群 Red
□ 备份:ES Snapshot 定期备份到 S3/OSS
□ 监控 ES 自身:集群健康、JVM 堆使用、磁盘水位
□ 安全:TLS 加密传输、最小权限角色、网络隔离

小结

阶段

建议

刚起步

单节点 ES + Filebeat + Kibana,够用

日志量上来

加 Logstash 做处理,ES 扩 3 节点

容器化

换 Fluent Bit DaemonSet,考虑 EFK

成本敏感

评估 Loki,或 ES 冷热分层 + ILM

大规模

Kafka 缓冲 + 多层聚合 + 对象存储归档

日志平台不是一蹴而就的。先跑起来,再迭代优化。记住:能搜到的日志才有价值,搜不到的就是噪音。