sábado, 22 de junio de 2019

Cluster ONTAP - Replicación de datos con SnapMirror - Parte I

Hoy, después de mucho tiempo sin publicar nada sobre sistemas NetApp, vamos a revisar como podemos crear una estrategia de disaster recovery para proteger los datos almacenados en sistemas ONTAP. Cómo creo que es necesario que sea bastante detallado, voy a hacer una serie de posts sobre SnapMirror en sistemas cluster ONTAP, siendo este el primero de la serie y donde veremos como establecer la relación de peering entre los clusters y comenzaremos la creación de la relación de SnapMirror.

En un post anterior, nada menos que de 2015, ya explicaba como configurar y usar SnapMirror en sistemas ONTAP 7-mode con lo que, lo que vamos a ver en este nuevo post es similar, pero con las características de la versión cluster ONTAP.

Cómo ya sabemos, la replicación de datos entre sistemas ONTAP se basa en el uso de SnapMirror, con el cual podemos replicar los snapshots de un volumen a una localización de respaldo, desde donde servir los datos replicados en caso de desastre en el site principal. Cómo es lógico, en el caso de estar implementando una solución de backup a disco con SnapVault, ONTAP también está empleando SnapMirror para replicar los snapshots de los volúmenes, pero se establecen una serie de reglas de retención en función de nuestras necesidades de backup.

La principal diferencia que encontramos en cluster ONTAP, es la necesidad de crear una relación entre ambos clusters para poder establecer la relación de SnapMirror entre ellos. Esta medida de seguridad nos asegura que solamente los clusters autorizados podrán establecer una relación SnapMirror para la réplica de datos. Esta relación de peering debe existir también entre las SVMs propietarias de los volúmenes.

Adicionalmente, ya que en cluster ONTAP creamos SVMs para servidr datos mediante diferentes protocolos, podremos establecer relaciones de SnapMirror no solamente entre volúmenes, sino entre las SVMs propietarias de los volúmenes. De este modo, todo o parte de la configuración de la SVM se replicará al sistema secundario, así como los datos de los volúmenes de los que es propietaria la SVM. La replicación de SVMs la trataremos en otro post de la serie en más detalle.

Hay dos modos o motores de replicación diferentes implementados en ONTAP, denominados XDP (eXtended Data Protection) y DP (Data Protection). La diferencia fundamental entre ambos es que, si creamos una relación de SnapMirror de tipo DP, las versiones ONTAP de los sistemas involucrados debe ser la misma, mientras que el modo XDP soporta que las versiones sean diferentes. Este punto, aunque pueda parecer secundario, es importante por sus implicaciones y porque hasta ONTAP 9.3, DP era el modo por defecto empleado al crear una relación SnapMirror.

Para establecer la relación entre los clusters, o peering, es necesario que estos tenga interfaces dedicadas para la comunicación entre clusters, los denominados LIF de tipo intercluster. Estos LIFs deben existir en todos los nodos de los clusters que vayan formar parte de la relación de peering y pueden ser dedicados o compartidos con data LIFs. En resumen, necesitaremos crear tantos LIFs de tipo intercluster como nodos, siendo la configuración la habitual, tanto por CLI como usando OnCommand System Manager, pero especificando que el rol del LIF es intercluster.

Una vez que hemos contado todo el rollo teórico básico, vamos a la parte interesante y configuremos una relación de SnapMirror básica, siendo el punto de partida dos clusters de dos nodos con los LIFs de intercluster ya configurados:

LIFs intercluster configuradas.
LIFs intercluster configuradas.
Una vez creadas las LIFs de tipo intercluster y tras comprobar que existe conectividad y que ningún firewall nos impide el acceso a los puertos necesarios, creamos la relación de peering entre los dos clusters.

En versiones anteriores a ONTAP 9.3 el comando usado para crear la relación de peering era bastante largo, además de que era necesario especificar las direcciones IP de intercluster, con lo que era bastante más cómodo usar OnCommand System Manager para establecer la relación de peering.

A partir de ONTAP 9.3, el comando cluster peer create es mucho más simple y nos permite crear la relación de peering de una forma mucho más sencilla. Solo tenemos que lanzar el comando cluster peer create en el sistema ONTAP de destino, el cual generará una salida incluyendo la passphrase que usaremos para establecer el peering con el cluster de origen. Por ejemplo:

Creamos la relación de peering en el sistema ONTAP de destino.
Aceptamos la relación de peering en el sistema ONTAP de origen.
Como vemos, la creación de la relación de peering es muy simple y es importante tener en cuenta que la passphrase generada es única para cada ejecución del comando y válida solamente para un cluster. Solamente en el caso del sistema origen de nuestros datos es necesario que especifiquemos las direcciones IP de los LIFs intercluster del sistema ONTAP con el que queremos establecer la relación de peering.

Ahora podemos comprobar que la relación de peering es correcta entre ambos clusters:

Estado de la relación de peering en un cluster.
Estado de la relación de peering en el otro cluster.
Ahora establecemos la relación de peering entre las SVMs implicadas, para lo cual solo tenemos que lanzar el comando siguiente desde nuestro cluster ONTAP destino:

Creamos la relación de peering entre las SMVs. Es necesario especificar el tipo de aplicación.
Este comando crea una petición de peering que es necesario que aceptemos en el sistema ONTAP origen:
La petición de peering de SVMs pendieente en origen.
La petición de peering de SVMs pendieente en destino.
Para aceptar la petición de peering solo tenemos que lanzar el comando vserver peer accept:

Aceptamos la petición de peering entre las SVMs en el sistema ONTAP origen.
Tras lanzarlo podemos comprobar cómo la relación entre ambas SVMs pasa a estado peered.

Relación entre SVMs en estado peered.
En uno de los posts siguientes de esta serie veremos como podemos crear la relación de SnapMirror en un solo paso con el comando snapmirror protect, pero creo que para entender mejor ese comando y todo lo que implica, vamos a crear la relación de SnapMirror paso a paso.

Crear una relación de SnapMirror entre dos clusters implica realizar una serie de pasos simples los cuales, en resumen, son los siguientes:
  1. Es necesario crear en el sistema destino un volumen que contendrá los datos del volumen origen. Cómo es lógico el tamaño del volumen destino debe ser igual o superior al de origen.
  2. Creamos una programación o schedule para especificar cuando queremos que se realicen las actualizaciones de los datos entre los volúmenes de la relación SnapMirror.
  3. Especificamos la política de replicación que queremos emplear. De esta manera establecemos que snapshots queremos transferir entre los clusters y como queremos hacerlo.
  4. Creamos la relación de SnapMirror entre los volúmenes, en función de los pasos anteriores.
  5. Inicializamos la relación de SnapMirror, realizando la copia de datos inicial que se denomina transferencia baseline.
Siguiendo los pasos anteriores, si quiero proteger el volumen VOL_nfsserver1_datos de la SVM SVM_nfsserver1, primero debo crear un volumen en el sistema destino. Por ejemplo, podemos hacerlo del siguiente modo y usando un nombre descriptivo que nos permita distinguir rápidamente que es un volumen replicado. Además es importante indicar que el tipo de volumen es DP, indicando así que se va a utilizar para destino de SnapMirror:

Creamos el volumen destino de tipo DP.
Aunque en este ejemplo no lo hemos especificado, el volumen puede ser thin provisioned, con lo que solo tenemos que especificar la opción space-guarantee a none en el momento de crearlo.

Ahora establecemos la programación de la tarea de replicación, para lo cual podemos crear uno o usar uno de los ya predefinidos. Suponiendo que necesitamos que nuestros datos se repliquen de lunes a viernes desde las 8:00, cada 3 horas, hasta las 23:00 horas, nuestro schedule podría ser el siguiente:

Creamos la programación de nuestra tarea de replicación.
Este schedule lo crearemos en el cluster destino ya que, es importante recordar que SnapMirror siempre se inicia en el sistema ONTAP destino el cual hace un pull de los datos desde el sistema origen.

Ahora necesitamos crear la política de replicación o bien usar una de las predefinidas. En este caso, y por sencillez, vamos a usar la política MirrorAllSnapshots, mediante la cual SnapMirror trnasferirá todos los snapshots, tanto en la inicialización cómo en las actualizaciones. Así conseguimos que los snapshots que realice el sistema ONTAP origen también se transfieran al sistema destino.

Con los datos anteriores, el comando que debemos utilizar en el cluster destino para establecer la relación de SnapMirror es el siguiente:

Creamos la relación de SnapMirror entre los dos volúmenes.
Ahora podemos ver que la relación de SnapMirror esta creada pero no inicializada, todavía no se ha transferido ningún dato entre ambos volúmenes:

La relación de SnapMirror creada pero no inicializada.
Para inicializarla es necesario realizar la primera copia, llamada baseline transfer, entre ambos volúmenes. De esta manera todos los datos del volumen, así como todos los snapshots existentes, se transferirán al volumen destino ya que la política que hemos especificado es MirrorAllSnapshots. Para lanzar la transferencia baseline, y por tanto inicializar de forma efectiva la relación de SnapMirror, solo tenemos que lanzar el siguiente comando desde el sistema ONTAP destino:

Lanzamos la transferencia baseline entre ambos volúmenes.
Esta transferencia inicial tardará más o menos timepo, dependiendo de la cantidad de información que deba transferir.

Proceso de inicialización de la relación SnapMirror.
Una vez terminado el proceso de transferencia inicial, podremos comprobar que la relación SnapMirror ha pasado a estado Snapmirrored y, más importante todavía, que todos los datos, así como los snapshots del volumen origen, se han replicado al volumen destino.

La relación SnapMirror ya establecida entre ambos volúmenes.
Lista de snapshots en el volumen destino.
Con esto ya hemos establecido la relación de SnapMirror entre nuestros dos sistemas ONTAP y hemos protegido un volumen. En las siguientes entradas de esta serie veremos más en detalle los tipos de políticas existentes, como establecer relaciones de DR para SVMs completas y como usar los datos replicados en caso de desastre.

sábado, 8 de junio de 2019

Configurando el servicio autofs

Recientemente he tenido que revisar unos sistemas en los que se emplea automount. Mientras los revisaba me he dado cuenta que hacía demasiado tiempo que no me pegaba con ello, así que aquí va un pequeño recordatorio de cómo funciona y como se configura el servicio automount.

El servicio automount nos permite montar de manera automática recursos compartidos por NFS en cualquier cliente Linux. El objetivo de este servicio es que se realice la petición de montaje al servidor, y posterior montaje del recurso, en el momento en el que se intente acceder al mismo por primera vez.

El nombre del paquete que debemos instalar para disponer de este servicio es autofs, el cual proporciona, además de los binarios necesarios, los siguientes fucheros de configuración:

Ficheros de configuración de automount.
De forma rápida, el fichero autofs.conf contiene todas las opciones de configuración, siendo la más importante la definición del fichero denominado mapa maestro. Por defecto, el fichero que contiene el mapa maestro de autofs es siempre auto.master, que indica que directorios están controlados por el servicio autofs y en los que se montarán los recursos compartidos por servidores remotos. Básicamente podemos decir que es un fichero en el que, cada entrada de directorio apunta al fichero correspondiente con la configuración necesaria.

Siguiendo la configuración por defecto, en la que se configura el directorio /misc mediante el fichero auto.misc, podemos establecer una configuración simple como la siguiente:

Configuración básica auto.misc.
Con esta configuración, lo que le estamos diciendo al servicio autofs, es que cada vez que se realice un acceso a las rutas /misc/dir1 o /misc/dir2, debe montar por NFS los directorios correspondientes compartidos por el servidor NFS indicado.

Una de las características interesantes, y cuando se nos olvida como funciona autofs también un poco desconcertante, es que autofs se encargará de crear los directorios necesarios en caso de que no existan.

Siguiendo con el ejemplo que nos ocupa, el directorio /misc que estamos configurando no existe por defecto en el sistema de archivos:

El directorio misc no existe por defecto.
Al arrancar el servicio veremos que el directorio /misc aparece, ya que autofs lo creará para empezar a realizar el montaje de los directorios remotos en cuanto se realice cualquier acceso a los mismos:

El directorio /misc es creado por autofs cuando arranca.
Podemos comprobar dos puntos importantes:
  • Si no se realiza ningún intento de acceso a las rutas que es necesario montar, el directorio /misc está vacio.
  • No podemos crear ningún fichero o subdirectorio dentro de /misc aunque seamos el usuario root.

En cuanto se realice un acceso a uno de los subdirectorios que hemos configurado, autofs lo creará y montará el directorio compartido por NFS:

Montaje de directorios mediante autofs.
Por tanto, y en resumen, mediante automount podemos acceder a rutas remotas exportadas mediante NFS bajo demanda, una solución que en determinadas ocasiones puede ser de mucha utilidad.

Ah, pero espera, que a mi no me gusta el directorio /misc, ¿no puedo cambiarle el nombre? Por supuesto que si. Para esto solo tenemos que cambiar la definición del directorio en el fichero auto.master. Así, por ejemplo, si queremos que autofs monte los recursos remotos en el subdirectorio /DATA, lo que haremos será lo siguiente:

Configuración de fichero auto.master.
Y el contenido del fichero auto.data, será similar al siguiente:

Configuración del fichero de mapa auto.data.
Cómo vimos con la configuración para el directorio /misc, el directorio /DATA no aparecerá en nuestro sistema de archivos hasta que no arranquemos el servicio autofs y, una vez arrancado, este montará los subidrectorios correspondientes cuando se realice el primer acceso a los mismos.

El directorio DATA tras arrancar el servicio autofs.
Montaje de los subdirectorios bajo demanda.

sábado, 18 de mayo de 2019

ELK - Introducción a Logstah III

En el post anterior sobre Logstash, vimos que los dos filtros más utilizados para registros de tipo texto son dissect y grok. Usando syslog_generator, el cual os recuerdo que tenéis disponible aquí por si queréis usarlo, creamos entradas de tipo fijo para el syslog de una máquina donde habíamos instalado filebeat, las enviamos a Logstash y las procesamos con un filtro dissect para obtener los campos que nos interesaban.

En esta entrada usaremos grok sobre el mismo tipo de registros, enviados también desde un sistema con filebeat, para poder realizar la misma tarea.

Primero es importante recordar que grok es perfecto para procesar registros de tipo texto no estructurados, es decir, para aquellos casos en los que se generan registros con una estructura variable de línea a línea. Además, al basarse en expresiones regulares, es posible extender los patrones reconocidos creando patrones para poder usarlos en cualquier pipeline de Logstash que utilice grok.

La sintaxis básica para definir un filtro con grok se define del siguiente modo:
Sintaxis básica para un filtro grok.
Y esta definición, ¿que quiere decir exactamente? Como ya hemos visto, a diferencia de dissect, grok se basa en expresiones regulares ya existentes, identificadas con un nombre que se corresponde con el campo PATTERN, las cuales asignaremos al campo NAME que necesitemos cuando la coincidencia sea satisfactoria. Grok incluye un buen número de patrones ya listos para su uso, cuya lista puede consultarse aquí aunque pueden incluirse nuevos patrones, si es necesario, modificando la configuración de Logstash añadiendo un fichero con la definición de nuevos patrones.

Al definir filtros usando grok, este busca coincidencias desde el principio de cada línea de tecto hasta el final de la misma intentando mapear todo el evento, o hasta no encontrar una coincidencia. En función de los patrones, es posible que grok procese los datos varias veces, por lo que es más lento y requiere más recursos que filtros realizados con dissect.

Para nuestro ejemplo, vamos a seguir con líneas de registro que son perfectas para el uso de dissect, ya que no varían y presentan los mismos campos y delimitadores en todos los casos, pero así podremos comprobar que podemos usar indistintamente cualquiera de los dos tipos de filtro.

Las líneas a procesar presentan la siguiente estructura:
Líneas de registro a procesar y definición de campos.
Al igual que hicimos con el filtro dissect, vamos a empezar con un filtro simple y luego iremos complicándonos un poco más. 

Para empezar, vamos a hacer uso de una herramienta muy útil disponible en Kibana y que nos permite probar nuestros filtros grok, con lo que podemos analizar si el resultado obtenido es o no correcto antes de configurarlo en nuestro pipeline. Podemos acceder al Grok Debugger desde la sección Dev Tools de Kibana:

Grok Debugger disponible en Kibana.
Esta herramienta es de gran ayuda ya que, al tratarse de expresiones regulares, es fácil que tengamos que probar bastantes veces antes de dar con la configuración correcta del filtro. Por tanto, para empezar, vamos a probar a quedarnos solo con 4 campos de la línea message, en concreto con la fecha de syslog, el hostname, el nombre del programa que ha generado la línea y el mensaje generado por syslog_generator. Para esto, el filtro grok a aplicar sobre la línea message será:

Filtro grok inicial.
Como vemos en el filtro, puede que en algunos casos sea necesario definir los espacios que existan en el registro de texto, dados por el patrón SPACE. Teniendo esto en cuenta, el filtro está estableciendo lo siguiente:
  • Almacenar en el campo logger_date el texto encontrado que haga match con el patrón SYSLOGTIMESTAMP.
  • Almacenar en el campo logger_hostname la siguiente palabra encontrada.
  • Almacenar en el campo logger_program los siguientes datos encontrados.
  • Almacenar en el campo logger_message todo lo que queda hasta el final de la línea, lo cual especifcamos con el patrón GREEDYDATA.
Usando el grok debugger con este filtro, sobre una línea de ejemplo recibida en Logstash, podemos simular el resultado y comprobar que, al trabajar con expresiones regulares, los resultados que buscamos son un poco más complicados de obtener de lo que esperábamos:

Usando Grok Debugger con el filtro inicial.
Como vemos en la imagen anterior, el campo logger_program se queda vacio, es decir, que la definición %{DATA:logger_program} que hemos hecho para ese campo, no nos devuelve ningún contenido. Si vemos la definición del patrón DATA disponible, vemos que equivale a .*? lo cual establece que debe devolver la menor cantidad posible de caracteres (?) de cualquiera de los caracteres (.*) anteriores. Básicamente, al especificar que se devuelva la menor cantidad posible de caracteres con ?, no está devolviendo ningún valor en dicho campo.

La forma de corregirlo sería usar un filtro como el siguiente, en el cual fijásemos que haga match con cualquier palabra y todos aquellos caracteres que no sean un espacio:

Filtro grok modificado para incluir de forma correcta el cmapo logger_program.
Al probarlo en el depurador de grok de Kibana, vemos que ahora obtenemos los campos que queremos de forma correcta:

Comprobación de que el nuevo filtro es correcto.
La sintaxis empleada es la que podemos usar cuando la expresión regular que necesitamos, no se encuentra en los patrones disponibles incluidos con el plugin de filtro grok de Logstash. Podemos usarla directamente en la definición del filtro o, si queremos usarla de manera habitual, podemos extender la biblioteca de patterns incluyendo las nuestras y asignándoles un nombre para poder usarlas en múltiples pipelines.

Modificamos el fichero de configuración del filtro grok con esta definición de filtro y arrancamos Logstash. La salida que obtenemos en este caso es la siguiente:

Salida de Logstash con el filtro grok correcto.
Podemos ver como los cuatro campos que hemos definido están en la salida, además del resto generados por el plugin de entrada filebeat. Además, el campo message del registro ya no aparece al eliminarlo con la opción remove_field.

Ahora ya solo nos queda procesar el campo logger_message para obtener el resto de campos que necesitamos, para lo cual podríamos aplicar un filtro como el siguiente:

Filtro grok procesando todos los campos de message.

Cuando lo aplicamos, la salida de Logstash ya nos muestra los campos necesarios como podemos ver en la siguiente imagen:

Salida de Logstash con el filtro final grok.
En resumen, está claro que grok nos da mucha más potencia para procesar registros de tipo texto, pero con un coste de dificultad añadido debido al uso de expresiones regulares. En general, el uso de dissect o grok, dependerá de la estructura de los registros de texto que sea necesario procesar y, en algunos casos, será conveniente combinar ambos en el mismo pipeline.

En próximas entradas crearemos más de un pipeline, para ver como trabajan conjuntamente y enviaremos los datos a Elasticsearch para podeer realizar búsquedas sobre los datos recibidos.

Como referencia para el estudio de expresiones regulares, recomiendo usar la página https://regex101.com/ en la cual, además de poder probar expresiones regulares, nos explica cada una de las opciones y operadores que podemos usar para la construcción de expresiones regulares complejas.

sábado, 4 de mayo de 2019

ELK - Introducción a Logstah II

Como ya vimos en el post anterior sobre Logstash, este elemento de la pila ELK nos permite recibir datos, procesarlos mediante pipelines y luego enviar dichos datos a otros destinos. Resumiendo el post anterior, los pipelines presentan una estructura que, de forma muy simplificada, podemos ver del siguiente modo:

Definición básica de un pipeline.
Utilizando esta estructura para configurar un pipeline simple, vimos cómo recibir entradas de syslog de un sistema en el cual instalamos filebeat y enviamos dicha salida directamente a STDOUT, sin aplicar ningún filtro a los datos recibidos. Hoy veremos cómo podemos procesar esa información, aplicando filtros a los datos recibidos desde nuestro cliente, para obtener los campos que nos resulten útiles.

Recordando la configuracion que establecimos, al lanzar el generador de entradas de syslog para el tipo FIXED con el pipeline sin filtro, la salida que nos muestra logstash en stdout es como la siguiente:

Salida de Logstash sin filtro a STDOUT.
El objetivo es procesar el campo message, aplicando filtros para poder obtener los valores que realmente necesitamos y que luego podremos enviar a Elasticsearch.

Logstash dispone de múltiples plugins para realizar el filtrado de los datos recibidos, siendo dos de los más usados para registros de tipo texto dissect y grok. Para consultar la lista de plugins de filtro recomiendo visitar la página de Elastic y consultar la documentación disponible sobre Logstash.

Usar dissect o grok dependerá de como sea la estructura de los datos que debemos procesar, más concretamente de si la estructura de los datos está claramente definida. En general:
  • Es recomendable usar dissect cuando los registros que recibe logstash siempre contienen los mismos campos separados por delimitadores. Este filtro es muy rápido y es el más sencillo de configurar.
  • El filtro grok es ideal para los casos en los que los registros de texto recibidos varían, ya que usa patrones de expresiones regulares para hacer coincidir campos y delimitadores. Este filtro es más complejo de configurar y consume más recursos.
Es importante tener en cuenta que los pipelines de Logstash nos permiten aplicar múltiples filtros a la misma entrada, con lo que podremos pasar los registros recibidos por un filtro para posteriormente aplicarle otro diferente.

Teniendo en cuenta el campo message recibido en cada registro, esta claro que el filtro ideal en este caso es dissect ya que tenemos varios campos separados por delimitadores de longitud fija. Por tanto, podríamos hacer algo como lo siguiente:

Procesado básico de campo message.
El filtro dissect no permitirá mapear cada uno de esos campos y asignarles el nombre que más nos convenga, con lo que podríamos hacer un filtro básico como el siguiente:

Filtro inicial empleando dissect.

Si analizamos el filtro anterior vemos los siguientes puntos importantes:
  • Un campo del filtro es la clave que hay entre los caracteres %{}, con lo que en este caso establecemos 6 campos. Cada uno de estos campos se corresponde con los 6 campos que hemos identificado sobre el mensaje recibido. Asignamos un nombre diferente a cada uno de ellos, según nuestras necesidades.
  • El delimitador es cualquier carácter entre los caracteres } y %{, siendo en este caso espacios.
  • Como el número de espacios después del campo timestamp_month es variable, añadimos el sufijo -> tras el nombre del campo.
  • Además, al emplear la opción remove_field, dissect eliminrá el campo message siempre que se produzca una coincidencia del filtro, con lo que eliminaremos el campo que procesamos de la salida dada.
Al aplicar esta configuración al pipeline de Logstash, la salida que obtenemos ahora es la siguiente:

Salida de Logstash con filtro dissect básico.

Al aplicar el filtro dissect, aparecen los nuevos campos que hemos definido en el filtro, además de todos los campos que genera el plugin de entrada de filebeat con la información del cliente remoto y ha desaparecido el campo message que hemos procesado.

Ya hemos avanzado en el procesado de los registros de syslog que recibimos, pero suponiendo que la información que necesitamos es realmente la que está en el campo logger_message, podemos cambiar el filtro, asignar unos nombres más claros a los campos que nos interesan y trocear el campo logger message con el siguiente filtro:


Ampliamos el filtro dissect al campo logger_message.

Con lo que la nueva salida de logstash es la siguiente:

Salida de Logstash tras procesar el campo logger_message.
Como vemos en la salida anterior, ahora tenemos un conjunto de campos identificados con el prefijo logger_ así como los campos identificados con el prefijo timestamp_. Suponiendo que los campos con el prefijo logger_ de fecha y hora son los que necesitamos además de unificarlos todos en un solo campo y eliminando los campos con el prefijo timestamp_, el filtro final que hacemos con dissect sería el siguiente:

Eliminamos campos y unificamos los campos de fecha en uno solo.
Al aplicar el filtro anterior, la salida de Logstash es la siguiente:

Salida de Logstash eliminando campos timestamp_ y unificando campos de fecha.
Cómo podemos ver en la salida anterior, ya no aparece ningún campo timestamp_ y todos los campos de fecha, con el prefijo logger_, están unificados en el campo logger_timestamp. Para conseguir esto, solo debemos tener en cuenta los siguientes puntos sobre el filtro que hemos definido:
  • Para eliminar totalmente un campo de la salida del filtro dissect, solo tenemos que prefijar el nombre del campo con el carácter ?. Como vemos en el filtro anterior, he incluido el carácter ? a todos los campos timestamp_ para eliminarlos.
  • Si queremos concatenar campos, basta con repetir el nombre del campo añadiendo el carácter + delante del nombre. Así, en el filtro aplicado, podemos ver como todos los campos que incluyen información de fecha tienen el nombre logger_timestamp correspondiente al primer campo de fecha. El resto de campos que queremos concatenar, repiten el nombre del campo inicial incluyendo el caracter + delante del nombre, indicando así con que campo deben concatenarse.
En la próxima entrada sobre Logstash analizaremos como podemos realizar el mismo filtrado usando grok el cual, como veremos, es un poco más complejo y a la vez más potente.

sábado, 6 de abril de 2019

ELK - Introducción a Logstash

Con este artículo voy a comenzar una nueva sección dedicada a estudiar la pila ELK. Vamos a empezar con una pequeña introducción a Logstash, lo que nos permitirá entender su función y cómo configurar un pipeline sencillo.

Bueno y ¿que es logstash? pues es el elemento de la pila ELK que permite procesar datos para luego enviarlos a Elasticsearch, para indexarlos y realizar tareas de análisis y búsqueda.

Logstash funciona mediante pipelines, siendo cada uno de ellos los que realizan, en su conjunto, todo el proceso. Un pipeline de logstash tendrá uno o más plugins de entrada, que recibirán los datos que luego se colocarán en una cola interna. Esta cola es por defecto pequeña y se almacena en memoria, pero puede configurarse para que se almacene en disco y ampliar su tamaño.
Los datos se leen de esta cola interna y se procesan a través de plugins de filtro, que se configuran en secuencia y estos datos procesados se envían a los plugins de salida correspondientes, que formatean y envían los datos a Elasticsearch o a otro destino como veremos. Todo esto podemos verlo, más o menos, del siguiente modo:
Estrucutura lógica de un pipeline.
Cada uno de los pasos que forman el pipeline nos permitirá especificar lo siguiente:
  • Plugin de entrada, mediante los cuales especificartemos el origen de nuestros datos. Este plugin puede especificar como fuentes de origen ficheros, stdin, topics de Kafka, varias APIs públicas disponibles, syslog, los beats de Elastic, etc. En este artículo usaremos como plugin de entrada filebeat, para recibir datos de los ficheros de una máquina cliente.
  • Filtros, los cuales nos permitirán procesar la información recibida y modificarla como más nos interese, creando los campos que necesitemos para su posterior procesado.
  • Plugins de salida, que nos permitirán especificar donde enviará Logstash los datos procesados. Al igual que en el caso de los plugins de entrada, hay disponibles una gran cantidad de plugins de salida para enviar los datos a una base de datos MongoDB, a un topic de Kafka, a un socket TCP, a stdout, a un servidor Zabbix, etc. En este artículo enviaremos la salida a stdout, para poder comprobar como logstash procesa la información y la modifica de acuerdo a los filtros que configuremos.
Para generar información, y salvo que dispongas de una gran cantidad de máquinas generando logs que procesar, he creado un pequeño script en Python que genera entradas de syslog que enviaré a Logstash y utilizaré a lo largo del artículo. Si quieres usar este script, echarle un ojo, modificarlo o lo que sea, puedes descargarlo de GitHub.

Como es lógico debemos instalar Logstash, en la máquina que queremos usar como servidor y filebeat en la máquina cliente, auunque pueden estar instalados en la misma máquina. Para la instalación, recomiendo consultar la documentación de instalación proporcionada por Elastic, aunque disponen de sus propios repositorios con lo que es muy sencillo instalarlos.

Filebeat es un agente de Elastic para el envio del contenido de ficheros a los diferentes elementos de la pila ELK y que veremos más detenidamente en posteriores artículos. Por tanto, para empezar, configuramos el cliente, para lo cual es necesario modificar el fichero de configuración filebeat.yml del módulo filebeat. Este módulo dispone de un fichero de configuración cuya ruta por defecto, si instaláis el paquete oficial de Elastic, está en la ruta /etc/filebeat.

Ficheros de configuración de filebeat.
Lo que necesitamos especificar en el fichero de configuración es la ruta de los ficheros que van a enviarse y cual es el destino al que deben enviarse. En la configuración por defecto, filebeat dispone de un solo input, que se denomina log, y especifica que deben leerse los ficheros de la ruta /var/log, con lo que habilitamos dicho input, ya que vamos a enviar registros de syslog:

Configuración de input de filebeat.
La línea include_lines permite especificar que expresiones regulares deben contener las líneas de los ficheros de log para que se envíen al destino que luego especifiquemos. Esta es una manera de realizar un primer filtrado en origen, asegurándonos que solamente se enviarán aquellas líneas de nuestro interés. En el caso que nos ocupa, las lineas de syslog a enviar serán aquellas que contengan la cadena SYSLOG_GEN, como puede verse en la imagen anterior.

Por último solo es necesario habilitar la salida a la que filebeat enviará los datos, para lo cual debemos configurar la sección Logstash output del fichero indicando el nombre o dirección IP del host donde se encuentre instalado Logstash:

Configuración de output de filebeat.
Como se ve en la imagen anterior, podemos enviar la salida de filebeat directamente a un cluster de Elasticsearch pero es muy importante tener en cuenta que solo puede configurarse un output para filebeat. En nuestro caso lo enviamos al servidor donde se encuentra Logstash.

Una vez configurado filebeat en el cliente, pasamos a configurar Logstash definiendo un pipeline muy sencillo, en el cual solo vamos a tener un plugin de entrada, un filtro y un plugin de salida. 
En general los pipelines de Logstash se crean sobre uno o más ficheros de configuración, los cuales pueden estar en un directorio único que especificaremos en el fichero de configuración /etc/logstash/pipelines.yml, con lo que Logstash los concatenrá al arrancar, usando todos los pipelines simultáneamente.

Ficheros de configuración de Logstash.
Cómo el fichero pipelines.yml apunta a la ruta /etc/logstash/conf.d, crearemos los ficheros de configuración de los pipelines en dicho directorio
Ficheros de configuración de pipelines.
Teniendo en cuenta como es la estructura lógica que hemos visto de un pipeline, la estructura básica del fichero de configuración será:

Definición básica de un pipeline.
Para este caso, nuestro plugin de entrada será filebeat, comenzaremos sin especificar un filtro, lo cual es válido y el plugin de salida será stdout. En ese caso, el fichero de configuración del pipeline será como se muestra en la siguiente imagen:

Pipeline básico sin filtro.
Como vemos, el plugin de entrada especifica que para este pipeline Logstash recibirá información de filebeat en el puerto 5044, que no se va a aplicar ningún filtro y que el plugin de salida es stdout, usando un codec para mostrar la salida de un forma más entendible.

Con esta configuración podemos arrancar el servicio filebeat y el generador de syslog en el cliente y, para arrancar logstash usaremos un comando como:
 
Arranque de Logstash especificando la configuración de pipeline.
Desde el cliente, el generador de entradas de syslog está enviando registros a logstash, a través de filebeat, como los siguientes:
 
Salida de syslog_generator.
Con esta configuración, syslog_generator crea una línea de syslog fija con un identificador y una dirección IP ficticia, simulando así una línea de syslog básica.
 
Con esto ¿que nos muestra Logstash en stdout? pues una salida como la siguiente:
 
Salida de Logstash.
Si revisamos detenidamente la imagen anterior, vemos que Logstash nos muestra una serie de campos, formados por parejas clave-valor, muchos de los cuales genera a partir de la información que recibe de filebeat. Al usar beats como plugin de entrada, logstash aplica por defecto un codec específico mediante el cual nos muestra el hostname de origen, la versión de sistema operativo, timestamp, plataforma, etc.
 
Como también podemos ver, hay un campo específico que contiene el mensaje completo generado por syslog_generator y que se guarda en los ficheros de log del cliente a través de su syslog local. Como vemos en la salida, el campo es:
 
Campo message recibido en Logstash desde filebeat.
Debemos tener en cuenta que, al usar filebeat especificando como origen /var/log, este envía el contenido de los ficheros de dicha ruta a la salida configurada, en este caso Logstash. El campo message se corresponde con una línea de syslog, que contiene el registro de syslog generado por el script syslog_generator que estamos lanzando en el cliente.
 
Con esta configuración tan simple, hemos visto como podemos enviar entradas de ficheros de un cliente a Logstash mediante el uso de filebeat. Cómo la idea es procesar dichas entradas, aplicando filtros, en los próximos posts veremos como crear dichos filtros con dissect y con grok para poder extraer los datos que nos interesan de ese campo message.