Elasticsearch, Creation d'un template d'index

Posted on Fri 20 October 2017 in Elasticsearch

Lorsque l'on envoie des logs parsés par Logstash à Elasticsearch, si on ne spécifie pas de type lors de la création de l'index, tout sera considéré comme du texte par défaut. Mais cela nous empêche d'exploiter correctement certains champs (IP, nombres typiquement). Voyons comment arranger cela.

Log parsé

Prenons une ligne d'access log nginx que l'on va parser avec Logstash :

192.168.6.66 app.serveur.entreprise.net - [20/Oct/2017:08:38:04 +0000] "GET /page/visitee.php HTTP/1.1" 200 387 "http://app.serveur.entreprise.net/page/precedente.php" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36" "-"

Voici grosso-modo ce que l'on obtiendra après parsing par Logstash (article qui reste à écrire) :

{
"message": "192.168.6.66 app.serveur.entreprise.net - [20/Oct/2017:08:38:04 +0000] \"GET /page/visitee.php HTTP/1.1\" 200 387 \"http://app.serveur.entreprise.net/page/precedente.php\" \"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\" \"-\"",
"@timestamp": "2017-10-20T08:38:04.000Z",
"file": "/var/log/nginx/access.log",
"host": "serveurhostname",
"type": "nginx",
"app": "app.serveur",
"env": "production",
"clientip": "192.168.6.66",
"domain": "app.serveur.entreprise.net",
"auth": "-",
"timestamp": "20/Oct/2017:08:38:04 +0000",
"verb": "GET",
"pathrequest": "/page/visitee.php",
"httpversion": "1.1",
"coderesponse": "200",
"bytesresponse": "387",
"referrer": "http://app.serveur.entreprise.net/page/precedente.php",
"useragent": "\"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\""
}

Vous pouvez remarquer que tous les champs et leur valeurs sont quotés, entre doubles-quotes. Même les valeurs de type IP et numérique. Elasticsearch risque de les stocker en tant que valeur textuelle.

Index templating

Pour remédier à cela, il faut utiliser l'index templating.

Voyons ce que cela donnerait pour nous. le champ "clientip" est une adresse IP, le "bytesresponse" est un nombre. "httpversion" sera gardé comme texte ainsi que "coderesponse". Pour ce dernier, il s'agit d'un code de statut, on ne va pas l'additionné, le multiplier our faire ce genre d'opération. On aurait pu mettre "tout_bon" que cela aurait été exactement pareil.

Allons-y :

curl -XPUT 'http://ELASTICSEARCH_SERVER_ADDRESS:9200/_template/default_log?pretty' -d '
{
  "template": "logstash-*",
  "mappings": {                                                                                      
    "nginx": {
      "properties": {
        "bytesresponse": {"type": "long"},
        "clientip": {"type": "ip"}
      }                
    },         
  }
}'

Et voilà !

Ok, et en détail ?

  • template: indique à quels nom d'index seront appliqué ce template. Dans notre cas, si l'index créé commence par "logstash-", il s'appliquera, sinon, il ne s'appliquera pas.
  • mappings: mot clef pour indiquer que l'on va mapper des champs avec des types
  • nginx: correspond au champ "type" de vos enregistrement. Donc "nginx" dans notre exemple. Cela aurait pu être autre chose car (nous le verrons au § suivant), les rêgles ne s'appliqueront pas qu'aux enregistrements de type nginx.
  • properties: mot clef

Pour plus d'information, je vous invite à vous référer à la documentation très complète sur le site officiel.

A savoir : globalité et disponibilité

Attention, il y a quelques subtilités à savoir quand on crée un index template : la globalité de la configuration et le fait qu'il ne s'applique aux futurs index créés, pas au anciens.

Potentiels conflits de typage

En effet, et il s'agit d'une limitation connue et documentée, tous les champs "bytesresponse" et "clientip" dans notre exemple doivent de type "long" et "ip" respectivement. Même s'ils sont dans le résultat de parsing d'une autre application.

Et méconnaire cette contrainte peut entrainer des problèmes : dans le cas où un enregistrement arriverait dans le cluster elasticsearch avec un champ "clientip" non castable en IP (s'il y a un reverse DNS appliqué et qu'il est fournit un hostname au lieu de l'ip, par exemple), l'enregistrement ne s'effectuera pas, sera perdu et une erreur sortira dans les logs d'elasticsearch.

Application du template uniquement sur les futurs index

La seconde chose à savoir est qu'un index template ne s'appliquera que sur les index qui seront créés après la création du template. Ce n'est pas un problème si vous utilisez Elasticsearch comme une base de données comme une autre dans laquelle vous avez un schéma dès le départ (équivalent au CREATE TABLE... en SQL).

Si par contre, vous stockez des logs et n'avez pas défini de template avant de les envoyer dans elasticsearch, il va y avoir un petit temps de latence entre la création de cet index template et sa totale opérabilité. En effet, les index créés avant la création du template n'en bénéficieront pas sauf à tout ré-indexer. Généralement, on attend plutôt que tous les vieux index sans template disparaissent avec leur durée de rétention.