05 ‐ Procedimentos ILM - BktechBrazil/elastic-eck GitHub Wiki
O ILM (Index Lifecycle Management) é o mecanismo nativo do Elasticsearch para automatizar o gerenciamento do ciclo de vida dos índices. Em vez de gerenciar manualmente quando um índice deve ser movido para um hardware mais barato ou quando deve ser deletado, o ILM faz isso de forma automática com base em regras chamadas policies.
O ILM é especialmente útil em cenários de dados temporais — logs, métricas, eventos de segurança — onde os dados perdem valor com o tempo e precisam ser retidos por um período determinado antes de serem descartados.
Antes do ILM existir, o problema de gerenciar o ciclo de vida de índices era resolvido de três formas:
- Scripts cron manuais: alguém escrevia um script shell que rodava toda madrugada, verificava o tamanho ou a idade dos índices e executava operações via API. Frágil, difícil de auditar, sem visibilidade.
- Curator: uma ferramenta open source mantida pela própria Elastic que funcionava como um agendador de tarefas para índices. Melhor que scripts manuais, mas ainda era uma ferramenta externa que precisava ser instalada, configurada e monitorada separadamente.
- Rollover manual: equipes criavam novos índices manualmente toda semana ou todo mês, atualizavam aliases e deletavam os antigos. Com volume alto, isso virava um processo operacional relevante.
O ILM foi introduzido na versão 6.6 como feature X-Pack paga, e passou a ser gratuito na versão 7.x. A motivação era clara: a Elastic observava que uma grande parte dos clusters em produção tinha problemas de gestão de disco causados justamente pela falta de automação. O ILM resolveu isso de forma nativa, com visibilidade pelo Kibana e sem dependência de ferramentas externas.
Sem o ILM, é comum que clusters acumulem índices antigos indefinidamente, consumindo disco e degradando o desempenho. Gerenciar isso manualmente é trabalhoso e propenso a erros. O ILM resolve isso com automação baseada em políticas declarativas.
Uma ILM policy é composta por fases, cada uma com suas próprias condições de entrada e ações a executar. As fases disponíveis são:
Fonte: Madhusudhan Konda — Elasticsearch in Action, 2nd Edition
| Fase | Quando ocorre | Ações típicas |
|---|---|---|
| Hot | Fase inicial — o índice está sendo ativamente escrito e lido | Rollover por tamanho, idade ou número de documentos |
| Warm | O índice não recebe mais escritas, mas ainda é consultado com frequência | Force merge, shrink, readonly, realocação para nodes warm |
| Cold | Dados raramente consultados | Realocação para nodes cold, searchable snapshots |
| Frozen | Dados muito raramente acessados | Searchable snapshots em object storage (S3, GCS) |
| Delete | Dados que não precisam mais ser retidos | Deleção do índice |
Não é obrigatório usar todas as fases — uma policy pode ter apenas hot e delete, por exemplo.
Cada fase suporta um conjunto específico de actions. Conhecer o que está disponível em cada fase é essencial para desenhar uma policy adequada ao seu caso de uso.
Fase Hot:
| Action | O que faz |
|---|---|
rollover |
Cria um novo índice quando o atual atinge o critério configurado (tamanho, idade ou número de documentos). É a action central da fase hot. |
set_priority |
Define a prioridade de recuperação do índice após uma falha de node. Índices com prioridade maior são recuperados primeiro. |
readonly |
Bloqueia novas escritas no índice — útil quando o rollover já aconteceu mas você quer garantir imutabilidade. |
downsample |
Reduz a granularidade de dados de série temporal, agregando múltiplos pontos em um único ponto. Útil para métricas — mantém o dado mas ocupa muito menos espaço. |
Fase Warm:
| Action | O que faz |
|---|---|
forcemerge |
Compacta todos os segments do índice em um único segment. Melhora a performance de leitura e libera o espaço de documentos deletados. |
shrink |
Reduz o número de primary shards do índice. Exige que o índice esteja em read-only e que todos os shards estejam no mesmo node. |
readonly |
Bloqueia novas escritas. |
set_priority |
Define prioridade de recuperação — geralmente menor que na fase hot. |
allocate |
Controla em quais nodes o índice pode ser alocado e permite reduzir o número de réplicas. |
migrate |
Move o índice para o tier warm automaticamente, atualizando o _tier_preference. Injetada automaticamente pelo ILM nas fases warm e cold, a menos que a policy defina a alocação por atributos com allocate. |
Fase Cold:
| Action | O que faz |
|---|---|
allocate |
Reduz réplicas para zero ou move para nodes cold. |
migrate |
Move para o tier cold automaticamente. |
readonly |
Bloqueia escritas. |
searchable_snapshot |
Monta o índice como um searchable snapshot — os dados são movidos para um repositório de snapshots e acessados sob demanda sem ocupar disco local. |
Fase Delete:
| Action | O que faz |
|---|---|
delete |
Deleta o índice. |
wait_for_snapshot |
Aguarda que um SLM (Snapshot Lifecycle Management) tenha executado um snapshot do índice antes de deletar. Garante que os dados foram preservados antes da remoção. |
Licença: ILM, rollover, data streams e as fases hot, warm, cold e delete estão disponíveis na licença Basic (gratuita). A action
searchable_snapshot, usada nas fases cold e frozen, exige licença Enterprise.
O rollover é a action mais importante da fase hot. Ele cria um novo índice quando o índice atual atinge uma condição configurada:
-
max_size: tamanho total dos shards primários -
max_age: tempo desde a criação do índice -
max_docs: número de documentos -
max_primary_shard_size: tamanho do maior shard primário individualmente -
max_primary_shard_docs: número de documentos no maior shard primário
Fonte: Opster — ILM Hot-Warm-Cold Architecture
Quando o rollover acontece, o alias de escrita é movido automaticamente para o novo índice, sem nenhuma interrupção para quem está indexando.
Detalhe importante: o rollover só acontece se o ILM verificar as condições e o índice já tiver pelo menos um documento. Um índice vazio nunca sofre rollover, mesmo que o
max_agetenha sido atingido. Isso evita a criação de índices vazios em série.
O fluxo completo de uma implementação com ILM tem três peças:
ILM Policy → Index Template → Índice (gerenciado automaticamente)
- ILM Policy: define as regras de ciclo de vida
- Index Template: associa a policy a todos os índices que correspondem a um padrão de nome
- Índice inicial: criado com um alias de escrita — a partir daí, o ILM cuida do resto
No ECK, cada tier de dados é um nodeSet com o papel correspondente (data_hot, data_warm, data_cold). Quando um índice entra na fase warm ou cold, a action migrate altera o _tier_preference do índice e o Elasticsearch move fisicamente os shards para os pods daquele tier. O operator não participa dessa movimentação: ele só mantém os pods de cada tier como o manifesto descreve.
Neste capítulo, o cluster lab-es tem um node em cada tier:
| Pod (node) | Tier | Papéis |
|---|---|---|
lab-es-es-default-0 |
Hot |
master, data_hot, data_content, ingest, ml, remote_cluster_client, transform
|
lab-es-es-node2-0 |
Warm | data_warm |
lab-es-es-node3-0 |
Cold | data_cold |
Como cada tier tem um único node, os índices dos laboratórios usam
number_of_replicas: 0. Com réplica, a cópia não teria outro node do mesmo tier para ser alocada e o índice ficariayellow. Em produção, com dois ou mais nodes por tier, mantenha pelo menos 1 réplica nas fases hot e warm.
Nos laboratórios do rollover clássico, você escreve em um alias e o ILM cria os índices -000001, -000002 e assim por diante. Hoje, para dados de série temporal (logs, métricas, eventos), o mecanismo recomendado é o data stream.
Um data stream expõe um nome único para escrita e leitura (por exemplo, lab-logs-app) e, por baixo, mantém uma série de índices de apoio (backing indices), com nomes como .ds-lab-logs-app-2026.09.23-000001, -000002... O rollover cria um novo índice de apoio e passa a escrever nele; os antigos continuam disponíveis para leitura até o ILM deletá-los.
| Alias de rollover | Data stream | |
|---|---|---|
| Nome para escrita | Alias com is_write_index: true
|
O próprio nome do data stream |
| Índice inicial | Criado à mão (-000001) com o alias |
Criado automaticamente no primeiro documento |
| Configuração no template | index.lifecycle.rollover_alias |
"data_stream": {} |
Campo @timestamp
|
Opcional | Obrigatório em todo documento |
| Atualizar ou apagar documentos | Direto pelo alias | Só com _update_by_query / _delete_by_query ou no índice de apoio |
O index template é quem amarra tudo: padrão de nome + data_stream + policy de ILM + mapping.
Os coletores da Elastic (Elastic Agent, Beats, Logstash) gravam em data streams com nomes no formato
logs-*-*emetrics-*-*. Por isso o Elasticsearch já traz index templates prontos para esses padrões, e os índices dos laboratórios usam outros nomes para não colidir com eles.
No terminal da VM:
kubectl -n elastic get elasticsearch lab-es
kubectl -n elastic get pods | grep lab-esResultado esperado: NODES 3, HEALTH green e os pods lab-es-es-default-0, lab-es-es-node2-0 e lab-es-es-node3-0 com 1/1 Running.
Se o lab-es estiver com um único node, crie e aplique o manifesto com os tiers:
mkdir -p ~/eck-lab/manifests
cd ~/eck-lab
cat <<'EOF' > manifests/elasticsearch-tiers.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: lab-es
spec:
version: 9.4.2
nodeSets:
- name: default
count: 1
config:
node.roles: ["master", "data_hot", "data_content", "ingest", "ml", "remote_cluster_client", "transform"]
node.store.allow_mmap: true
node.attr.datacenter: datacenter1
podTemplate:
spec:
containers:
- name: elasticsearch
env:
- name: ES_JAVA_OPTS
value: -Xms2g -Xmx2g
resources:
requests:
memory: 4Gi
cpu: "1"
limits:
memory: 4Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 20Gi
storageClassName: local-path
- name: node2
count: 1
config:
node.roles: ["data_warm"]
node.store.allow_mmap: true
node.attr.datacenter: datacenter1
podTemplate:
spec:
containers:
- name: elasticsearch
env:
- name: ES_JAVA_OPTS
value: -Xms1g -Xmx1g
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 2Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
storageClassName: local-path
- name: node3
count: 1
config:
node.roles: ["data_cold"]
node.store.allow_mmap: true
node.attr.datacenter: datacenter2
podTemplate:
spec:
containers:
- name: elasticsearch
env:
- name: ES_JAVA_OPTS
value: -Xms1g -Xmx1g
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 2Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
storageClassName: local-path
EOF
kubectl apply -n elastic -f manifests/elasticsearch-tiers.yaml
kubectl -n elastic get pods -wResultado esperado: os pods lab-es-es-node2-0 e lab-es-es-node3-0 são criados e o lab-es-es-default-0 é reiniciado. Aguarde os três ficarem 1/1 Running e pressione Ctrl+C. Durante o reinício do lab-es-es-default-0, o Kibana fica indisponível por um ou dois minutos.
PASSWORD=$(kubectl -n elastic get secret lab-es-es-elastic-user \
-o go-template='{{.data.elastic | base64decode}}')
echo "Senha do elastic: $PASSWORD"
pkill -f "port-forward.*5601" || true
nohup kubectl -n elastic port-forward --address 0.0.0.0 service/lab-kb-kb-http 5601 > /tmp/pf-kibana.log 2>&1 &
sleep 2
cat /tmp/pf-kibana.logResultado esperado: a senha do usuário elastic e a mensagem Forwarding from 0.0.0.0:5601 -> 5601.
O túnel fica ligado ao pod do Kibana que estava rodando quando ele foi aberto. Se esse pod for reiniciado ou recriado, o túnel cai e o Kibana deixa de responder no navegador: execute este bloco novamente.
No navegador, abra https://<IP_DA_VM>:5601, faça login com o usuário elastic e abra o Console (Dev Tools). Confira os papéis dos nodes:
GET _cat/nodes?v&h=name,node.roleO lab-es-es-default-0 deve aparecer com os papéis hilmrst, o lab-es-es-node2-0 com w (warm) e o lab-es-es-node3-0 com c (cold).
Por padrão, o ILM verifica as condições das policies a cada 10 minutos. Para os laboratórios, vamos reduzir para 10 segundos:
PUT /_cluster/settings
{
"persistent": {
"indices.lifecycle.poll_interval": "10s"
}
}Esse valor vale para todos os laboratórios deste capítulo e é restaurado no final, na seção de volta para um node.
Nota sobre os timers dos laboratórios: os tempos configurados (segundos e minutos) são propositalmente curtos para que as transições entre fases sejam observáveis durante a aula. Em produção, os valores típicos são dias ou semanas.
Neste laboratório vamos configurar um ciclo de vida completo para um conjunto de dados de vendas. O índice será gerenciado automaticamente pelo ILM, com rollover na fase hot, migração para o tier warm e deleção após um período configurado.
Todos os comandos devem ser executados no Console do Kibana.
PUT /_ilm/policy/policy-vendas
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "1m",
"max_docs": 5
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "2m",
"actions": {
"forcemerge": {
"max_num_segments": 1
},
"set_priority": {
"priority": 50
}
}
},
"delete": {
"min_age": "5m",
"actions": {
"delete": {}
}
}
}
}
}Explicação das condições:
- Hot → Rollover: acontece quando o índice tiver mais de 1 minuto ou mais de 5 documentos — o que ocorrer primeiro
-
Warm: o índice entra nessa fase 2 minutos após o rollover. O ILM injeta a action
migrate, que move o índice para o node warm, e oforcemergecompacta todos os segments em um único, otimizando leitura - Delete: o índice é deletado 5 minutos após o rollover
O template associa a ILM policy e o alias de rollover a todos os índices com o padrão vendas-*:
PUT /_index_template/sales
{
"index_patterns": ["vendas-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"index.lifecycle.name": "policy-vendas",
"index.lifecycle.rollover_alias": "vendas"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"Category": { "type": "keyword" },
"City": { "type": "keyword" },
"Country": { "type": "keyword" },
"CustomerName": { "type": "keyword" },
"DaystoShipActual": { "type": "long" },
"DaystoShipScheduled": { "type": "long" },
"Discount": { "type": "double" },
"OrderDate": { "type": "date", "format": "iso8601" },
"OrderID": { "type": "keyword" },
"OrderProfitable": { "type": "keyword" },
"PostalCode": { "type": "long" },
"ProductName": { "type": "text" },
"Profit": { "type": "long" },
"ProfitRatio": { "type": "double" },
"Quantity": { "type": "long" },
"Region": { "type": "keyword" },
"Sales": { "type": "long" },
"SalesForecast": { "type": "long" },
"SalesaboveTarget": { "type": "keyword" },
"SalesperCustomer": { "type": "double" },
"Segment": { "type": "keyword" },
"ShipDate": { "type": "date", "format": "iso8601" },
"ShipMode": { "type": "keyword" },
"ShipStatus": { "type": "keyword" },
"State": { "type": "keyword" },
"Sub_Category": { "type": "keyword" },
"latitude": { "type": "double" },
"location": { "type": "geo_point" },
"longitude": { "type": "double" }
}
}
}
}O primeiro índice precisa ser criado manualmente com o alias configurado como is_write_index: true. Esse alias é o que o ILM usa para saber para onde direcionar as novas escritas após cada rollover.
PUT /vendas-000001
{
"aliases": {
"vendas": {
"is_write_index": true
}
}
}A convenção de nomear o índice inicial com sufixo numérico (
-000001) é exigida pelo mecanismo de rollover — o Elasticsearch incrementa esse número a cada rollover (-000002,-000003, etc.).
GET /vendas-000001/_ilm/explainO campo phase deve indicar hot e o campo action deve mostrar rollover. O campo age mostra quanto tempo o índice tem desde sua criação.
Veja também em qual node o índice está:
GET /_cat/shards/vendas-*?v&h=index,prirep,state,nodeO shard fica no lab-es-es-default-0, o node hot.
Insira alguns documentos via alias. Note que usamos o alias vendas como destino — não o nome do índice diretamente. Isso garante que as escritas sempre vão para o índice ativo de escrita, independentemente de quantos rollovers já aconteceram.
POST /vendas/_doc
{
"Category": "Office Supplies",
"Discount": 0,
"ProductName": "Xerox 214",
"latitude": 41.6347,
"PostalCode": 2740,
"ShipStatus": "Shipped Early",
"OrderID": "CA-2014-148355",
"Segment": "Corporate",
"SalesaboveTarget": "No",
"Sales": 19,
"SalesperCustomer": 19.44,
"Profit": 9,
"ShipMode": "Standard Class",
"CustomerName": "Nick Crebassa",
"DaystoShipScheduled": 6,
"longitude": -70.9372,
"Sub_Category": "Paper",
"ProfitRatio": 48,
"Quantity": 3,
"City": "New Bedford",
"ShipDate": "2014-12-29T00:00:00.000Z",
"SalesForecast": 25,
"OrderDate": "2014-12-25T00:00:00.000Z",
"OrderProfitable": "No",
"@timestamp": "2014-12-25T00:00:00.000Z",
"State": "Massachusetts",
"Country": "United States",
"Region": "East",
"location": "41.6347,-70.9372",
"DaystoShipActual": 4
}Insira mais 4 documentos para atingir a condição de rollover por max_docs: 5. Pode repetir o mesmo documento alterando o OrderID para simular registros diferentes.
Após inserir 5 documentos ou aguardar 1 minuto (o que ocorrer primeiro), o ILM deve executar o rollover automaticamente. Verifique:
GET /_cat/indices/vendas-*?v&s=indexApós o rollover, você verá dois índices: vendas-000001 (que saiu da fase hot) e vendas-000002 (novo índice ativo de escrita).
Verifique o alias:
GET /_cat/aliases/vendas?vO is_write_index deve ter migrado para vendas-000002.
Acompanhe o estado do ILM em cada índice:
GET /vendas-*/_ilm/explainAguarde os tempos configurados e execute os comandos abaixo repetidamente para observar as transições:
GET /vendas-000001/_ilm/explainGET /_cat/shards/vendas-*?v&h=index,prirep,state,nodeOs campos a observar:
| Campo | O que indica |
|---|---|
phase |
Fase atual do índice (hot, warm, delete) |
action |
Ação sendo executada na fase atual |
step |
Etapa dentro da ação |
age |
Tempo de vida do índice |
phase_time |
Quando o índice entrou na fase atual |
Quando o vendas-000001 entra na fase warm, o _cat/shards passa a mostrar o shard dele no lab-es-es-node2-0: a action migrate moveu o índice para o tier warm. Na fase delete, o índice desaparece.
Para ver a preferência de tier que o ILM aplicou:
GET /vendas-000001/_settings?filter_path=**._tier_preferenceSe quiser acelerar a transição para testes sem esperar o timer, use:
POST /_ilm/move/vendas-000001
{
"current_step": {
"phase": "hot",
"action": "complete",
"name": "complete"
},
"next_step": {
"phase": "warm",
"action": "forcemerge",
"name": "forcemerge"
}
}Use com cautela — forçar transições em produção pode ter efeitos colaterais dependendo das ações configuradas na policy.
Por padrão, o Elasticsearch bloqueia deleções usando wildcard (*) para evitar remoção acidental de dados. Antes de deletar os índices do lab, é necessário desabilitar essa proteção temporariamente:
PUT /_cluster/settings
{
"persistent": {
"action.destructive_requires_name": false
}
}Remover os índices do lab:
DELETE /vendas-*Após a limpeza, restaure a proteção:
PUT /_cluster/settings
{
"persistent": {
"action.destructive_requires_name": null
}
}Remover o template:
DELETE /_index_template/salesRemover a policy:
DELETE /_ilm/policy/policy-vendasNeste laboratório vamos criar uma policy mais próxima de produção, com três fases ativas, readonly e forcemerge na fase warm, e acompanhar o índice passando pelos três nodes do cluster: hot, warm e cold.
PUT /_ilm/policy/policy-logs-completa
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "2m",
"max_docs": 10,
"max_primary_shard_size": "50gb"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "3m",
"actions": {
"set_priority": { "priority": 50 },
"readonly": {},
"forcemerge": { "max_num_segments": 1 },
"allocate": { "number_of_replicas": 0 }
}
},
"cold": {
"min_age": "6m",
"actions": {
"set_priority": { "priority": 0 },
"allocate": { "number_of_replicas": 0 }
}
},
"delete": {
"min_age": "10m",
"actions": {
"delete": {}
}
}
}
}
}Explicação das ações adicionais em relação ao Lab 1:
-
readonlyna fase warm: após o rollover, o índice nunca mais vai receber escritas — torná-lo readonly explicita isso e protege contra escritas acidentais -
forcemergecommax_num_segments: 1: compacta todos os segments em um único, o que melhora a performance de leitura e elimina o overhead de múltiplos segments pequenos -
allocate: number_of_replicasno warm e no cold: controla o número de réplicas em cada fase. No laboratório, com um node por tier, as duas fases usam0. Em produção, o comum é manter1réplica no warm (leitura paralela e proteção contra falha) e usar0no cold, para economizar disco — os dados raramente são consultados nessa fase -
migrate(injetada automaticamente) no warm e no cold: move o índice para o node warm e, depois, para o node cold
Por que não usar 1 réplica no warm do laboratório? A action
allocateespera todas as cópias do índice serem alocadas antes de seguir para as próximas actions. Com um único node warm, a réplica nunca seria alocada e o ILM ficaria parado nessa etapa.
PUT /_index_template/template-logs-completa
{
"index_patterns": ["app-completa-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"index.lifecycle.name": "policy-logs-completa",
"index.lifecycle.rollover_alias": "app-completa"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"mensagem": { "type": "text" },
"nivel": { "type": "keyword" },
"servico": { "type": "keyword" }
}
}
}
}O padrão de nome é
app-completa-*, e nãologs-completa-*: nomes no formatologs-*-*são reservados pelo template interno do Elasticsearch para data streams de logs, que tem prioridade maior e impediria a criação de um índice comum com esse nome.
PUT /app-completa-000001
{
"aliases": {
"app-completa": {
"is_write_index": true
}
}
}POST /_bulk
{ "index": { "_index": "app-completa" } }
{ "@timestamp": "2026-05-01T10:00:00Z", "mensagem": "Serviço iniciado", "nivel": "INFO", "servico": "api-gateway" }
{ "index": { "_index": "app-completa" } }
{ "@timestamp": "2026-05-01T10:01:00Z", "mensagem": "Conexão estabelecida", "nivel": "INFO", "servico": "auth-service" }
{ "index": { "_index": "app-completa" } }
{ "@timestamp": "2026-05-01T10:02:00Z", "mensagem": "Timeout na chamada ao banco", "nivel": "ERROR", "servico": "api-gateway" }
{ "index": { "_index": "app-completa" } }
{ "@timestamp": "2026-05-01T10:03:00Z", "mensagem": "Retry bem-sucedido", "nivel": "WARN", "servico": "api-gateway" }
{ "index": { "_index": "app-completa" } }
{ "@timestamp": "2026-05-01T10:04:00Z", "mensagem": "Cache invalidado", "nivel": "DEBUG", "servico": "cache-service" }Acompanhe as transições com:
GET /app-completa-*/_ilm/explainObserve como o índice transita pelo hot → warm → cold → delete ao longo dos minutos configurados. Em cada fase, verifique também em qual node o índice está e o número de réplicas:
GET /_cat/shards/app-completa-*?v&h=index,prirep,state,nodeGET /_cat/indices/app-completa-*?v&h=index,pri,rep,status,healthO app-completa-000001 começa no lab-es-es-default-0 (hot), passa para o lab-es-es-node2-0 (warm), depois para o lab-es-es-node3-0 (cold) e, por fim, é deletado.
PUT /_cluster/settings
{
"persistent": {
"action.destructive_requires_name": false
}
}DELETE /app-completa-*
DELETE /_index_template/template-logs-completa
DELETE /_ilm/policy/policy-logs-completaPUT /_cluster/settings
{
"persistent": {
"action.destructive_requires_name": null
}
}Um dos cenários mais comuns em produção é o ILM travar em um step com erro. Vamos simular essa situação e ver como diagnosticar e resolver.
Os erros mais frequentes de ILM em produção são:
| Situação | Causa | Solução |
|---|---|---|
| Rollover não acontece | Índice não tem o alias configurado como is_write_index
|
Recriar o alias corretamente |
| Rollover não acontece | Index template não tem o rollover_alias configurado |
Corrigir o template e recriar o índice |
forcemerge trava |
Node com disco insuficiente | Liberar espaço ou mover o índice para outro node |
shrink falha |
Índice não está em read-only | Adicionar a action readonly antes do shrink na policy |
allocate ou migrate não avança |
Não há nodes com o tier configurado, ou as réplicas não cabem nos nodes do tier | Configurar os papéis dos nodes (no ECK, os nodeSets de cada tier) ou reduzir as réplicas |
| Fase não avança |
min_age não foi atingido |
Aguardar ou usar _ilm/move para forçar a transição |
Vamos criar um índice gerenciado pelo ILM, mas sem o alias correto, e observar o que o ILM reporta. Primeiro crie uma policy e o template:
PUT /_ilm/policy/policy-erro
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_docs": 1 }
}
}
}
}
}PUT /_index_template/template-logs-erro
{
"index_patterns": ["app-erro-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"index.lifecycle.name": "policy-erro",
"index.lifecycle.rollover_alias": "app-erro"
}
}
}Agora crie o índice sem o alias:
PUT /app-erro-000001O índice foi criado com a policy, mas o alias app-erro com is_write_index: true não existe. Insira um documento e aguarde o ILM tentar o rollover:
POST /app-erro-000001/_doc
{
"@timestamp": "2026-05-01T10:00:00Z",
"mensagem": "Teste de erro ILM"
}Após alguns segundos, consulte o estado do ILM:
GET /app-erro-000001/_ilm/explainVocê verá o campo step como ERROR e o step_info com um erro descrevendo que o alias não aponta para o índice. Para ver só índices com erro em todo o cluster:
GET /*/_ilm/explain?only_errors=trueAdicione o alias correto ao índice:
POST /_aliases
{
"actions": [
{
"add": {
"index": "app-erro-000001",
"alias": "app-erro",
"is_write_index": true
}
}
]
}Agora instrua o ILM a tentar novamente:
POST /app-erro-000001/_ilm/retryVerifique que o erro foi resolvido:
GET /app-erro-000001/_ilm/explainO índice sai do estado de erro, faz o rollover e o alias passa a apontar para o app-erro-000002.
PUT /_cluster/settings
{ "persistent": { "action.destructive_requires_name": false } }DELETE /app-erro-*
DELETE /_index_template/template-logs-erro
DELETE /_ilm/policy/policy-erroPUT /_cluster/settings
{ "persistent": { "action.destructive_requires_name": null } }Neste laboratório o ciclo de vida é aplicado a um data stream. Não há alias nem índice inicial para criar: o primeiro documento cria o data stream e o primeiro índice de apoio.
PUT _ilm/policy/lab-logs-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_primary_shard_size": "1gb", "max_age": "5m" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "2m",
"actions": {
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 },
"allocate": { "number_of_replicas": 0 }
}
},
"delete": {
"min_age": "10m",
"actions": { "delete": {} }
}
}
}
}PUT _index_template/lab-logs-template
{
"index_patterns": ["lab-logs-*"],
"data_stream": {},
"priority": 200,
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"index.lifecycle.name": "lab-logs-policy"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"mensagem": { "type": "text" },
"nivel": { "type": "keyword" }
}
}
}
}O "data_stream": {} faz o template criar data streams em vez de índices comuns. O "priority": 200 garante que este template tenha precedência sobre outros templates que também casem com o nome (como um template genérico para lab-*): dois templates com padrões sobrepostos não podem ter a mesma prioridade.
POST lab-logs-app/_doc
{ "@timestamp": "2026-08-04T12:00:00Z", "nivel": "INFO", "mensagem": "app iniciada" }Como existe um template com data_stream para lab-logs-*, o Elasticsearch cria o data stream lab-logs-app e o primeiro índice de apoio .ds-lab-logs-app-<data>-000001.
GET _data_stream/lab-logs-appA resposta mostra o data stream, a policy de ILM e a lista de índices de apoio (indices). Você escreve e consulta o nome único; o ILM gerencia os índices por baixo.
POST lab-logs-app/_rolloverGET _data_stream/lab-logs-appAgora o data stream tem dois índices de apoio: o ...-000001, que saiu da fase hot, e o ...-000002, que passou a receber as escritas.
GET lab-logs-app/_ilm/explainGET _cat/shards/.ds-lab-logs-app-*?v&h=index,prirep,state,nodeIndexe mais um documento e repita os dois comandos a cada 1 ou 2 minutos:
POST lab-logs-app/_doc
{ "@timestamp": "2026-08-04T12:05:00Z", "nivel": "ERROR", "mensagem": "falha X" }O índice de apoio mais antigo passa da fase hot para a warm depois de 2 minutos (e o shard dele muda do lab-es-es-default-0 para o lab-es-es-node2-0) e é deletado depois de 10 minutos.
GET _ilm/policy/lab-logs-policyO campo in_use_by mostra os índices, data streams e templates que usam a policy.
DELETE _data_stream/lab-logs-app
DELETE _index_template/lab-logs-template
DELETE _ilm/policy/lab-logs-policyApagar o data stream apaga também todos os índices de apoio dele.
Além da API, é possível criar e gerenciar ILM policies diretamente pelo Kibana, sem escrever JSON. O caminho é:
Menu → Stack Management → Index Lifecycle Policies
A interface permite:
- Criar e editar policies com um formulário visual
- Visualizar todos os índices gerenciados por cada policy
- Ver o estado atual de cada índice (fase, ação, possíveis erros)
- Retentar steps com erro sem precisar usar a API
Para associar uma policy a um index template existente, vá em:
Menu → Stack Management → Index Management → Index Templates
Selecione o template desejado e edite a seção Index settings adicionando:
{
"index.lifecycle.name": "nome-da-policy",
"index.lifecycle.rollover_alias": "nome-do-alias"
}Nomeie policies de forma descritiva: policy-logs-nginx-30d é mais claro do que policy1. O nome deve comunicar o tipo de dado e o período de retenção.
Não reutilize a mesma policy para dados com requisitos diferentes: logs de aplicação e eventos de segurança podem ter retenções e tiers diferentes. Crie uma policy para cada caso de uso.
Sempre teste com timers curtos antes de ir para produção: configure max_age em minutos e min_age em segundos para validar o comportamento completo do ciclo de vida antes de ajustar para dias e semanas.
Use wait_for_snapshot na fase delete: em ambientes onde compliance é importante, nunca delete sem garantir que o snapshot foi executado. A action wait_for_snapshot integra o ILM com o SLM (Snapshot Lifecycle Management) e só deleta após confirmar que o backup existe.
Monitore índices com erro regularmente:
GET /*/_ilm/explain?only_errors=trueEm produção, esse endpoint deve fazer parte do monitoramento da stack — um índice travado no ILM significa que dados estão acumulando em disco sem passar pelo ciclo esperado.
Cuidado com shrink em produção: o shrink requer que todos os shards do índice estejam no mesmo node antes de executar. Em clusters grandes, isso pode causar movimentação significativa de dados. Avalie se o benefício de reduzir o número de shards justifica o custo operacional.
Dimensione os tiers junto com a policy: no ECK, cada tier é um nodeSet. Os nodes warm e cold precisam ter disco suficiente para receber os índices que o ILM vai mover para eles, e o número de nodes de cada tier precisa comportar as réplicas definidas na policy.
Listar todas as policies:
GET /_ilm/policyVer o estado do ILM em todos os índices de um padrão:
GET /vendas-*/_ilm/explainVer apenas índices com erro no ILM:
GET /vendas-*/_ilm/explain?only_errors=trueRetentar um step com erro:
POST /vendas-000001/_ilm/retryVer o status geral do ILM no cluster:
GET /_ilm/statusPausar o ILM globalmente (útil durante manutenções):
POST /_ilm/stopRetomar o ILM:
POST /_ilm/startO Curator é uma ferramenta open source mantida pela Elastic para gerenciar o ciclo de vida de índices via linha de comando. Antes do ILM existir (versão 6.6), o Curator era a principal forma de automatizar tarefas como deleção de índices antigos, force merge e snapshot.
Hoje, com o ILM disponível nativamente e gratuito desde a versão 7.x, o Curator perdeu relevância em novas implementações. No entanto, ainda aparece em ambientes legados que migraram de versões antigas do Elasticsearch sem rever a gestão de índices.
Quando você ainda pode encontrar o Curator:
- Clusters que foram instalados antes do ILM existir e nunca foram migrados
- Ambientes com restrições que impedem o uso de features X-Pack (muito raros hoje)
- Scripts de automação externos que gerenciam índices de forma imperativa
Para novas implementações, use o ILM. Ele é mais integrado, mais simples de configurar e não requer ferramentas externas.
Referência: github.com/elastic/curator
Com os laboratórios de ILM concluídos, o lab-es volta a ter um único node. Essa etapa é obrigatória: os próximos laboratórios usam a memória da VM para outros componentes.
Voltar para um node é reaplicar o manifesto original do lab-es: o operator migra os dados dos nodes node2 e node3 para o lab-es-es-default-0 e só depois remove os pods e os PVCs deles. A migração só acontece se todos os shards couberem no node que fica.
No Console, restaure o intervalo de verificação do ILM para o valor padrão:
PUT /_cluster/settings
{
"persistent": {
"indices.lifecycle.poll_interval": null
}
}Confira se sobrou algum índice ou data stream dos laboratórios e se algum índice tem réplica:
GET _cat/indices/vendas-*,app-*,.ds-lab-logs-*?v&h=index,health,pri,repGET _cat/indices?v&h=index,health,pri,rep&s=rep:descApague o que sobrou dos laboratórios usando as seções de limpeza de cada um. Se algum índice seu tiver rep maior que 0, zere as réplicas dele (os índices de sistema, que começam com ., ajustam as réplicas sozinhos):
PUT <nome-do-indice>/_settings
{
"index": { "number_of_replicas": 0 }
}No terminal da VM, recrie o manifesto original do lab-es (se ele já existir, o conteúdo é o mesmo) e aplique:
mkdir -p ~/eck-lab/manifests
cd ~/eck-lab
cat <<'EOF' > manifests/elasticsearch.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: lab-es
spec:
version: 9.4.2
nodeSets:
- name: default
count: 1
config:
node.store.allow_mmap: true
podTemplate:
spec:
containers:
- name: elasticsearch
env:
- name: ES_JAVA_OPTS
value: -Xms2g -Xmx2g
resources:
requests:
memory: 4Gi
cpu: "1"
limits:
memory: 4Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 20Gi
storageClassName: local-path
EOF
kubectl apply -n elastic -f manifests/elasticsearch.yaml
kubectl -n elastic get pods -wResultado esperado: o operator reinicia o lab-es-es-default-0 (que volta a ter todos os papéis) e, depois de migrar os dados, remove os pods lab-es-es-node2-0 e lab-es-es-node3-0. Pressione Ctrl+C quando só restar o lab-es-es-default-0 com 1/1 Running.
kubectl -n elastic get elasticsearch lab-es
kubectl -n elastic get pvc | grep lab-esResultado esperado: NODES 1 e HEALTH green, e só o PVC elasticsearch-data-lab-es-es-default-0.
Se a redução não avançar (os pods
node2enode3continuamRunningdepois de vários minutos), algum shard não consegue ir para o node que fica. Veja os eventos e o log do operator e confira o Passo 1:kubectl -n elastic describe elasticsearch lab-es | sed -n '/Events/,$p' kubectl -n elastic-system logs statefulset/elastic-operator | tail -20
- O ILM automatiza o ciclo de vida dos índices com policies divididas em fases (hot, warm, cold, frozen, delete), cada uma com suas actions.
- O rollover cria um novo índice quando o atual atinge um limite de tamanho, idade ou documentos; a escrita segue por um alias ou por um data stream.
- A policy, o index template e o índice inicial (ou o data stream) formam o fluxo completo do ILM.
- No ECK, cada tier é um
nodeSet; a actionmigratemove os shards para os pods do tier da fase, sem participação do operator. - O
_ilm/explainmostra fase, action e step de cada índice;only_errors=truelista os índices travados e o_ilm/retryretoma um step com erro. - Nomes
logs-*-*emetrics-*-*são reservados pelos templates internos de data streams; use outros padrões nos seus índices.
5.1 - O que é o ILM
- Index lifecycle management
- Index lifecycle (fases e actions)
- Index lifecycle actions
- Rollover
- Rollover action
- Migrate action
- Data tiers
- Elastic subscriptions
5.2 - Data streams
5.3 a 5.7 - Laboratórios
- Configure a lifecycle policy
- Lifecycle settings (indices.lifecycle.poll_interval)
- Explain the lifecycle state API
- Move to a lifecycle step API
- Retry a policy API
- Rollover API
- Get data streams API
- Aliases API
- Fix index lifecycle management errors
- Advanced Elasticsearch node scheduling: hot-warm topologies (ECK)
5.8 a 5.11 - Kibana, boas práticas, operações e Curator
5.12 - Voltando o lab-es para um node