4. 日志存储:Elasticsearch集群搭建、索引生命周期管理与冷热数据分层
日志存不住,审计就是空谈。我见过太多团队把日志往一个单节点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 整体架构一览
说了这么多,我画了一张图帮你把整个体系串起来。从日志采集到最终删除,每一步都有对应的组件和策略。
这张图把整个流程串起来了。日志从采集层进入热节点,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审计的查询需求。记住,存储是地基,地基不稳,上面的一切都是空中楼阁。