Gestion des logs: Architecture & Introduction a logstash

Portrait de fira

Comment faire pour gérer les logs de son infrastructure de façon centralisée ?
Récemment quelqu'un m'a parlé de leur méthode: gérée complétement à la main à base de scripts, avec de rsync, scp et des cronjobs.
Sa réaction immédiate à été de nous déconseiller de faire pareil et se tourner vers une solution commerciale, Splunk étant la plus connue.
Mais avec un peu d'huile de coude et les bons outils open-source, on peut arriver à une solution libre très puissante !
Je ne vais pas détailler ici l'installation des outils périphériques, on trouve amplement assez de blogs sur internet, mais plus de l'architecture générale, et de l'utilisation du parseur logstash.

L'architecture

Quel que soit le type de solution qu'on utilise, on va retrouver les mêmes genres de composants:

  • Un mécanisme client-serveur pour envoyer des logs suivant généralement trois mécanismes:
    • Le Syslog standard sur la plus part des appliances réseau et systèmes unix
    • Les fichiers de logs des applications ne supportant pas Syslog
    • Les évenements WMI Windows

    On pourrait aussi regrouper tous ceux là sur les clients, et ne communiquer au serveur de centralisation que par un broker de messagerie type AMQP.

  • Un tampon pour le traitement des évenements
  • Un parseur de logs pour en extraire les données importantes
  • Une base de données d'indexation pour stocker les logs
  • Une interface pour rechercher dans ces logs

La solution Logstash

La solution que je vous propose est basée sur des logiciels open-source. Il est cependant intéressant de noter que devant leur succès, plusieurs de ces logiciels ont commencés à avoir des distributions commerciales, offrant un support ou des outils supplémentaires.

  • rsyslogd ou syslog-ng comme relai syslog, qui est probablement déjà installé sur vos machines Unix
  • lumberjack (appelé aussi logstash-forwarder) pour envoyer les logs plaintext au parseur, sécurisé et très léger
  • logstash comme parseur de logs, assez lourd mais très puissant (on trouve aussi des solutions commerciales dérivées comme fluentd
  • ElasticSearch comme base d'indexation pour le stockage des logs, et qui servira aussi pour les paramètres du GUI
  • Kibana qui est un moteur de visualisation web pour ElasticSearch, orienté vers l'utilisation avec logstash. La encore, on peut trouver des solutions commerciales dérivées.

Question: Mais, mais, y'a pas de tampon?
Pour une première mise en place, il n'est pas nécessaire d'en avoir un, mais sous charge c'est très important: syslog par exemple étant en UDP, un burst soudain d'évenement pourrait remplir les buffers systèmes et causer la perte de certains évennements.
De ce que j'ai pu voir pour l'instant on utilise souvent Redis ou AMQP pour ça. Un premier logstash recoit les évenement et les place dans redis, un second les récupère de Redis et fait le traitement -- qui peut être beaucoup plus long.

Question: Pourquoi ces outils plutôt que d'autres?
J'utilisais déjà Grafana (voir les articles précédent) pour l'affichage de métriques avec Graphite, et c'est en fait un fork de Kibana.
Il parait donc assez logique de partir sur le même type de solution pour les logs, d'autant plus que ElasticSearch est une plateforme très puissante.
Les serveurs syslogs sont ceux utilisés par défaut sur la plus part des distributions, mais on peut vraiment utiliser n'importe lequel !
Enfin, il est possible de parser les logs puis les envoyer, mais logstash étant en java, celà apporte une charge sur les systèmes surveillés. Lumberjack est une alternative qui permet de déporter cette charge sur le serveur de centralisation de logs.

Logstash: comment ca marche ?

La configuration de logstash se fait avec un fichier json. On déclare trois grand types d'objets:

  • Des méthodes d'entrées (input)
  • Des filtres a appliquer (filter)
  • Des méthodes de sortie (output)

Bloc output

Pour communiquer avec ElasticSearch, on dispose de deux grandes méthodes. Soit via l'API REST HTTP, soit via le protocole intégré ou logstash devient lui même un noeud du cluster. J'ai choisi la deuxième méthode, on peut trouver des informations à ce sujet dans la documentation de logstash.
Par défaut, logstash emporte son propre moteur elasticsearch, mais il peut être judicieux de l'installer séparément pour pouvoir le déporter, ou simplement l'utiliser pour d'autres choses (grafana, kibana, etc...)

Bloc input

Il existe de très nombreuses façon de récupérer des informations dans logstash mais je me suis concentré sur celles dont nous avons parlé plus haut.

  • file: permet de récupérer un fichier local. Dans cet exemple on récupère les logs apache et les marque avec un type "apache-access" :

    file { 
         path => "/var/log/apache2/*access.log"
         type => "apache-access"
    }

    En pratique, il peut être nécessaire d'ajouter un paramètre sincedb_path pour indiquer ou logstash peut écrire ses infos temporaires.
    Si jamais le fichier est formaté dans un format particulier, par exemple du JSON, il est possible d'en extraire directement les champs avec le paramètre "codec". La liste des nombreux codecs est dans la documentation logstash.

  • syslog: pour récupérer des infos syslog envoyées par d'autres clients/serveurs
    syslog { 
          type => syslog 
    }

    C'est tout! Par défaut, le plugin écoute sur le port syslog standard (514) en TCP et UDP.

  • lumberjack: pour récupérer des fichiers d'autres hôtes
    lumberjack {
    port => 12345
         ssl_certificate => ssl.crt
         ssl_key => ssl.key
    }

    Ici, j'ai choisi de ne pas tagger avec un type, et que celà soit fait de côté client.
    Le protocole lumberjack nécessite au moins d'utiliser SSL côté serveur, d'ou la nécessité d'un certificat & clé. On peut en plus utiliser une authentification côté client.
    Tout ceci est très bien détaillé dans la documentation de logstash-forwarder qu'on peut trouver sur github.

Le bloc filter

C'est là qu'on va faire le gros du travail. On utilise généralement les blocs suivants:

  • grok: permet d'extraire des informations en parsant
  • date: permet de parser une date pour récupérer la bonne information dans logstash
  • mutate: permet de faire des modifications arbitraires aux logs

Pour gérer les quelques exemples dont j'ai parlé au dessus, on pourrait utiliser ce genre de configuration:

filter {
 
if [type] == "syslog" { 
    date { match => [ "timestamp", "MMM dd HH:mm:ss" ] }
} else if [type] == "apache-access" {
    grok { match => [ "message" , "%{COMBINEDAPACHELOG}" ] }
    if "_grokparsefailure" not in [tags] {
         date { match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ] }
         mutate { remove_field [ "message" ] }
    }
}
 
}

En détaillant: si c'est un syslog, on essaie d'indiquer sa date à logstash.
Les formats les plus courants de syslog sont automatiquement gérés, donc pas besoin de parser quoi que ce soit.

Si c'est un log d'accès apache, on éssaie de le parser. Pour celà on fait matcher le "message" avec le pattern %{COMBINEDAPACHELOG}.
C'est un pattern qui est fourni de base avec logstash, comme beaucoup d'autres.
On vérifie ensuite que le parsage a réussi, pour en récupérer sa date. Je choisis personnellement de supprimer le champ message après ça, car il est redondant avec les infos parsées..

Si vous essayiez cette configuration, vous remarqueriez probablement qu'elle marche la plus part du temps, mais pas tout le temps !
De temps en temps logstash va rapporter des erreurs de parsing de date. Pourquoi ?
La réponse est simple: On a choisi en entrée tous les logs correspondants a "/var/log/apache2/*access.log".
Parmis eux on trouve par défault other_vhosts_access.log, qui rajoute en plus le nom du virtualhost accedé. Corrigons:

...
} else if [type] == "apache-access" {
    if [file] =~ /other_vhosts_access.log$/ {
         grok { match => [ "message", "%{DATA:vhost} %{COMBINEDAPACHELOG}" ] }
    } else {
         grok { match => [ "message" , "%{COMBINEDAPACHELOG}" ] }
    }
 
    if "_grokparsefailure" not in [tags] {
         date { match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ] }
         mutate { remove_field [ "message" ] }
    }
}

Désormais pour le fichiers other_vhost_access.log, on obtiens en plus le paramètre "vhost".
Les patterns utilisés dans grok sont des PCRE: il supporte les fonctionnalités avancées tels les look-ahead.
Pour extraire un champ, on utilise la syntaxe %{PATTERN:nomduchamp}. Ici, DATA est un pattern standard qui est en fait simplement "*.?"

La visualisation

Une fois toutes cettes données parsées on peut les visualiser, par exemple dans Kibana.
Kibana est un outil très puissant qui permet d'obtenir des dashboards sous le même genre de format que Grafana.
La configuration des dashboards est un sujet entièrement différent, et que je détaillerais dans un prochain post.
En attendant, vous pouvez trouver bon nombre d'exemples sur Internet !