Los índices son la unidad lógica que nos permite organizar los datos en elasticsearch. La forma más sencilla de usar índices sería crear uno para cada grupo de logs/métricas que compartan una cierta estructura o campos. Teniendo en cuenta que principalmente estamos guardando logs o métricas, esta estrategia nos provocará una serie de problemas:
- ¿Cómo vamos a controlar que los índices/shards no crezcan demasiado?
- ¿Cómo borramos datos antiguos?¿Mediante un delete by query? Puede ser un proceso bastante lento.
- Si los índices (y concretamente los shards) crecen demasiado, vamos a tardar bastante en recuperar los shards ante un fallo o reinicio.
Para simplificar estos problemas, es muy común generar nuevos índices cada día o cada semana. En Logstash se puede hacer de forma sencilla. Por ejemplo, con el siguiente output el nombre del índice en el que escribimos dependerá del día actual:
output {
elasticsearch {
hosts => ["http://elasticsearch.datadope.io:9200"]
index => "app-%{+YYYY.MM.dd}"
}
}
Esta estrategia nos aporta una serie de ventajas:
– Si queremos borrar datos de un día en concreto, es tan sencillo como borrar el índice de ese día.
– Los índices ya no crecen tan descontroladamente. Como mucho, un índice contendrá todos los datos que se hayan generado en un día.
– Búsquedas más eficientes, ya que si necesitamos hacer una búsqueda sobre los datos de hoy o ayer, podemos consultar únicamente los índices referentes a estos días, sin necesidad de buscar en todos los datos.
No obstante, con esta estrategia seguimos teniendo una serie de problemas. Por una parte, hay que tener en cuenta que, sobretodo si hablamos de logs, la cantidad de datos que nos llegan puede ser muy variable. Por tanto, aunque tengamos automatizado el borrado de índices antiguos, en ocasiones pueden llegar más datos de lo normal, llenando los discos de los nodos de datos, provocando intervenciones manuales y forzándonos a revisar de vez en cuando las retenciones de cada tipo de log. Además, tener un índice diario para cada tipo de log puede ser excesivo,ya que si tenemos muchos tipos de datos, terminaremos con múchos índices (y muchos shards). En elasticsearch cuantos más shards tengamos (ignorando shards con muy pocos datos, que no tienen impacto), más memoria necesitaremos en nuestros nodos. Por tanto tener muchos índices de tamaño medio/pequeño tendrá un impacto en el uso de memoria de los nodos de datos. En general, es recomendable tener shards de entre unos pocos y unas decenas de GB .
Para solucionar estos problemas aparecieron los índices rollover e ILM (Index Lifecycle Management). Con esta estrategia, inicialmente crearemos un índice (bootstrap index) y un alias apuntando a dicho índice. En general usaremos el alias tanto para leer como para escribir, sin tener que preocuparnos de qué índices usa el alias. Cuando el índice supere el tamaño configurado en la política ILM, elasticsearch ejecutará un rollover de ese índice (también podemos provocar el rollover en base al tiempo desde la creación del índice o el número de documentos que contenga), lo cual generará un nuevo índice y a partir de ese momento todas las peticiones de indexación de nuevos documentos sobre el alias se realizarán en el nuevo índice. Las lecturas se realizarán sobre el índice de escritura actual y los índices anteriores (es decir, sobre todos los índices que gestiona el alias). Tras ejecutar el rollover, con ILM los índices irán pasando por una serie de fases a medida que avance el tiempo. Esto nos permite, por ejemplo, la posibilidad de usar disco SSD para datos “hot” recientes en los que se produzcan muchas lecturas o escrituras, o enviar los índices antiguos que apenas son consultados a discos mecánicos. En cada fase podemos ejecutar distintas tareas de mantenimiento, como forcemerge, shrink, etc. La idea general es que ahora en vez de usar índices directamente, usaremos alias con políticas ILM que se encargan de controlar el tamaño y el cíclo de vida de los índices subyacentes.
El alias siempre contará con un índice para añadir nuevos datos (parámetro is_write_index). El resto de índices sólo se usarán para lecturas y actualizaciones de documentos. Y cada vez que se produzca el rollover, se generará un nuevo índice que pasará a ser el write_index.
Data streams
Los data streams son una nueva abstracción que aparece en Elasticsearch 7.9 para gestionar datos de tipo timeseries y es la forma recomendada para guardar datos de tipo append-only (es decir, datos que no vayamos a modificar ni borrar). Para leer o escribir, enviaremos nuestras peticiones hacia el data stream, y, al igual que con los alias, los datastreams por debajo mantendrán una serie de índices rollover, que también pueden ir rotando con políticas ILM, exactamente igual que con los alias. Además, los data streams también tienen un único índice activo al que se dirigirán las escrituras de nuevos documentos y el resto de índices estarán siempre accesibles en modo lectura para cuando hagamos una consulta al data stream. Podemos usar los data streams directamente como fuente de datos a la hora de crear index patterns (llamados data views desde kibana 8) en kibana, búsquedas en Watcher o incluso data sources en grafana. Por tanto, los data streams tienen un funcionamiento muy similar a los alias con políticas ILM e índices rollover. Pero hay algunas diferencias que conviene destacar:
– Cuando queríamos usar ILM e índices rollover basados en alias, para cada nuevo grupo de datos necesitábamos crear manualmente el primer índice (bootstrap index) así como el alias que apuntaba a este índice. Con data streams esto ya no es necesario. Podemos crear data streams directamente con el simple hecho de enviar peticiones de escritura al data stream (siempre que haya definida una template en elastic).
– Con alias definíamos un nombre de índice (ejemplo log-demo-000001) y cada vez que se producía el rollover se generaban nuevos índices log-demo-000002, log-demo-000003, etc. Con data streams los índices subyacentes se generan de forma automática y emplean la siguiente nomenclatura:
.ds-<data-stream>-<yyyy.MM.dd>-<generation>
donde <data-stream> es el nombre del data stream, <yyy.MM.dd> es la fecha de creación del índice y generation es un número de 6 dígitos empezando por 000001. Cada vez que se produzca el rollover, este número se incrementará.
– Con alias podíamos ejecutar operaciones de update o delete. Con data streams esto no es posible. No obstante, sí que está permitido ejecutar las llamadas a la api _update_by_query para actualizar documentos) y _delete_by_query (para borrar documentos). Conviene destacar que estos límites aplican a nivel de datastream. Por tanto, si intentamos las operaciones de update y delete directamente sobre los índices subyacentes, sí que funcionarán sin problemas (siempre y cuando la configuración de los índices lo permita).
– Todos los documentos que queramos ingestar en un data stream deben tener definido el campo @timestamp.
A la hora de nombrar los datastreams, desde Elasticsearch recomiendan emplear la siguiente nomenclatura:
{type}-{dataset}-{namespace}
El campo type representa el tipo de datos (en el estándar de Elastic actualmente este campo pede tener los valors logs o metrics). El campo dataset es un nombre que identifica el tipo de datos así como su estructura. Y por último, el campo namespace representa agrupaciones arbitrarias definidas por el usuario. Ejemplo: logs-nginx.access-prod.
Logstash e índices con nombres dinámicos
Desde Logstash 7.13.0 el output de elasticsearch soporta oficialmente el uso de data streams mediante una serie de nuevas variables para facilitar la escritura en data streams. Además estas variables nos permiten:
– Una mejor integración con Elastic Agent, ya que ahora Elastic agent (o cualquier otro agente) puede enviar datos referentes al data stream en las variables del documento tipo data_stream.*, que logstash entenderá y usará para saber dónde y cómo indexar los datos, que podrán ser sobreescritas de forma explícita con las opciones de configuración del output de elastic data_stream_*.
– Nos empujan a emplear el Elasticsearch Common Schema que en Elasticsearch tienen definido para data streams, y que sigue la nomenclatura definida arriba.
Por tanto, si queremos seguir los estándares de elastic de la forma más rigurosa, deberíamos emplear estas variables data_stream_* en el output de elastic. No obstante, el hecho de que logstash tenga estas nuevas opciones para data streams, no significa que estemos obligados a usarlas siempre que queramos escribir en data streams. Podemos e incluso tendremos que prescindir de ellas en algunos casos particulares, como veremos a continuación.
En ocasiones nos puede interesar usar campos del propio documento para determinar el nombre del índice en el que queremos escribir. Imaginemos que tenemos una aplicación llamada app para la cuál queremos indexar los documentos en distintos índices en base al campo app_type del documento. Podríamos emplear el indexado diario clásico de un índice diario:
output {
elasticsearch {
hosts => ["http://elasticsearch.datadope.io:9200"]
index => "app-%{app_type}-%{+YYYY.MM.dd}"
}
}
Tal y como hemos visto al inicio, los índices diarios no son la forma más óptima de gestionar los datos en elastic. ¿Y si quisiéramos emplear alias basados en índices rollover con políticas ILM? Para usar estos alias debemos inicializar el primer índice y crear un alias que apunte a él. Si estamos hablando de casos concretos y controlados, podemos crear estos índices y alias a mano. Pero si tenemos muchos casos diferentes y necesitamos que estos índices y alias se generen automáticamente, tenemos un problema, ya que Logstash nunca ha sido capaz de hacer esto. No obstante, con los data streams ya no tenemos esta restricción, ya que, como hemos comentado, para crear un data stream, únicamente necesitamos que haya una template que haga match con el nombre del data stream que queremos crear, y al enviar la petición de escritura, se creará el nuevo data stream si no existe.
Para escribir en data streams desde Logstash podemos usar las opciones data_stream_* del output de elastic. Pero cuando necesitamos que nombre del índice en el que queremos escribir venga de una variable, estas opciones no sirven, ya que no interpretan las variables. Pero esto no es problema, ya que podemos escribir en data streams como si fueran índices normales. Únicamente necesitamos que exista una template en elasticsearch que haga match con el nombre del data stream en el que queremos escribir y usar la siguiente opción en el output de logstash:
action => "create"
Ejemplo
Vamos a probar la generación de data streams desde logstash usando como nombres del data stream campos del documento a ingestar.
1. Primero generamos una política (en realidad este paso no es necesario, ya que elasticsearch incluye políticas por defecto que podríamos usar). Podemos hacer esto usando la api de elasticsearch mediante “Dev Tools” en kibana:
PUT _ilm/policy/demo-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "30d"
},
"set_priority": {
"priority": 75
}
}
},
"delete": {
"min_age": "60d",
"actions": {
"delete": {
"delete_searchable_snapshot": true
}
}
}
}
}
}
2. Creamos una template para que elastic sepa que todo lo que haga match con este patrón será un data stream que use la política generada en el paso anterior.
PUT _index_template/logs-demo
{
"index_patterns": [
"logs-demo-*"
],
"template":{
"settings": {
"index.lifecycle.name": "demo-policy"
}
},
"data_stream": {},
"priority": 300
}
3. Ejecutamos la siguiente pipeline de logstash, que genera 6 mensajes distintos de prueba de forma repetida, parsea los campos app y msg, e indexa en elasticsearch en base al campo app:
input {
generator {
lines => [
"msg=Mensaje de prueba 1 app=alpha",
"msg=Mensaje de prueba 2 app=alpha",
"msg=Mensaje de prueba 1 app=beta",
"msg=Mensaje de prueba 2 app=beta",
"msg=Mensaje de prueba 1 app=gamma",
"msg=Mensaje de prueba 2 app=gamma"
]
}
}
filter {
dissect {
mapping => {
"message" => "msg=%{msg} app=%{app}"
}
}
}
output {
elasticsearch {
hosts => ["http://elastic.datadope.io:9200"]
user => "ingestor"
password => "***"
index => "logs-demo-%{app}"
action => "create"
}
}
4. Podemos comprobar que se han generado data streams para cada tipo de app automáticamente:
Si vemos la sección de índices, podemos comprobar que cada data stream ha generado un índice, donde escribe los documentos.
Es decir, hemos generado 3 data streams, que por debajo emplean índices rollover y sus políticas ILM de forma automática desde logstash, sin necesidad de crear manualmente ningún alias ni ningún índice. Lo único que hemos necesitado es generar una template (y una política, si es que las que ya teníamos no nos valen).
Conclusión
Tal y como hemos visto, podemos organizar la información en elasticsearch de muchas formas. Si queremos optimizar el uso de recursos por parte de elasticsearch y simplificar la administración/operación de nuestros índices, evitando desequilibrios generados por un volumen de datos variable, lo más recomendable será emplear índices rollover con políticas ILM. Para usar esta estrategia de indexación, solíamos tener que generar manualmente un índice (bootstrap index) y un alias apuntando a dicho índice. Ahora con los data streams esto no es necesario.
Los data streams no introducen muchas novedades. Más bien son una pequeña evolución. Una nueva abstracción que encapsula los alias y los índices controlados por estos alias. Con esta nueva abstracción podemos generar nuevos grupos de datos con sus índices rollover y políticas ILM sin necesidad de intervenir manualmente cada vez que aparezca un nuevo grupo de datos. Esto nos permite, entre otras cosas, poder generar índices rollover de forma dinámica en base a información de los documentos desde Logstash.