## 19.2 ELK 套件

ELK (Elasticsearch，Logstash，Kibana) 是目前业界最流行的开源日志解决方案。而在容器领域，由于 Fluentd 更加轻量级且对容器支持更好，EFK (Elasticsearch，Fluentd，Kibana) 组合也变得非常流行。

### 19.2.1 方案架构

我们将采用以下架构：

1. **Docker Container**：容器将日志输出到标准输出 (stdout/stderr)。
2. **Fluentd**：作为 Docker 的 Logging Driver 或运行为守护容器，收集容器日志。
3. **Elasticsearch**：存储从 Fluentd 接收到的日志数据。
4. **Kibana**：从 Elasticsearch 读取数据并进行可视化展示。

### 19.2.2 部署流程

我们将使用 Docker Compose 来一键部署整个日志堆栈。

#### 1. 编写 Compose 文件

1. 编写 `compose.yaml` (或 `docker-compose.yml`) 配置如下。**版本提示**：本示例使用 Elasticsearch 9.x、Kibana 9.x、Fluentd 等组件的特定版本号，仅作为参考。生产环境部署前，请查阅 [Elastic 官方文档](https://www.elastic.co/docs/) 和 [Fluentd 官方文档](https://docs.fluentd.org/) 确认最新版本与配置兼容性。

```yaml
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.3.3
    container_name: elasticsearch
    environment:
      - "discovery.type=single-node"
      - "xpack.security.enabled=false"
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    networks:
      - logging

  kibana:
    image: docker.elastic.co/kibana/kibana:9.3.3
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    links:
      - elasticsearch
    networks:
      - logging

  fluentd:
    # elasticsearch8 插件通过 REST API 与 ES 9.x 兼容
    image: fluent/fluentd-kubernetes-daemonset:v1.17-debian-elasticsearch8-1
    container_name: fluentd
    environment:
      - "FLUENT_ELASTICSEARCH_HOST=elasticsearch"
      - "FLUENT_ELASTICSEARCH_PORT=9200"
      - "FLUENT_ELASTICSEARCH_SCHEME=http"
      - "FLUENT_UID=0"
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    links:
      - elasticsearch
    volumes:
      - ./fluentd/conf:/fluentd/etc
    networks:
      - logging

volumes:
  es_data:

networks:
  logging:
```

#### 2. 配置 Fluentd

创建 `fluentd/conf/fluent.conf`：

```ini
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match *.**>
  @type copy
  <store>
    @type elasticsearch
    host elasticsearch
    port 9200
    logstash_format true
    logstash_prefix docker
    logstash_dateformat %Y%m%d
    include_tag_key true
    tag_key @log_name
    flush_interval 1s
  </store>
  <store>
    @type stdout
  </store>
</match>
```

#### 3. 配置应用容器使用 fluentd 驱动

启动一个测试容器，指定日志驱动为 `fluentd`：

```bash
docker run -d \
  --log-driver=fluentd \
  --log-opt fluentd-address=localhost:24224 \
  --log-opt tag=nginx-test \
  --name nginx-test \
  nginx
```
**注意**：确保 `fluentd` 容器已经启动并监听在 `localhost:24224`。在生产环境中，如果你是在不同机器上，需要将 `localhost` 替换为运行 fluentd 的主机 IP。

#### 4. 在 Kibana 中查看日志

1. 访问 `http://localhost:5601`。
2. 进入 **Management**->**Kibana**->**Index Patterns**。
3. 创建新的 Index Pattern，输入 `docker-*` (我们在 fluent.conf 中配置的前缀)。
4. 选择 `@timestamp` 作为时间字段。
5. 去 **Discover** 页面，你就能看到 Nginx 容器的日志了。

#### Kibana 建索引模式常见坑

首次接入 EFK/ELK 时，“Elasticsearch 有数据但 Kibana 看不到”很常见，通常是 Kibana 配置或时间窗口问题：

* **Index Pattern 不匹配**：确认 Kibana 的 Index Pattern 与实际索引前缀一致。可以先用 `_cat/indices` 查看真实索引名。
* **时间字段选择错误**：若索引里包含 `@timestamp`，一般选择它；如果选择了错误的字段，会导致 Discover 无法按时间筛选。
* **时间窗口/时区**：Discover 右上角的时间范围默认可能是最近 15 分钟，且时区可能影响显示。建议先把范围扩大到最近 24 小时再验证。
* **数据解析失败**：若日志是非结构化文本，仍可入库但字段不可用；生产环境建议输出 JSON 并在采集端解析。

#### 5. 验证日志是否写入 Elasticsearch：生产排错必备

当你在 Kibana 看不到日志时，建议先跳过 UI，从存储端直接验证“日志是否入库”。

1. 查看索引是否创建：

   ```bash
   curl -s http://localhost:9200/_cat/indices?v
   ```

   如果 Fluentd 使用了 `logstash_format true` 且 `logstash_prefix docker`，通常会看到形如 `docker-YYYY.MM.DD` 的索引。

2. 查看最近一段时间的日志文档：

   ```bash
   curl -s -H 'Content-Type: application/json' \
     http://localhost:9200/docker-*/_search \
     -d '{"size":1,"sort":[{"@timestamp":"desc"}]}'
   ```

如果 Elasticsearch 中已经有文档，但 Kibana 仍然为空，常见原因是：

* Index Pattern 没匹配到索引 (例如写成了 `docker-*` 但实际索引前缀不同)。
* 时间字段没选对或时区不一致，导致 Discover 时间窗口内看不到数据。

### 19.2.3 总结

通过 Docker 的日志驱动机制，结合 ELK/EFK 强大的收集和分析能力，我们可以轻松构建一个能够处理海量日志的监控平台，这对于排查生产问题至关重要。

### 19.2.4 生产要点

在生产环境中，日志系统往往比监控系统更容易因为“容量与写入压力”出问题，建议特别关注：

* **容量规划**：日志增长速度与磁盘占用直接相关。建议设置日志保留周期与索引生命周期策略 (ILM)，避免 Elasticsearch 因磁盘水位触发只读或不可用。
* **资源配置**：Elasticsearch 对 JVM Heap 较敏感。除示例中的 `ES_JAVA_OPTS` 外，生产环境需要结合节点内存、分片规模、查询压力做评估。
* **链路可靠性**：采集端到存储端要考虑网络抖动、背压与重试策略；当 Elasticsearch 写入变慢时，采集端的缓冲与落盘策略决定了是否会丢日志。
* **日志格式**：推荐应用输出结构化日志 (JSON) 并包含关键字段 (如 `trace_id`、`request_id`、`service`、`env`)，以便快速过滤与关联分析。

#### 索引与保留策略的落地建议

无论是 EFK 还是 ELK，生产上都需要回答两个问题：

* 日志保留多久？
* 保留期内的日志如何保证可查询、不过度占用存储？

建议按环境与业务重要性对日志分层，并制定不同的保留周期，例如：

* **生产环境**：7～30 天
* **测试环境**：1～7 天

实现方式通常有两类：

* **按天滚动索引**：如 `docker-YYYY.MM.DD`，再定期删除过期索引。
* **使用 ILM**：定义 Hot/Warm/Cold/删除阶段，按时间与容量自动滚动与回收。

对于中小规模集群，先把“按天滚动 + 过期删除”做扎实，往往就能解决 80% 的容量问题；当日志量上来、查询压力变大后，再逐步引入 ILM、分层存储与更精细的分片规划。

#### 最小可用的“过期索引清理”示例

如果你采用按天滚动索引 (例如 `docker-YYYY.MM.DD`)，可以通过 Elasticsearch API 定期清理过期索引。

下面示例仅用于演示思路：获取所有 `docker-` 前缀索引并删除指定索引。生产环境建议基于日期计算、灰度验证与权限控制后再执行自动化清理。

1. 列出索引：

   ```bash
   curl -s http://localhost:9200/_cat/indices/docker-*?v
   ```

2. 删除某个过期索引 (示例)：

   ```bash
   curl -X DELETE http://localhost:9200/docker-2026.02.01
   ```

如果你希望更自动化的治理能力，可以进一步使用 ILM 为索引配置滚动与删除策略。
