Hindsight

Hindsight est un outil de type ETL qui fonctionne en système de pipelines. Codé en C, il est modulaire et permet via des scripts Lua de traiter de la donnée pour la transformer et la sortir sous divers formats.

Hindsight est sans conteste un remplaçant de choix de l’outil Logstash codé en JRuby (Ruby sur une JVM). Il ne souffre pas des mêmes soucis de performances et l’interface de traitement en Lua permet d’être bien plus flexible que les interfaces Grok de Logstash.

Architecture d’Hindsight

L’architecture se décompose en 3 éléments:

  • Des entrées (input)
  • Des analyseurs (analyzers)
  • Des sorties (output)

Hindsight data flow

Le transfert d’un élément de l’architecture vers le suivant s’effectue au moyen de message matchers. Lorsqu’un message sort d’un input ou d’un analyseur les analyseurs et sorties vont vérifier que le message correspond à un type d’entrée autorisée, si oui, chacun reçoit le message.

Cette fonctionnalité permet donc à partir d’une entrée ou d’un analyseur d’envoyer le message en multicast à plusieurs analyseurs et/ou plusieurs sorties.

Input Hindsight

L’input Hindsight est un plugin recevant les données depuis une source de données. Il se contente de lire les données séquentiellement et de les transférer au sein du pipeline. L’input peut avoir plusieurs formes:

  • Fichier plat (tail)
  • Syslog
  • Systemd
  • Lecture d’une BDD (PostgreSQL)
  • Lecture d’une queue (AMQP/RabbitMQ, Kafka)
  • Socket (TCP, UDP, Syslog)
  • Amazon S3 (lecture de fichier plat)

Analyzers Hindsight

Les analyseurs Hindsight sont des outils plus ou moins intelligents qui vont décoder un flux entrant suivant le message matcher et vont le transformer pour le pousser de nouveau dans le pipeline.

Les analyseurs prennent la forme de modules Lua qui vont être injectés dans le runtime.

On peut retrouver plusieurs types de décodeurs:

  • FTP
  • HTTP(S)
  • Syslog
  • Systemd
  • GeoIP
  • Nginx
  • Mime
  • LPEG
  • JSON (cjson ou rjson)

Output Hindsight

L’ouput Hindsight est un plugin recevant les données du pipeline correspondant au message matcher configuré. Il peut avoir plusieurs formes:

  • Écriture de fichiers (avec ou sans rotation)
  • Flux TCP
  • Création de documents Elasticsearch (via l’API bulk)
  • Écriture dans une table d’une BDD (PostgreSQL)
  • Envoi de mails

Installer et configurer Hindsight

Une installation Hindsight est composée de 3 éléments:

  • Hindsight: le core engine
  • LuaSandbox: la sandbox Lua qui va être utilisée par Hindsight
  • Les extensions de la LuaSandbox: l’intelligence fonctionnelle qui s’appuie sur l’architecture de la sandbox Lua

Configuration du repository

Hindsight est packagé sur Debian stretch mais la version présente dans le repository est très ancienne. De plus il n’y a pas de package pour les extensions, Hindsight est donc assez inutile.

Je vous propose d’utiliser mon repository autogénéré par un pipeline Gitlab, vous pourrez y trouver les 3 paquets nécessaires packagés.

Ajoutez la clef du repository dans APT:

apt-key adv --fetch-keys https://nerzhul.gitlab.io/debianrepo/repo.key

et ajoutez l’entrée dans /etc/apt/sources.list.d/nerzhul_gitlab_io.list:

deb https://nerzhul.gitlab.io/debianrepo stretch main

Installation des paquets

Installez Hindsight et les extensions de la LuaSandbox (la sandbox est en dépendance):

apt-get install hindsight luasandbox-extensions

Hindsight est installé sous la forme d’une unit systemd et tournera en utilisateur non privilégié hindsight

Configuration

Hindsight est conçu de telle manière que toute la configuration peut être rechargée à chaud. Le fichier de configuration global d’Hindsight est le suivant: /etc/hindsight/hindsight.cfg.

Pour charger de la configuration à chaud, il suffit de la placer dans l’un des répertoires de plugins du répertoire load du workdir de Hindsight. Dans le package fourni ci-dessus, le workdir est /var/lib/hindsight

Vous pouvez changer le répertoire de chargement dynamique en modifiant la valeur de la variable sandbox_load_path dans la configuration globale. Si vous la mettez à vide vous désactiverez le chargement à chaud des plugins.

Il peut être également intéressant d’augmenter le nombre de threads pour vos analyseurs en incrémentant la valeur de la variable analysis_threads.

Concentrons nous maintenant sur les plugins.

Dans le workdir d’Hindsight vous trouverez l’arborescence suivante:

  • load: répertoire parent du chargement des plugins
    • analysis: répertoire de chargement des analyzers
    • input: répertoire de chargement des input plugins
    • output: répertoire de chargement des output plugins
  • run: répertoire parent des plugins chargés
    • analysis: contient les fichiers des plugins d’analyse chargés
    • input: contient les fichiers des plugins d’entrées chargés
    • output: contient les fichiers des plugins de sortie chargés
  • output: répertoire de travail d’Hindsight

Ajout d’un input plugin (exemple syslog)

Un input plugin se compose à minima de 2 fichiers:

  • Un fichier de runtime Lua
  • Un fichier de configuration

Nous allons ici activer le support syslog.

Dans un premier temps il faut charger le code Lua du plugin d’entrée syslog. Celui-ci est fourni dans le package, il suffit de le copier dans le répertoire de chargement

cp /usr/lib/luasandbox/sandboxes/heka/input/udp.lua /var/lib/hindsight/load/input

Si la configuration dynamique est bien activée, vous devriez voir disparaître le fichier du répertoire et celui-ci devrait se retrouver dans le répertoire run/input

Ensuite on pousse la configuration du plugin syslog afin de charger le module. Créez le fichier input_syslog.cfg avec le contenu suivant, puis poussez le dans le répertoire load/input

filename            = "udp.lua"
instruction_limit   = 0

-- listen on all interfaces
address = "0.0.0.0"

-- listening port
port = 1514

-- decode the flow
decoder_module = "decoders.syslog"

-- display errors when decoder fails
send_decode_failures = true

Cette configuration définit un listener utilisant le module udp.lua précédemment poussé, le met en écoute sur le port 1514 et décode le flux au moyen du décodeur syslog.

Le module de decoding va analyser le flux en entrée pour le transformer en objet Hindsight découpé suivant le format défini dans le décodeur.

Ajout d’un output plugin (exemple Elasticsearch)

Un output plugin est composé des mêmes élements qu’un input plugin:

  • Un fichier de runtime Lua
  • Un fichier de configuration

Nous allons ici activer une sortie Elasticsearch.

Chargeons tout d’abord le code Lua du plugin, il est présent dans les extensions LuaSandbox du repository ci-dessus:

 cp /usr/lib/luasandbox/sandboxes/heka/output/elasticsearch_bulk_api.lua /var/lib/hindsight/load/output

On pousse maintenant la configuration du plugin Elasticsearch.

Créez un fichier output_elasticsearch.cfg avec le contenu ci-dessous, puis poussez le dans le répertoire load/output:

filename        = "elasticsearch_bulk_api.lua"
message_matcher = 'Logger == "analysis.syslog_to_es"'
-- Limit memory to 64MB
memory_limit    = 64 * 1024 * 1024
ticker_interval = 5

address             = "es-cluster-http.example.org"
port                = 9200
timeout             = 5000
flush_count         = 5000
flush_on_shutdown   = false
preserve_data       = not flush_on_shutdown
discard_on_error    = true
max_retry           = 1

encoder_module  = "encoders.elasticsearch.payload"
encoders_elasticsearch_common    = {
    es_index_from_timestamp = true,
    index                   = "%{Logger}-%{%Y.%m.%d}",
    type_name               = "%{Logger}",
}

Ce plugin va pousser dans Elasticsearch en utilisant les API HTTP en mode bulk. L’index créé aura pour nom le nom du logger suivi de la date du jour (YYYY-MM-DD) et les documents auront le type _outputelasticsearch.

Autre point intéressant, le ticker_interval et le flush_count. Le premier va définir un intervalle de flush des données côté ES (ici 5 secondes). Le second définit le nombre de documents à pousser avant de flush. Ces deux variables sont à tuner en fonction de la charge sur le cluster ES que vous allouez.

Enfin le dernier point important ici, le message_matcher. Il définit pour quels critères un message multicasté dans le pipeline va être reçu par cette sortie. Ici on demande à ce que le message provienne d’un analyseur appelé syslog_to_es.

Ajout d’un analyseur

Pour finir ajoutons un analyseur. C’est le coeur de notre pipeline Hindsight.

Dans un premier temps créons le fichier de code de l’analyseur (syslog_to_es.lua)

require "cjson"
require "os"

local syslog_msg
function process_message()
    syslog_msg = decode_message(read_message("raw"))
    -- Transform fields in flat attributes
    if syslog_msg["Fields"] ~= nil then
        for _, field in pairs(syslog_msg["Fields"]) do
                syslog_msg[field.name] = field.value[1]
        end
    end

    if syslog_msg["Timestamp"] ~= nil then
        -- Rewrite timestamp for logstash
        syslog_msg["@timestamp"] = os.date("%Y-%m-%dT%H:%M:%S.000Z", syslog_msg["Timestamp"] / 1000000000)
        -- Drop old timestamp format
        syslog_msg["Timestamp"] = nil
    end

    if syslog_msg["Payload"] ~= nil then
        -- Rewrite timestamp for logstash
        syslog_msg["@message"] = syslog_msg["Payload"]
        -- Drop old timestamp format
        syslog_msg["Payload"] = nil
    end

    -- Remove Uuid and fields
    syslog_msg["Uuid"] = nil
    syslog_msg["Fields"] = nil
    return 0
end

function timer_event()
    inject_payload("json", "syslog_msg", cjson.encode(syslog_msg))
    syslog_msg = nil
end

Notre analyseur utilise 2 callbacks fonctions d’Hindsight:

  • process_message(): Comme son nom l’indique l’objectif est ici de traîter le message. Dans notre cas on décode le message, on récupère les champs syslog pour les remettre au premier niveau de notre objet d’event puis on réécrit le timestamp et le payload dans des formats et champs conformes aux attentes d’un Kibana. Enfin on drop les champs inutiles.
  • timer_event(): Cette fonction est appelée lors du ticker_interval de l’analyseur. Dans notre cas on encode l’objet Lua en json avec le module cjson de la sandbox.

Poussez ensuite ce fichier dans le répertoire load/analysis afin qu’il soit chargé.

Créez enfin syslog_to_es.cfg avec le contenu suivant:

filename = "syslog_to_es.lua"
message_matcher = 'Logger == "input.syslog"'
ticker_interval = 5

On définit ici notre analyseur comme utilisant le code lua précédemment poussé, il prend en entrée les messages provenant de notre entrée syslog et pousse les messages régulièrement dans le pipeline, vers notre sortie Elasticsearch.

Pour terminer, déployez la configuration dans /var/lib/hindsight/load/analysis.

Conclusion

Nous avons étudié ici les bases de l’installation d’Hindsight et du fonctionnement des pipelines, comment les brancher dans un cas concret de récolte d’informations syslog pour les pousser vers Elasticsearch, en les convertissant dans un format acceptable pour le frontend Kibana.

Nous verrons dans de prochains articles comment réaliser proprement le parsing de logs au moyen d’analyseurs se branchant sur le décodeur syslog.