viernes, 5 de marzo de 2021

ELK Stack: Iniciación

En este post, vamos a ver el funcionamiento de ELK orientado a la gestión de logs en una aplicación Java, y para ello usaremos la aplicación con Apache Camel que hemos visto otras veces. 

ELK, es como se conoce a un conjunto de aplicaciones, concretamente a: Elasticsearch, Logstash y Kibana. Todas ellas son herramientas open source desarrolladas por Elastics. Las cuales permiten recoger datos de cualquier tipo de fuente y en cualquier formato para realizar búsquedas, análisis y visualización de los datos en tiempo real. 

  • Logstash es un pipeline de procesamiento de datos del servidor que consume datos de una multitud de diferentes fuentes y de forma simultanea. Estos datos son transformados y enviados a un repositorio de datos.  
  • Elasticsearch es un motor de búsqueda y análitica de los datos que tiene almacenados.
  • Kibana nos permite crear gráficos y cuadros de mando para los usuarios, en base a la información que tenga Elasticsearch.
Actualmente este stack de aplicaciones también lo compone Beat. Pero de el, no hablaremos en este post. 

A lo largo del post, veremos como podemos montarlo, como configurarlo y explotar la información de una aplicación Java. El método más sencillo para tener el stack disponible es a través de Docker Compose. 

Empezamos por Elasticsearch. Es el componente más importante y el que necesita más recursos. En entornos productivos se recomienda que se cree un cluster que mantenga la alta disponibilidad y un pleno rendimiento. Pero para nuestro ejemplo, como característica principal, contará unicamente con un nodo. Por lo demás será una configuración común de Docker:
  • Indicaremos una carpeta local como volumen del contenedor, que nos permitirá mantener la información aunque borremos el contenedor. 
  • Una red común a usar por los contendores.
  • Los puertos expuestos: 9200 y 9300.
  • Un healthcheck que compruebe si la instancia sigue funcionando correctamente. Para ello simplemente comprobaremos que en el puerto expuesto se obtiene una respuesta al invocarlo.
version: "3.7"
services:
    elasticsearch:
        image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2
        container_name: elasticsearch
        hostname: elasticsearch
        environment:
            - node.name=elasticsearch
            - cluster.name=es-docker-cluster
            - discovery.type=single-node
        volumes:
            - ./data:/usr/share/elasticsearch/data
        networks:
            - elastic
        ports:
            - 9200:9200
            - 9300:9300
        healthcheck:
          test: curl -s https://localhost:9200 >/dev/null; if [[ $$? == 52 ]]; then echo 0; else echo 1; fi
          interval: 30s
          timeout: 10s
          retries: 5
networks:
  elastic:
    driver: bridge

El siguiente apartado la creación del contenedor de Kibana. Esta herramienta leerá la información almacenada en Elasticsearch, por tanto tiene que saber donde se encuentra esta ubicada. Pero esto no se indicará en el Docker Compose, que para esta herramienta tendrá una configuración muy básica. Sino que se indicará en el fichero de configuración YAML, que indicamos como volumen de entrada.

    kibana:
        image: docker.elastic.co/kibana/kibana:7.10.2
        container_name: kibana
        hostname: kibana
        volumes:
        - ./config/kibana.yml:/usr/share/kibana/config/kibana.yml
        ports:
          - "5601:5601"
        networks:
          - elastic
        depends_on:
          - elasticsearch

En el fichero de configuración es donde deberemos indicar como se llama el servicio, en que IP se encuentra y lo que es más importante, donde se encuentra Elasticsearch.

server.name: kibana
server.host: 0.0.0.0
elasticsearch.hosts: [ "http://elasticsearch:9200" ]

Para terminar la configuración de nuestro Docker Compose, ya solo nos quedaría configurar Logstash. Esta configuración será un poco más compleja y se compone de los siguientes apartados:

  • Un volumen para indicarle el fichero en el cual configuraremos Logstash.
  • Un volumen donde configuraremos los distintos pipelines que nos servirán como la entrada de los datos a ser enviados a Elasticsearch por Logstash. 
  • Un volumen que Logstash tenga acceso a los logs creados por la aplicación
  • Los puertos que expone el servicio.
  • La configuración de memoria de la JVM a utilizar.
    logstash:
      image: docker.elastic.co/logstash/logstash:7.10.2
      container_name: logstash
      hostname: logstash
      volumes:
        - ./config/logstash.yml:/usr/share/logstash/config/logstash.yml
        - ./logstash/pipeline:/usr/share/logstash/pipeline
        - /home/dsblanco/Documentos/logs:/tmp
      ports:
        - "5044:5044"
        - "5000:5000/tcp"
        - "5000:5000/udp"
        - "9600:9600"
      environment:
        LS_JAVA_OPTS: "-Xmx256m -Xms256m"
      networks:
        - elastic
      depends_on:
        - elasticsearch

El fichero logstash.yml nos permitirá configurar la herramienta. La cual no contendrá muchos valores, para este ejemplo. A igual que para Kibana, pondremos cual será nuestra IP y donde se encuentra Elasticsearch:

http.host: 0.0.0.0
xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch:9200" ]

En la carpeta pipeline se almacenarán diferentes ficheros de configuración que permitirán a Logstash gestionar diferentes fuentes de información y enviarlos a Elasticsearch. Estos ficheros se dividen en tres apartados:

  • Input: Donde se indica cual va a ser el origen de los datos. Para nuestro caso serán los ficheros log generados por la aplicación. Aquelos que fueron montados en el volumen.
  • Filter: Donde podremos si queremos realizar alguna operación de filtrado sobre los datos. Para nuestro ejemplo no realizaremos ninguno. 
  • Output: Donde indicamos el repositorio al que lo vamos a mandar. En esta caso Elasticsearch. 

input {
        file {
        path => "/tmp/*.log"
        codec => "json"
        type => "camelELK"
    }
}

output {
    if [type]=="camelELK" {
         elasticsearch {
             hosts => [ "elasticsearch:9200" ]
             index => "camelELK-%{+YYYY.MM.dd}"
        }
    }
}

Una vez que tenemos el Docker Compose configurado, podemos arrancarlo y comprobar que funciona de forma correcta al acceder a la ruta http://localhost:9200. 

El siguiente paso será la configuración de nuestra aplicación. Lo más importante sobre esto es que usaremos logback. Y que a través de la clase de la clase LogstashEncoder se nos permitirá indicar ciertas caracteristicas. Como puede ser el formato de la salida, añadir campos personalizados o excluir determinados paquetes. 

<configuration>
	<appender name="STASH"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>/home/myUser/logs/apache_Camel_ELK.log</file>
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>>/home/myUser/logs/apacheCamelELK.%d{yyyy-MM-dd}.log</fileNamePattern>
			<maxHistory>7</maxHistory>
		</rollingPolicy>
		<encoder class="net.logstash.logback.encoder.LogstashEncoder">
			<customFields>{"appname":"Apache Camel ELK"}</customFields>
		</encoder>
	</appender>
	<root level="INFO">
		<appender-ref ref="STASH" />
	</root>
</configuration>

Arrancaremos la aplicación y realizaremos varias consultas, por ejemplo a http://localhost:9090/book/1. 

El siguiente paso será configurar Kibana para poder visualizar todos los datos. Para ello accedemos a http://localhost:5601/app/home. Una vez dentro tenemos que realizar dos pasos. 

  • Crear un indice
Creamos un indice, accediendo primero a la opción de menú Manage Spaces y luego en el menú izquierdo a la opción Index Management. Una vez en esta pantalla, creamos el indice asociado al identificador que indicamos en el pipeline de logstash. 


  • Visualizar la información
Desde la opción Discover del menú principal podemos acceder a los logs que esta mandando Logstash a Elasticsearch. 



Con esto ya tendremos monitorizada nuestra aplicación a través de los logs. Y podremos hacer una mejor utilización de los mismos a través de Kibana. Podemos realizar búsquedas en función de la aplicación que nos envia la información, del contenido del mensaje o por rangos de fechas y horas. Algo fundamental cuando tu aplicación tiene cierta envergadura o produce una gran cantidad de mensajes. 

Si quieres ver todo el ejemplo, lo tienes aquí

No hay comentarios:

Publicar un comentario