4. 日志存储

日志存不住,审计就是空谈。我见过太多团队把日志往一个单节点ES里塞,结果磁盘打满、查询超时、索引乱成一锅粥。说白了,存储层设计得好不好,直接决定了你能否在事故发生后快速翻出那根“救命稻草”。

这一章,我们聚焦三个核心问题:集群怎么搭才稳、索引怎么管才不乱、冷热数据怎么分才省钱。我会把我在生产环境里踩过的坑和沉淀下来的套路,一并讲清楚。

4.1 集群搭建:别让单点成为你的噩梦

API审计日志的写入量通常很猛。一个中等规模的服务,每天可能产生几十GB甚至上百GB的日志。单节点ES扛不住的,你想想看,一旦节点挂了,所有日志查询都瘫痪,审计回溯直接抓瞎。

我个人习惯至少部署3个节点的集群。为什么是3?因为ES的选举机制需要多数派,3节点可以容忍1个节点宕机而不丢失数据。如果你只有2个节点,脑裂风险会很高。

生产环境最低配置建议:

  • 节点数:3个(master eligible + data 混合节点)
  • CPU:8核以上
  • 内存:32GB(ES堆内存设为16GB,留一半给操作系统做文件缓存)
  • 磁盘:SSD(日志写入是IO密集型,机械盘扛不住)

搭建时,有几个关键配置我建议你死记住:

# elasticsearch.yml 核心配置
cluster.name: api-audit-cluster
node.name: node-1
path.data: /data/elasticsearch/data
path.logs: /data/elasticsearch/logs
network.host: 0.0.0.0
discovery.seed_hosts: ["192.168.1.10", "192.168.1.11", "192.168.1.12"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]

# 防止脑裂
discovery.zen.minimum_master_nodes: 2

我曾经在一个项目里,因为忘记设置 discovery.zen.minimum_master_nodes,导致网络抖动时集群分裂成两个小集群,各自写入数据,最后合并时数据乱成一团。嗯,那晚的教训很深刻。

避坑指南:千万不要把 path.data 和 path.logs 放在同一个磁盘分区。日志写满磁盘会导致ES强制关闭索引,数据可能损坏。我曾经见过有人把数据和日志都放在系统盘,结果系统日志把磁盘撑爆,ES直接罢工。

4.2 索引生命周期管理:让日志自动“退休”

日志是越存越多的,但你不能让它无限膨胀。一个索引里塞了几十亿条文档,查询慢得像蜗牛爬。这时候就需要索引生命周期管理(ILM)登场了。

ILM的核心思想很简单:给索引定个“人生规划”。刚写入时是“热数据”,频繁查询;过几天变成“温数据”,偶尔查查;再久一点变成“冷数据”,几乎不查但得留着合规;最后到期删除。

我一般这样设计API审计日志的ILM策略:

阶段 时间 动作 说明
hot 0-1天 rollover(滚动) 每50GB或每1小时滚动一次,保持索引大小可控
warm 1-7天 force merge + shrink 合并段、缩减分片数,降低存储和内存开销
cold 7-30天 freeze + 迁移到冷节点 冻结索引,释放内存,只保留磁盘数据
delete 30天后 删除索引 合规保留期结束,彻底清理

对应的ILM策略配置长这样:

PUT _ilm/policy/api-audit-ilm-policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "1h"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "1d",
        "actions": {
          "forcemerge": {
            "max_num_segments": 1
          },
          "shrink": {
            "number_of_shards": 1
          },
          "allocate": {
            "require": {
              "data_type": "warm"
            }
          }
        }
      },
      "cold": {
        "min_age": "7d",
        "actions": {
          "freeze": {},
          "allocate": {
            "require": {
              "data_type": "cold"
            }
          }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

小技巧:rollover的触发条件我建议用 max_size 为主、max_age 为辅。因为日志流量可能有波动,按大小滚动能保证每个索引的“体重”差不多,查询性能更稳定。按时间滚动是兜底策略,防止流量太低时索引一直不滚动。

4.3 冷热数据分层:把钱花在刀刃上

你想想看,SSD多贵啊。如果所有日志都放在SSD上,成本会高得离谱。但最近几天的日志又确实需要快速查询,怎么办?冷热数据分层就是干这个的。

简单说,就是让“热节点”用SSD,“冷节点”用HDD。ILM策略会自动把数据从热节点迁移到冷节点。用户查询时,ES会透明地路由到正确的节点,你不需要手动干预。

节点角色配置如下:

# 热节点配置(hot-data-node.yml)
node.roles: [ data_hot, data_content, ingest ]
node.attr.data_type: hot

# 冷节点配置(cold-data-node.yml)
node.roles: [ data_cold, data_content ]
node.attr.data_type: cold

然后在索引模板里指定路由规则:

PUT _index_template/api-audit-template
{
  "index_patterns": ["api-audit-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "index.lifecycle.name": "api-audit-ilm-policy",
      "index.routing.allocation.require.data_type": "hot"
    }
  }
}

这里有个关键点:index.routing.allocation.require.data_type 设置为 hot,保证新创建的索引一开始就落在热节点上。等ILM策略执行到warm或cold阶段时,ES会自动把索引迁移到对应的节点上。

避坑指南:我曾经犯过一个错误——冷节点和热节点的磁盘容量没规划好。热节点SSD只有2TB,冷节点HDD有20TB。结果热节点很快写满,ILM还没来得及迁移,整个集群就进入了只读模式。建议热节点容量至少能容纳3天的日志量,给ILM迁移留出缓冲时间。

4.4 整体架构一览

说了这么多,我画了一张图帮你把整个体系串起来。从日志采集到最终删除,每一步都有对应的组件和策略。

API审计日志存储架构总览 日志采集(Filebeat/Logstash) 热节点(SSD,hot) ILM策略自动迁移 温节点(SSD/HDD,warm) 冷节点(HDD,cold) 删除(delete,30天后) 关键说明 • 热节点:3天内的日志,SSD存储 • 温节点:3-7天的日志,force merge • 冷节点:7-30天的日志,冻结索引 • 删除:超过30天,合规保留期满 • 分片数:热节点3分片,温节点缩为1 • 副本数:热节点1副本,冷节点可降为0 • 查询路由:ES自动选择对应节点 • 用户无感知,无需手动迁移

这张图把整个流程串起来了。日志从采集层进入热节点,ILM策略像时钟一样自动推动索引从热到温、从温到冷,最后到期删除。你只需要在搭建时配置好节点角色和ILM策略,剩下的交给ES自己处理。

4.5 实战中的几个关键检查点

最后,我总结几个你在部署时必须检查的点。这些都是我在生产环境里用血泪换来的经验:

  • 检查磁盘水位线:ES默认在磁盘使用率达到95%时禁止写入,85%时开始迁移分片。建议把警戒线调低一点,比如90%就触发告警,给自己留出处理时间。
  • 检查ILM是否生效:用 GET _ilm/status 查看ILM运行状态。我见过有人配置了ILM但忘记在索引模板里引用,结果索引一直留在热节点上,直到磁盘爆满。
  • 检查冷节点是否真的“冷”:冷节点上的索引应该被冻结(freeze),冻结后索引不再占用堆内存。用 GET _cat/indices?v 查看索引状态,确认冻结索引的 pri.store.size 远小于 store.size。
  • 检查滚动是否正常:用 GET _cat/indices/api-audit-*?s=index 查看索引列表,确认索引按预期滚动,没有出现单个索引过大或长时间不滚动的情况。

一个实用脚本:我习惯写一个cron job,每天凌晨检查一次集群健康状态和磁盘使用率。脚本里会调用 GET _cluster/health 和 GET _cat/allocation?v,如果发现异常就发告警到钉钉或Slack。别等到用户说“查不到日志”了才去排查,那时候已经晚了。

好了,这一章的内容就到这里。集群搭建、ILM策略、冷热分层,这三板斧用好了,你的日志存储体系就能稳稳地支撑起API审计的查询需求。记住,存储是地基,地基不稳,上面的一切都是空中楼阁。