ELK

Analysez, recherchez et visualisez vos données

Je veux découvrir ELK
Amenez moi à la partie technique

Qu'est-ce que ELK ?

Elasticsearch - Logstash - Kibana

Pourquoi utiliser ELK ?

Votre application produit des données qui peuvent rapidement devenir conséquentes. Plus elles sont nombreuses et plus l'analyse que vous pourrez en faire sera lente. ELK va vous permettre de :

  • Centraliser vos données
  • Parser et récupérer uniquement les informations qui vous sont utiles
  • Visualiser vos logs et identifiez l'information en un rien de temps !

Logstash

  • Centraliser des données de tout type
  • Normaliser et regrouper en une sortie unique des données aux schémas et formats variables
  • Modifier les formats de logs en ajoutant/modifiant/supprimant des champs
  • Ajouter et utiliser des plugins très facilement

Elasticsearch

  • Moteur de recherche distribué, évolutif et hautement disponible
  • Capacité de recherche et d'analyse de données en temps réel
  • Fonctionne à base d'API Restful

Kibana

  • Plateforme flexible d'analyse et de visualisation
  • Interface intuitive pour une grande variété d'utilisateurs
  • Possiblité de partager en un rien de temps les tableaux de bords

Quelques conseils

Pensez à bien formater vos logs, la construction de vos filtres n'en sera que plus simple.
Vous pouvez aussi ajouter un identifiant de suivi au sein de votre application. Il vous permettra, par exemple, de suivre les actions associées à une requête. En effet, grâce à Elasticsearch et Kibana, vous pourrez afficher les logs contenant cet id.

Comment tester rapidement ELK?

Docker ELK générique

Je vous proprose d'utiliser le Docker ELK générique que j'ai mis en place et disponible ici :
L'intérêt ici est de pouvoir déployer rapidement une stack ELK et ne se concentrer que sur les fichiers de configurations Logstash. Il ne vous restera qu'à suivre le tutoriel.

Cas d'usage et contexte

Architecture globale

A l'intérieur de la bulle de votre projet se trouvent les machines et les services. Chaque service produit des logs qui sont transférés vers un concentrateur. Un shipper va envoyer les logs vers la plateforme ELK. Dans notre cas, nous utiliserons Rsyslog.

Au niveau du front...

Imaginons une requête produisant ce log :
127.0.0.1 - - [02/Jul/2015:00:13:48 +0200] "VboEgQpaiRAAAB0bBJgAAACD" "GET /sthing/ HTTP/1.0" 200 3175 17173 "-" "-"
"VboEgQpaiRAAAB0bBJgAAACD" correspond à un identifiant unique généré pour chaque requête entrante et permettant de suivre les actions correspondantes qui sont effectuées

Au niveau du middle...

Le serveur reçoit cette requête et produit le log suivant :
127.0.0.1 - - [02/Jul/2015:00:13:48 +0200] "VboEgQpaiRAAAB0bBJgAAACD" "GET /sthing/2 HTTP/1.1" 200 35

Enfin le logger de l'application produit ces 2 logs :
2015-07-02 00:13:48,804 - [VboEgQpaiRAAAB0bBJgAAACD] - [INFO] [Class.java:55] - Content
2015-07-02 00:13:48,885 - [VboEgQpaiRAAAB0bBJgAAACD] - [INFO] [Class.java:55] - Content

"VboEgQpaiRAAAB0bBJgAAACD" correspond à l'identifiant unique récupéré

Configuration Rsyslog

Maintenant imaginons une configuration Rsyslog permettant d'attribuer une étiquette à chaque log envoyé.
Les logs étant tous envoyés sur un même port, cette étiquette va nous servir plus tard à différencier les logs.

  • front-access: Pour les logs access produits au niveau du front.
  • middle-access: Pour les logs access produits au niveau du middle
  • middle-applicative: Pour les logs applicatifs

Configurations de Logstash

Avant de commencer

Il est conseillé d'utiliser un fichier de configuration différent pour chaque filtre. J'utilise, par exemple, cette convention :

  • De 01 à 09 pour les inputs (Exemple : 01-input.conf)
  • De 10 à 29 pour les filtres (Exemple : 10-filtre.conf)
  • A partir de 30 pour les outputs (Exemple : 30-output.conf)
Logstash va construire son chemin d'intégration en fonction de l'ordre des fichiers de configurations. Voilà pourquoi j'utilise une telle numérotation, permettant ainsi de déterminer l'ordre d'analyse.

Input

Voici le contenu de notre premier fichier de configuration. Il définit simplement un port sur lequel Logstash va recevoir les logs via Rsyslog


input {
	syslog {
		port => 5000
	}
}
								

Premier filtre

Ce premier filtre sert de prétraitement. Il va attribuer à chaque logs de nouveaux tags afin de simplifier la suite du travail avec Logstash et les recherches de logs dans Elasticsearch.


filter { 
	if [program] == "front-access" {
		mutate {
			add_tag=> ["front","access"]
		}
	}
	if [program] == "middle-access" {
		mutate {
			add_tag=> ["middle","access"]
		}
	}
	if [program] == "middle-applicative" {
		mutate {
			add_tag=> ["middle","applicative"]
		}
	}
}
								

Second filtre

Ce second filtre va s'occuper des logs access. L'intérêt d'avoir défini des tags précédemment est de pouvoir les groks similaires en un seul filtre. Par exemple ici, les logs access ont relativement le même format. Il est possible de définir notre filtre grok pour qu'il accepte plusieurs patterns.


filter{
	if "access" in [tags]{
		grok {
			match => { "message" => ["<pattern1> ","<pattern2>"]}
		}
		date {
			locale => "en"
			match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
			target => "@timestamp"
			timezone => "Europe/Paris"
		}
		mutate {
			remove_field => ["timestamp"]
		}
	}
}
								

Traitement des dates

La plupart des logs de production est en locale EN, tandis que nos postes sont en locale FR. Si l'horodatage est uniquement en numérique, que le format soit YYYY-MM-dd, MM/dd/YY ou autre ne pose pas de problème majeur, il suffit de bien créer sa regex. En revanche, pour des logs type Apache par exemple (date : 29/Aug/2014), le mois n'est pas au format numérique. La regex serait donc dd/MMM/YYYY, avec "MMM" interprétés selon la locale du poste. "Aug" ne correspond à aucun mois en locale FR, les logs sont donc datés arbitrairement par logstash à la date courante lors de l'évaluation. Pour remédier à cela, il faut utiliser l'attribut locale lors du parsing de la date.
De plus, il peut être intéressant faire correspondre le champ @timestamp avec la date figurant sur le logs. Ainsi, sur Kibana, le log apparaît bien à l'heure à laquelle il a été produit et non pas celle de son analyse par logstash.

Pattern 1

"%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{DATA:requestId}\" \"(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})\" %{NUMBER:response} (?:%{NUMBER:bytes}|-) %{NUMBER:responseTime:float} %{QS:referrer} %{QS:agent}"

  • %{IPORHOST:clientip}: 127.0.0.1
  • \[%{HTTPDATE:timestamp}\]: [02/Jul/2015:00:13:48 +0200]
  • \"%{DATA:requestId}\": VboEgQpaiRAAAB0bBJgAAACD
  • %{WORD:verb}: GET
  • %{NOTSPACE:request}: /sthing/
  • %{NUMBER:responseTime:float}: 17173

Pattern 2

"%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{DATA:requestId}\" \"(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})\" %{NUMBER:response} (?:%{NUMBER:bytes}|-)"

  • %{IPORHOST:clientip}: 127.0.0.1
  • \[%{HTTPDATE:timestamp}\]: [02/Jul/2015:00:13:48 +0200]
  • \"%{DATA:requestId}\": VboEgQpaiRAAAB0bBJgAAACD
  • %{WORD:verb}: GET
  • %{NOTSPACE:request}: /sthing/2

Dernier filtre

Ce dernier filtre va permettre de parser nos logs applicatifs


filter{
	if "applicative" in [tags]{
		grok {
			match => { "message" =>"<pattern3>"}
		}
		date {
			match => ["timestampiso", "yyyy-MM-dd HH:mm:ss,SSS"]
			target => "@timestamp"
			timezone => "Europe/Paris"
		}
		mutate {
			remove_field => ["timestampiso"]
		}
	}
}
								

Pattern 3

"%{TIMESTAMP_ISO8601:timestampiso} - \[%{DATA:requestId}\] - \[%{LOGLEVEL:loglevel}\] \[%{WORD:class}.%{WORD}:%{NUMBER:line:float}\] - (?m)%{GREEDYDATA:content}"

  • %{TIMESTAMP_ISO8601:timestampiso}: 2015-07-02 00:13:48,804
  • \[%{DATA:requestId}\]: VboEgQpaiRAAAB0bBJgAAACD
  • \[%{LOGLEVEL:loglevel}\]: INFO
  • %{WORD:class}: Class
  • %{NUMBER:line:float}: 55
  • (?m)%{GREEDYDATA:content}: Content

Et si vous avez des logs multilignes ?

Comme dans cet exemple :


2015-07-02 04:21:44,600 - [INFO] [Class.java:523] - Content [
line1
...]
									

Vous pouvez utiliser les codec multilignes en input :


codec => multiline { 
        pattern => "<pattern>"
        what => "previous"
        negate => true
    }
									
Ou le filtre multiligne

multiline {
        pattern => "<pattern>"
        what => "previous"
        negate => true
}
								

Avantages et inconvénients du codec multilignes

  • Concatène les logs directement en input
  • Non conçu pour recevoir différents logs sur le même port
  • Version actuelle : omet le dernier log multiligne d'un fichier

Avantages et inconvénients du filtre multilignes

  • Flushing automatique
  • Ne fonctionne pas bien si le flux de données entrant est trop important
  • Cependant, devrait fonctionner correctement pour l'analyse en temps réel.

Output

Ceci est en exemple, la configuration de l'output dépend essentiellement de l'architecture utilisée


output {
	elasticsearch { host => "elasticsearch" }
	stdout { codec => rubydebug }
}
								

Typage de données

Lors du découpage des logs par regex grok, il est possible d'avoir des patterns tels que : %{INT:responseTime}. Attention ! Cela ne veut pas dire que la colonne "responseTime" dans ES sera typée "integer". Elle sera typée "string", comme la quasi-totalité des autres colonnes. Pour pallier à ceci, deux solutions s'offre à vous :

Directement lors du parsing


grok {
  match => { "message" =>"...%{NUMBER:responseTime:integer}..."}
 }
										
Ou dans un mutate

mutate {
 convert => [ "responseTime", "integer" ]
}
								

Kibana

Interface

Après le lancement de la stack ELK, vous devriez pouvoir accéder à Kibana. La première page sur laquelle vous allez tomber est la partie "Settings". Il s'agit simplement de dire à Kibana le format d'index qu'il doit chercher. Si des logs ont correctement été parsés, un simple rafraichissement de la page devrait vous permettre de créer l'index. Vous aurez ainsi accès aux différents champs créés, automatiquement ou via vos filtres, par logstash lors du parsing des logs.
Vous pouvez désormais vous rendre dans la partie "Discover". Pour pouvoir visualiser vos logs, choisir en haut à droite de la page, la plage horaire de recherche en fonction de la date de vos logs. Vous devriez alors normalement voir vos logs. Il est possible d'effectuer de recherche directment via des requêtes, ou en utilisant la table de gauche.
La partie "Visualisation" vous permet de créer des graphes en fonction d'une ou plusieurs recherches. La partie "Dashboard" permet de rassembler et organiser plusieurs visualisations sur un même tableau de bord.

Vous pouvez également, par exemple, recentrer votre recherche sur les logs contenant un id précis :

Vous verrez ainsi tous les logs correspondant à une requête.

Recherche de logs : quel format de requête ?

Afin d'effectuer une recherche dans Kibana, il est possible

  • to be or not to be : est équivalent à to OR be OR or OR not OR to OR be
  • Rechercher une expression : "to be or not to be"
  • Rechercher dans des champs particuliers: tags:(access AND middle)
  • Rechercher sur un intervalle pour les champs numériques: line_id:[30000 TO 80000]

Utilisation de scripts dans Kibana

Lors de la création de visualisations dans Kibana 4 vous avez la possibilité d'utiliser des scripts dans le champ "Json input ". Pour ce faire, vous aurez sans doute besoin de rajouter cette ligne dans le fichier de config Elasticsearch.yml :
script.disable_dynamic: false
De cette façon vous pourrez alors utiliser des scripts de la forme suivante :
{"script":"log(_value)/100"}

Merci de votre attention ! Des questions ?


A propos de l'auteur

Je m'appelle Alan DAMOTTE. Je suis un étudiant ingénieur de 5ème année en Réseaux Informatiques et Communication Multimédia (Polytech Grenoble).


© Présentation ELK. All rights reserved.