Zwindler sur GithubAllez faire un tour sur mes dépôts Git !

ElasticStack : Collecter et exploiter des métriques provenant de nginx

Comment collecter ? Les agents Beats à la rescousse

Dans l’article précédent, je vous ai donné la marche à suivre pour déployer ELK très simplement sur un même serveur. Je vous avais aussi expliqué la nouvelle architecture d’ElasticStack et notamment l’ajout des agents Beats, moins gourmands et plus spécialisés que Logstash.

Dans cet article je vais donc continuer où nous nous étions arrêté et faire un focus sur les agents Beats pour les intégrer à notre plateforme ELK existante.

Qui sont-ils ? Quels sont leurs réseaux ?

Et oui parce qu’il y en a plusieurs. Si vous avez lu précédent, vous savez qu’il existe pour la collecte de données, en plus de Logstash, des modules plus légers dont la fonction n’est que de récupérer les informations sur les noeuds que vous voulez surveiller (et rien de plus).

On a donc :

  • Filebeat, pour les fichiers de logs (texte)
  • Metricbeat, qui collecte tout un tas de métriques, notamment système, mais aussi provenant de Docker
  • Packetbeat, qui remonte des informations réseaux. Attention je parle d’informations très poussées sur les échanges réseaux, à la transaction près ! On peut faire de superbes graphiques et des analyses très poussées de flux réseaux intermachines… mais au prix d’un développement des dashboard assez complexe !
  • Winlogbeat, la même chose que Filebeat, mais sous Windows
  • Heartbeat, pour l’uptime (à quoi ça sert un module rien que pour ça et qui plus est pour l’afficher dans ELK… mystère?)

Vous trouverez plus d’informations sur le site officiel.

Collecte de données dans des fichiers plats avec Filebeat

Pour faire un exemple simple, on va commencer par utiliser Filebeat, le module dédié à la collecte de logs (documentation).

Filebeat (et les autres modules Beats) utilise des fichiers YAML pour savoir quoi faire. Là encore, je vous met à disposition mon playbook Ansible permettant d’installer simplement.

[A NOTER] Toute la configuration qui va suivre va être générée par le playbook ansible. Vous n’avez « rien » à faire mais je vous explique quand même ce que je fais histoire que vous puissiez reproduire vous même dans d’autre contexte. Le fameux poisson à pêcher proverbial, en somme.

mkdir -p /etc/ansible/roles && cd /etc/ansible/roles
git clone https://github.com/zwindler/ansible-deploy-filebeat-nginx
cd /etc/ansible
cat deploy_filebeat_for_nginx.yml
---
- name: Install filebeat for nginx
  hosts: @ip_ou_hostname_du_serveur_nginx
  vars:
    - elk_server: @ip_de_votre_serveur_elk
  roles:
    - { role: ansible-deploy-filebeat-nginx }

ansible-playbook deploy_filebeat_for_nginx.yml

Le principe du playbook est de :

  • déployer sur un serveur avec nginx un collecteur filebeat
  • qui va remonter les informations au logstash qu’on a installé sur notre serveur ELK
  • qui sera lui même configuré pour traiter les données nginx
  • et les réinjecter dans elasticsearch

Tout ça en un ligne de commande. Elle est pas belle la vie ?

Logstash en coupure…

Mais pourquoi s’embêter et passer par Logstash quand Filebeat envoie les logs et pas directement alimenter Elastisearch (sur la même machine) ?
C’est effectivement possible mais je préfère ne pas le faire pour plusieurs raisons.

D’abord, l’empreinte mémoire est bien moins importante. Là où Logstash consomme par défaut entre 512 et 1024 Mo, Filebeat ne consomme que quelques dizaines de Mo, 100 grand maximum. Un réel gain sur une infrastructure, quelle soit petite ou grande (chez moi, chaque Go compte !).

Ensuite, pour des raisons de sécurité, je n’ai autorisé que les connexions provenant de localhost sur Elasticsearch. Ainsi, seul Logstash (situé sur la même machine pourra y accéder), ce qui simplifie les autorisations au niveau du firewall et les flux de connexion. Les mauvaises langues diront « oui mais là tout le monde se connecte à Logstash, qui est sur la même machine, c’est pareil ».

Certes. Mais rien ne vous oblige à tout héberger sur la même machine et vous pouvez sécuriser vous même un peu mieux la plateforme. Toujours est-il que seul mon unique logstash peut alimenter la base et c’est quand même mieux, conceptuellement parlant.

Et enfin, Logstash dispose de plusieurs fonctionnalités pour trier et traiter les logs AVANT de les envoyer dans Elasticsearch, option que vous n’aurez pas si vous envoyez directement vos logs (ou toute autre information) directement depuis Beat vers Elasticsearch.

Configuration de Filebeat

Côté Beats, le template de configuration au format YAML est assez simple :
Ici, on donne 2 fichiers à manger, qu’on type tous les deux comme des « log » (purement informatif), puis un hôte et un port pour le Logstash de destination.

cat /etc/filebeat/filebeat.yml
filebeat.prospectors:
- type: log
  paths:
    -  /var/log/nginx/access.log
  document_type: nginx-access

- type: log
  paths:
   -  /var/log/nginx/error.log
  document_type: nginx-error

output.logstash:
  hosts: mon_serveur_elk:5044

Configuration côté nginx

Je ne vais pas insister longuement là dessus, mais j’utilise une modification de l’output du logs pour disposer d’une information supplémentaire pas indiquée par défaut : l’hôte dont provient la requête.

En effet lorsque vous avez un serveur nginx, il ne vous indique pas dans le log l’URL exacte d’où vient la requête. Si comme moi, vous avez plusieurs « Virtual Hosts », pointant vers plusieurs serveurs, c’est très pratique pour débuguer en cas de mauvaises redirections.

Rien ne vous oblige à utiliser « mon » log_format, mais il faudra bien entendu modifier ce qui va suivre dans ce cas là…

cat /etc/nginx/conf.d/default_main_log_format.conf
#add host in default logging format
log_format main '$remote_addr - $remote_user [$time_local] "$host" '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';
#                   '$request_time';
access_log /var/log/nginx/access.log  main;

Configuration de logstash

Nécessairement, puisque je passe par Logstash, je suis obligé de configurer Logstash (logique). C’est un mal pour un bien puisque ça me permet de traiter les lignes des fichiers de logs avant qu’ils soient intégrés dans Elasticsearch, comme je l’ai déjà dit.

Du coup le fichier de configuration côté Logstash est forcément plus complexe.

input {
    beats {
        port => "5044"
    }
}

filter {
#/var/log/nginx/access.log
  if [type] == "nginx-access" {
    grok {
      match => {
        "message" => '%{IPORHOST:remote_ip} - %{DATA:user_name} \[%{HTTPDATE:time}\] "%{DATA:target_host}" "%{WORD:request_action} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:response} %{NUMBER:bytes} "%{DATA:referrer}" "%{DATA:agent}"'
      }
    }

    date {
      match => [ "time", "dd/MMM/YYYY:HH:mm:ss Z" ]
      locale => en
    }

    geoip {
      source => "remote_ip"
      target => "geoip"
    }

    useragent {
      source => "agent"
      target => "user_agent"
    }
  }

#/var/log/nginx/error.log
  if [type] == "nginx-error" {
    grok {
      match => {
        "message" => '(?<time>%{YEAR}[./]%{MONTHNUM}[./]%{MONTHDAY} %{TIME}) \[%{LOGLEVEL:severity}\] %{POSINT:pid}.%{NUMBER}: %{DATA:errormessage}, client: %{IP:remote_ip}, server: %{IPORHOST:server}, request: "%{WORD:request_action} %{DATA:request} HTTP/%{NUMBER:http_version}", host: "%{DATA:target_host}"'
      }
    }

    geoip {
      source => "remote_ip"
      target => "geoip"
    }
  }

}

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

Là on comprend qu’on va un peu plus en baver. Mais je vais y aller par étapes.

Le plus facile

On va commencer par le plus facile : les balises input et output. En théorie, elles sont même auto-siffisantes. Si on ne met que ça, Logstash fera simplement office de passe plat.

input {
    beats {
        port => "5044"
    }
}

[...]

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

Pour faire clair, ici, on indique à Logstash de se mettre en écoute sur le 5044 pour des contenus provenant de beats, puis d’envoyer le résultat au serveur ELK dans l’index Logstash daté du jour. Trivial.

filter

Et là on arrive dans le vif du sujet. Si j’ouvre un log /var/log/nginx/access, voici ce que je pourrais y trouver :

1.1.1.1 - - [02/Oct/2017:03:44:27 +0200] "blog.zwindler.fr" "GET /2015/02/11/irl-redhat-virtualise-et-haute-dispo-rhcs-vmdk-partage-vs-vsphere-replication/ HTTP/1.1" 200 18219 "-" "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)"

Mon but ici, clairement, c’est de trouver comment débiter mon log en morceaux.

Petite note moins anglophiles d’entre vous, log en anglais, ça veut dire journal mais aussi rondin (de bois). D’où le petit jeu de mot/analogie avec Logstash (un abris bois) où l’on stocke les rondins après les avoir tronçonnés. Ça explique aussi l’ancien logo :

Comment ça on s’en fiche ? Moi ça me fait sourire 😉

access.log

Si vous avez bien suivi plus haut, nous avons récolté et différencié 2 logs via l’attribut « document_type » : access.log et error.log. Je ne vais en décrire qu’un car le principe est similaire.

filter {
#/var/log/nginx/access.log
  if [type] == "nginx-access" {
    grok {
      match => {
        "message" => '%{IPORHOST:remote_ip} - %{DATA:user_name} \[%{HTTPDATE:time}\] "%{DATA:target_host}" "%{WORD:request_action} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:response} %{NUMBER:bytes} "%{DATA:referrer}" "%{DATA:agent}"'
      }
    }
[...]

Ici, une fois ma balise ouverte, je commence par ajouter une clause « if » qui va me permet de filtrer le log en fonction du document_type du log dont il s’agit.

Ensuite, je lui indique que je souhaite utiliser « grok », un module pour parser mes données brutes. Grok utilise ensuite la fonction match sur l’attribut « message » (ma ligne de log, rappelez vous) sur lequel je vais appliquer une sorte d’expression régulière qui va devoir matcher votre ligne de log à tous les coups !

Et quand on a dit ça, on a tout dit. Préparez vous à des tonnes de fun !

Alors bien entendu, si vous êtes chanceux, vous trouverez toujours une bonne âme qui partagera avec l’Internet le bon pattern qui correspond au bon output de votre log pour votre application. Mais sachez que des fois, il n’y a d’autres choix que de tester à la main… et c’est long !

La méthode bourrin pour vérifier que votre pattern est la suivante :

  • modifier la configuration dans logstash
  • redémarrer logstash
  • attendre un peu pour que des logs soient générés et vérifier dans kibana que ça fonctionne correctement

grokparsefailure ==> dommage vous pouvez recommencer

Heureusement au fil de mes recherches, je suis tombé sur un outil qui vous aide à tester sans devoir dérouler ce fastidieux processus… Vous pouvez simuler sur cette page web vos pattern grok sur des bouts de logs.

Ça change la vie !

Le mot de la fin

Voilà, vous avez maintenant des logs qui remontent dans nginx et qui contiennent pléthore d’informations sur les requêtes entrantes et les utilisateurs. De quoi s’amuser pendant des heures pour créer des graphiques puis des dashboard.

Je ferai rapidement un prochain article sur la partie utilisation de Kibana car pour l’instant vous ne pouvez pas faire grand chose avec ça ;-).

J’ai également développé d’autres playbook pour déployer les autres modules Beats, je ferai également un article là dessus.

En attendant : have fun !

Pour aller plus loin

Add a Comment

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *