资讯动态

Elasticsearch索引模板与ILM生命周期策略实战:从配置到故障排查

发布时间:2026/10/15 20:08:00 来源:尧图企业网站定制
1. 集群搭完只是开始索引治理才是接下来的重头戏上一篇我们把7.x集群的节点角色、分片分配、脑裂参数这些基础设施都捋顺了集群状态也green了当时心情确实轻松。但这种轻松感维持不了太久——真实生产环境马上会给你上第二课业务开始接进来日志索引每天几十GB地涨新索引的mapping被动态映射搞出一堆莫名其妙的字段老索引只进不出磁盘使用率一路飙到85%以上最后集群因为watermark阈值开始只读写请求全部失败。这第二课的核心就两件事索引模板index template和生命周期策略index lifecycle management也就是ILM。template管的是新索引一出生长什么样ILM管的是索引从生到死按什么节奏走完一生。两者单独用都差点意思配合起来才能形成完整的索引治理闭环。这篇不搞高深理论所有配置都是能直接抄的附带我在实际运维里踩过的坑和排查思路。2. 生命周期策略 ILM四阶段模型与关键参数怎么定2.1 hot/warm/cold/delete 各阶段该干什么ILM把索引生命周期分成hot、warm、cold、frozen、delete几个阶段7.x常规场景用不到frozen掌握hot、warm、cold、delete这四个就够用。每个阶段职责完全不同hot主写入阶段索引承载实时读写通常跑在性能最好的节点上。这个阶段的核心动作是rollover防止单个索引无限膨胀。warm索引已经不再写入但还有比较高的查询频次。这个阶段适合做force merge合并段、收缩分片shrink也可以把副本数调低或者把索引迁移到配置稍低的节点。cold只是偶尔查一查甚至很少查的数据。进一步降低资源占用可以关副本、关掉索引释放内存配合冷节点存储。delete过了保留期限物理删除数据把磁盘空间还给集群。阶段之间的迁移参数核心就两个维度一个是时间min_age一个是索引大小或文档数rollover条件。我个人的习惯是hot阶段用rollover控制单个索引体量warm和cold阶段用min_age控制天数delete阶段用min_age控制总保留时长。不要把时间维度全部堆在rollover上也不要在warm里再靠rollover切索引每层管一件事出问题了也好排查。有一点需要先讲清楚min_age计算的是从索引创建时间算起的年龄不是从上一个阶段迁完开始算。这个特性决定了你要rollover配合使用因为每次rollover会生成一个全新索引新索引的创建时间就是rollover那一刻时间轴自然重新起算。如果没配rollover一个索引存活50天你把warm的min_age设成30天那它永远不会等到第30天才被管理——因为它已经50天了策略一套上就会被判定超过min_age直接往后面的阶段走中间阶段等于被跳过了。这类跳跃现象我们后面排查部分再细说。2.2 rollover 触发条件与分片大小预估rollover是ILM里最容易配置错的一环。它的工作原理是先给索引指定一个写入别名write alias当索引满足你设置的条件时ILM自动创建新索引并把写入别名切到新索引上老索引自然转为只读并被后续阶段接管。配置rollover时要注意一个关键点多个条件之间是任意满足即触发不是全部满足才触发。比如你同时设了max_size 50GB和max_age 30d那么索引只要到30天哪怕只有5GB也会被切走。很多团队以为要大小和时间都满足才滚结果发现索引一天一切分片碎得没法看。实际生产里我建议hot阶段只用一个主条件控制比如以max_size为主或者干脆用max_primary_shard_size按主分片大小判断再配合一个宽松的max_age做兜底。分片大小和数量的预估可以按这个思路粗略算官方建议单分片控制在30GB到50GB左右如果你每天产生200GB数据按单分片40GB算每天至少需要5个分片。还要考虑单分片的写入吞吐单分片每秒能扛住几千到上万文档写入写入热点场景下要先看单分片TPS是否成为瓶颈再决定是横向加shard还是分多个索引并行写。我见过一个系统索引分了30个分片却只有一个节点在写分片多不但没有提升性能反而因为每个分片都要刷盘和做副本复制把小机器直接拖垮。分片数不是越大越好节点数、索引大小、写入模式三方面要一起算。2.3 一套能直接抄的日志场景ILM配置下面这套配置是我在日志场景里用得最多的模板基本逻辑hot阶段到50GB或1天就滚动warm阶段2天后合并段并缩成1个分片cold阶段30天后关副本delete阶段90天后删除。PUT _ilm/policy/logs-rollover-policy { policy: { phases: { hot: { actions: { rollover: { max_size: 50gb, max_age: 1d, max_primary_shard_size: 40gb }, set_priority: { priority: 100 } } }, warm: { min_age: 2d, actions: { forcemerge: { max_num_segments: 1 }, shrink: { number_of_shards: 1 }, allocate: { number_of_replicas: 1 }, set_priority: { priority: 50 } } }, cold: { min_age: 30d, actions: { allocate: { number_of_replicas: 0 }, set_priority: { priority: 0 } } }, delete: { min_age: 90d, actions: { delete: {} } } } } }几个参数的取舍warm阶段的shrink缩分片时目标分片数必须是原分片数的因子。如果原索引3个分片可以缩成1个因为3能被1整除如果原索引5个分片能缩成1或5缩成3会直接报错。所以你在hot阶段定的分片数最好提前想好后面要不要shrink。force merge的max_num_segments设1是把每个分片合并成一个大段查询性能提升明显但合并过程很吃IO极端情况会出现写入短暂抖动所以一般放在warm阶段而不是hot阶段执行。delete阶段不需要其他参数触发就物理删。3. 索引模板新老两代模板怎么选、怎么写3.1 新版可组合模板和老版模板的差别7.x同时支持两套模板老版_template和新版_index_template。老版模板用起来简单一个索引名匹配到多个模板时按模板先后顺序后面的覆盖前面的没有明确的优先级控制。新版是composable template也就是可组合模板底层拆成多个component template通过一个组合模板把它们叠加在一起支持显式priority优先级排序。新项目我的建议很直接一律用_index_template别再用老模板。原因不只是新模板功能更强而是它解决了老模板最要命的覆盖顺序靠运气问题。新版模板里优先级高的覆盖优先级低的优先级相同则按通配符匹配程度决定行为可预期。更关键的是新版模板把settings、mappings、aliases、ILM策略、data stream配置组合在一起一套模板就能把索引的出身定义完整。如果你是从老版本升上来的集群老模板还挂在_template里新索引同时匹配到新旧两套模板时规则是新模板的priority和组合逻辑优先但老模板没有被自动纳入。迁移很简单把老模板的内容拆解成component template再建一个组合模板验证新索引行为无误后删掉老模板。不要直接删先让新旧并行跑一两个周期对比mapping和settings是否一致。3.2 mapping 与 settings 里的关键坑模板里最容易翻车的是mapping部分。默认情况下ES开启动态映射日志里随便带个request_id_123456如果之前没见过这个字段ES会按自己的猜测给你映射一个类型字段一旦映射错后面想改就难了。生产环境我建议mapping里至少配置dynamic: false允许写入但忽略未声明字段如果字段要求严格直接dynamic: strict未映射字段写入直接报错。这样做的代价是新日志格式上线时需要同步改模板加字段但能避免线上出现一整堆垃圾字段。settings部分有几个参数值得在模板里提前固定number_of_shards和number_of_replicas提前定好索引创建后再改shards只能靠reindex代价很大。refresh_interval日志写入场景默认1s刷新过于频繁可以调到5s甚至30s对写入吞吐有明显改善查询可以走?refreshtrue强制实时。index.lifecycle.name和index.lifecycle.rollover_alias把ILM策略直接挂在模板的settings里新索引一出生就自动受策略管理不用手工给每个索引单独设置。下面是一个带mapping和alias的完整组合模板示例PUT _index_template/my-logs-template { index_patterns: [my-logs-*], priority: 200, template: { settings: { number_of_shards: 3, number_of_replicas: 1, refresh_interval: 5s, index.lifecycle.name: logs-rollover-policy, index.lifecycle.rollover_alias: my-logs-write }, mappings: { dynamic: false, properties: { timestamp: { type: date }, level: { type: keyword }, message: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } }, host: { type: keyword }, request_time_ms: { type: integer } } }, aliases: { my-logs-write: { is_write_index: true } } } }这里把my-logs-write这个别名通过模板写到每个新索引上并标记为write index。rollover发生时ILM会把这个write标记从老索引摘掉、挂到新索引上老索引只剩读别名。这个模式我在多个集群里跑过稳定可靠。3.3 data stream 是模板ILM的另一种组合方式如果你用的ES版本在7.9以上并且场景是时序数据日志、指标、审计我更推荐直接用data stream。data stream本质上是把索引模板ILM自动rollover统一别名打包成了一个概念对外只有一个数据流名字你只管往里写ES自动在背后创建backing index索引名形如.ds-my-logs-2024.01.15-000001。data stream和普通模板加alias方案的主要区别普通方案里rollover别名要自己维护、老索引要留读别名做查询data stream把这些全部封装了读和写都走数据流名backing index的创建和rollover全部自动。对业务方来说感知不到底层索引的存在这对接日志采集尤其友好。用data stream时模板里加一行data_stream: {}settings里配置index.lifecycle.name即可不需要再配rollover_aliasPUT _index_template/my-logs-template { index_patterns: [my-logs-*], data_stream: {}, priority: 200, template: { settings: { number_of_shards: 3, number_of_replicas: 1, index.lifecycle.name: logs-rollover-policy }, mappings: { dynamic: false, properties: { timestamp: { type: date }, level: { type: keyword }, message: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } }, host: { type: keyword }, request_time_ms: { type: integer } } } } }data stream有一个前提mapping里必须包含一个date类型字段作为时间戳通常就是timestamp否则创建索引写入会报错。如果你的采集端用的就是标准timestamp字段基本零成本切换。如果因为历史原因没法升级版本或者业务端统一下游工具不支持data stream语义老方案alias加ILM也完全够用不用强行上数据流。4. 完整落地在7.x集群里把模板、策略、数据流串起来4.1 前置条件与节点分层动手之前先确认集群满足几个条件节点都跑在7.x且版本一致有一个数据节点作为hot节点如果要区分warm/cold节点需要给节点打自定义属性标签比如在elasticsearch.yml里配置node.attr.box_type: warm然后在ILM策略的allocate动作里用require: {box_type: warm}指定迁移目标。没有标签也没关系所有数据节点一视同仁阶段之间的动作依然会执行只是没有节点亲和性区分。ILM本身不需要额外的独立组件策略存在集群状态里master节点负责调度数据节点执行动作。需要确认的是indices.lifecycle.poll_interval默认是10分钟。这意味着你新建策略或者索引刚满足rollover条件最坏要等一个轮询周期才会看到动作。测试环境我习惯把它调小到10秒便于快速验证# 调小 ILM 轮询间隔仅限测试环境使用 curl -X PUT localhost:9200/_cluster/settings -H Content-Type: application/json -d { transient: { indices.lifecycle.poll_interval: 10s } }生产环境不要动这个参数10分钟的轮询对集群压力很小频繁轮询反而增加master负担。4.2 实操步骤一创建ILM策略先用上一节里的策略体创建ILM注意先创建策略再创建模板顺序不能反。如果模板已经引用了不存在的策略索引创建时不会报错但ILM元数据里会记录缺失索引会处于无人管理的状态等到你补上同名策略后才会接管。curl -X PUT localhost:9200/_ilm/policy/logs-rollover-policy -H Content-Type: application/json -d { policy: { phases: { hot: { actions: { rollover: { max_size: 50gb, max_age: 1d }, set_priority: { priority: 100 } } }, warm: { min_age: 2d, actions: { forcemerge: { max_num_segments: 1 }, shrink: { number_of_shards: 1 }, allocate: { number_of_replicas: 1 } } }, delete: { min_age: 90d, actions: { delete: {} } } } } } 返回{acknowledged:true}说明策略创建成功如果有语法错误例如时间单位拼错、min_age设置成负数请求会直接返回400把错误信息里提到的字段改掉重试即可。4.3 实操步骤二创建索引模板接着创建组合模板。这里以普通alias方案演示因为这种方案兼容性最好不依赖data stream特性很多现存7.x集群用的都是这个模式curl -X PUT localhost:9200/_index_template/my-logs-template -H Content-Type: application/json -d { index_patterns: [my-logs-*], priority: 200, template: { settings: { number_of_shards: 3, number_of_replicas: 1, refresh_interval: 5s, index.lifecycle.name: logs-rollover-policy, index.lifecycle.rollover_alias: my-logs-write }, mappings: { dynamic: false, properties: { timestamp: { type: date }, level: { type: keyword }, message: { type: text }, host: { type: keyword }, request_time_ms: { type: integer } } }, aliases: { my-logs-write: { is_write_index: true } } } } 模板创建后先手动创建一个起始索引让模板生效并自动应用策略curl -X PUT localhost:9200/my-logs-000001这个索引创建完你应该能在返回结果里看到它带着模板里的settings和mapping并且自动带上了my-logs-write这个写别名。4.4 实操步骤三写入数据并验证策略生效写入一批测试数据模拟一天的真实写入curl -X POST localhost:9200/my-logs-write/_bulk -H Content-Type: application/json -d {index:{}} {timestamp: 2024-01-15T10:00:00Z, level: INFO, message: user login success, host: web-01, request_time_ms: 23} {index:{}} {timestamp: 2024-01-15T10:00:01Z, level: WARN, message: slow query detected, host: web-02, request_time_ms: 812} {index:{}} {timestamp: 2024-01-15T10:00:02Z, level: ERROR, message: database connection timeout, host: web-01, request_time_ms: 3005} {index:{}} {timestamp: 2024-01-15T10:00:03Z, level: INFO, message: order created, host: app-01, request_time_ms: 45} 写入之后最直接的验证方式是查看ILM对索引的解释信息curl -X GET localhost:9200/my-logs-000001/_ilm/explain正常响应中包含关键字段{ indices: { my-logs-000001: { index: my-logs-000001, managed: true, policy: logs-rollover-policy, phase: hot, action: rollover, step: check-rollover-ready, age: 1.3d } } }managed为true说明索引确实在被策略管理phase、action、step显示当前进度。测试环境如果想让rollover立刻触发可以先把写入别名指到一个临时的小配置或者干脆临时把策略里的max_size调小验证完再调回来。注意测试完成后把indices.lifecycle.poll_interval恢复默认值。5. 常见故障与排查思路都是真实踩过的坑5.1 索引根本不受策略管理explain不到任何记录症状_ilm/explain返回空或者返回的结果里managed: false。排查思路按顺序走先查模板有没有给这个索引设置index.lifecycle.name。如果索引创建时模板里没有这个配置后续即使你手动补了模板已经创建的索引也不会自动套上策略因为模板只对新索引生效。这种情况只能手动补设置curl -X PUT localhost:9200/my-logs-000001/_settings -H Content-Type: application/json -d { index.lifecycle.name: logs-rollover-policy, index.lifecycle.rollover_alias: my-logs-write }还有一种常见情况索引用的是data stream模式但模板里忘了加data_stream: {}导致data stream创建失败backing index没有被自动管理。检查时会看到索引名是正常的业务名而不是.ds-开头这个时候删掉索引修正模板让data stream重新生成即可。5.2 索引一直卡在hot不往warm走先说一个容易被误诊的点min_age超过之后phase迁移是分步骤的每一步之间要等下一个poll周期所以明明到时间了却不动可能只是还没轮到。等10-15分钟再看。排除了轮询优先考虑是不是warm阶段的action执行失败。可以用_ilm/explain看step_info字段里面积压的错误信息一般写得很明确比如shrink报错cannot shrink number_of_shards from 3 to 2说明目标分片数不是原分片数的因子。forcemerge阶段如果索引不是只读状态也会失败ILM会先自动设置写阻塞但如果你手动改过index.blocks.write配置可能造成冲突。另一个高发原因hadoop这种有老旧索引跨阶段跳跃的场景索引创建时间太早策略一挂上就直接跳到deletehot和warm直接被跳过看起来就像不受管理。这个不是故障是min_age按创建时间计算的正常表现你在设计策略时就要考虑存量索引的情况新建策略给一个较大的初始min_age或者干脆让它自然过期删除。5.3 rollover 不触发或频繁触发rollover不触发最常见原因有三个一是别名没设is_write_indexILM在rollover动作里找不到明确的write index二是index.lifecycle.rollover_alias配置的别名在索引上不存在_ilm/explain里会直接报错rollover alias does not point to any index三是索引的index.lifecycle.indexing_complete被手动设置成了true这个参数一旦为trueILM认为索引已经完成写入不会再执行rollover。解决方式就是逐项确认查索引的alias和settings确认is_write_index是true且只指向一个索引别名的名字和模板里rollover_alias完全一致最后indexing_complete保持false或者直接不设置。rollover频繁触发基本是条件配置问题多个rollover条件任一满足的特性被忽略了。我接手过一个业务原来的运维设了max_size: 1gb, max_age: 1d结果每天索引切成十几个全是小分片查询性能差到不行。后来改成只留max_primary_shard_size一个主条件后索引数量回归正常。5.4 模板改了新索引却不按模板来模板确实更新成功但新索引行为还是旧的这种问题十有八九是index_patterns匹配顺序和priority在打架。比如你有两个模板都匹配到my-logs-*旧模板priority是100新模板priority是50新模板改了mapping却不生效因为低优先级模板的定义会被高优先级覆盖掉。排查时直接看某个索引实际生效的配置curl -X GET localhost:9200/my-logs-000001/_settings?flat_settingstrue curl -X GET localhost:9200/my-logs-000001/_mapping对比实际配置和模板配置就知道是被哪个模板覆盖了。再多提一句模板只影响创建时刻索引创建后改模板不会对存量索引产生任何修改想改存量索引的mapping只能reindex改settings可以直接用_settings接口。5.5 forcemerge 报错、delete 阶段不删除forcemerge报错需要区分两种情况手工执行force merge时索引需要先设为只读否则ES会拒绝合并操作。ILM内部执行forcemerge前会自动设置只读所以它报错时多半是索引里有未分配的分片集群健康状态非green。建议先查分片分配情况把未分配分片恢复后再让ILM重试。delete阶段不删除先看阶段时间配置delete的min_age要大于前面所有阶段min_age否则时间到了前面的阶段还没动作delete也排不上。还有一种情况是策略在delete动作上被卡住_ilm/explain里会看到step: wait-for-delete之类的状态通常是因为集群处于red状态或者索引删不掉。手动删掉这个索引或者等集群恢复后重新触发。所有阶段动作失败后ILM默认会进行重试。如果你确认问题已经修好不想等下一次轮询可以主动重试curl -X POST localhost:9200/my-logs-000001/_ilm/retry6. 我的几点实操体会从零开始搭过好几套带完整索引治理的集群之后我的建议始终是策略和模板上线前一定要用一小段时间的真实流量先验证别一上来就把全量业务切进去。我吃过一次亏上线当天就遇到磁盘水位告警最后发现是模板里lefecycle策略的delete阶段min_age写错存量索引全被卡在hot阶段新索引又一直在rollover分片数爆掉。后来养成了每次调整都先建一个小范围的模拟项目X用批量脚本灌几天数据观察完整生命周期跑完再放量。还有两个小习惯很值得保留一是所有策略和模板的修改都走配置管理工具留档每次变更记录做了什么、为什么改后面出问题能快速回滚二是在测试环境把indices.lifecycle.poll_interval调小验证完务必恢复这个参数在生产环境真的很影响排障体验默认10分钟会让你在调试时产生一种为什么没反应的错觉。索引治理这套东西不复杂但细节多希望这篇能帮你少踩几个坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑