Blog Zenika

#CodeTheWorld

BigData & NoSQL

Consolider les logs docker dans un ELK

La multiplication des instances de conteneurs Docker peut vite rendre la consultation des logs des différentes applications un calvaire. Une solution est de consolider les logs de toute une infrastructure dans une seule application. Le choix le plus évident aujourd’hui est d’utiliser Elasticsearch Logstash et Kibana aka ELK.

Introduction

Un petit rappel des rôles des composants d’un ELK :

  • Elasticsearch : moteur d’indexation et de recherche. Conçu pour être facilement scalable horizontalement;
  • Logstash : outil de pipeline de récupération de données opérant des transformations et poussant le résultat dans l’outil de persistence configuré. Un nombre important de connecteurs est disponible permettant de s’interfacer facilement avec les outils du marché;
  • Kibana : IHM de visualisation interagissant avec Elasticsearch pour mettre en forme les données via des graphiques, histogramme, carte géographique etc…

Remarque : tous ces composants sont libres et gratuits. Des images Docker existe pour chacun d’eux.
L’intégration d’une stack ELK avec Docker n’est pas triviale et plusieurs solutions sont disponibles. Nous allons en voir 2 aujourd’hui :

  • utilisation du driver gelf de Docker
  • mise en oeuvre d’un conteneur spécifique Logspout

Installation d’un ELK

Montons un ELK dans un premier temps. L’ensemble de ces composants sont disponibles sous forme d’image docker dans le docker hub, à savoir :

Voici un fichier docker-compose.yml permettant de démarrer les conteneurs :

elasticsearch:
    image: elasticsearch:2.1.1
    volumes:
            - /srv/elasticsearch/data:/usr/share/elasticsearch/data
    ports:
            - "9200:9200"
logstash:
    image: logstash:2.1.1
    environment:
            TZ: Europe/Paris
    expose:
            - "12201"
    ports:
            - "12201:12201"
            - "12201:12201/udp"
    volumes:
            - ./conf:/conf
    links:
            - elasticsearch:elasticsearch
    command: logstash -f /conf/gelf.conf
kibana:
    image: kibana:4.3
    links:
            - elasticsearch:elasticsearch
    ports:
            - "5601:5601"

Description de ce fichier :

  • l’image elasticsearch en version 2.1.1 est utilisée
    • le dossier /srv/elasticsearch/data de l’hôte est mappé sur le dossier /usr/share/elasticsearch/data du conteneur
    • le port 9200 est exposé et est mappé tel quel sur l’hôte
  • l’image logstash en version 2.1.1 est référencée
    • le dossier conf/ du dossier courant est mappé sur le dossier /conf du conteneur
    • le port 12201 en TCP et UDP est mappé sur l’hôte
    • le conteneur elasticsearch est lié à logstash, ce qui permet d’associer un nom d’hôte elasticsearch avec l’ip réelle du conteneur elasticsearch
  • l’image kibana est démarrée en version 4.3, le port 5601 est mappé sur l’hôte et ce conteneur sera également lié à elasticsearch

Le fichier de configuration de logstash référence le connecteur d’entrée gelf en écoutant sur le port 12201 et pousse sur notre elasticsearch :

input {
  gelf {
    type => docker
    port => 12201
  }
}
output {
  elasticsearch {
    hosts => elasticsearch
  }
}

Voici l’arborescence des fichiers :

├── docker-compose.yml
└── conf/
     └── gelf.conf

Pour tout lancer, il suffit de lancer la commande suivante dans le dossier contenant le fichier docker-compose.yml :

docker-compose up

Docker va récupérer les images puis démarrer les conteneurs. Il faut attendre quelque secondes le bon démarrage puis une trace de la forme suivante indique le démarrage de Kibana. Elasticsearch est un peu plus long à démarrer.

kibana_1 | {"type":"log","@timestamp":"2016-02-01T09:33:07+00:00","tags":["listening","info"],"pid":1,"message":"Server running at http://0.0.0.0:5601"}

Il est également possible de vérifier le démarrage d’Elasticsearch en se connectant sur “http://ip_machine:5601/status”
Il faut ensuite d’ouvrir son navigateur sur “http://ip_machine:5601” et l’interface suivante s’affiche :
consolider-les-logs-docker-dans-un-elk_1
Sur cet écran, Kibana propose de selectionner l’index elasticsearch mais il n’est pas possible de valider le formulaire ; en effet, aucune donnée et donc aucun index n’a été créé dans ES, Kibana a besoin d’analyser les index pour afficher les informations.
Nous allons voir comment peupler les données.

Driver gelf de Docker

Docker offre nativement depuis la version 1.8.2 un driver de log au format GELF (Graylog Extended Log Format). Celui ci permet de pousser les logs produites par un conteneur vers un serveur graylog ou logstash. Ce dernier sera utilisé.
Pour utiliser ce driver, voici les options de lancement d’un conteneur à ajouter :

--log-driver=gelf --log-opt gelf-address=udp://ip_machine:12201

Lançons donc un conteneur qui produit des traces sur stdout :

docker run --log-driver=gelf --log-opt gelf-address=udp://192.168.10.2:12201 debian bash -c 'seq 1 10'

Remarque : 192.168.10.2 est l’ip de la machine exécutant le démon docker.
Une suite de nombre de 1 à 10 s’affiche en console.
Vérifions que ces informations sont bien accessible dans Kibana : en rechargeant la page de sélection d’index, il est maintenant possible de valider le formulaire :
consolider-les-logs-docker-dans-un-elk_2L’onglet settings présente ici les différents attributs disponibles et leurs types.
consolider-les-logs-docker-dans-un-elk_3
Puis en allant sur l’onglet discover, nous retrouvons les 10 traces produites précédement :
consolider-les-logs-docker-dans-un-elk_4
L’interface présente plusieurs onglets :

  • settings : enregistre les index et leurs caractéristiques
  • discover : affichage rapide des informations et test des requêtes
  • visualize : création / édition des différents diagramme graphique
  • dashboard : synthétise les recherches et les agrège dans un tableau de bord

Il est possible de raffiner l’affichage en ne sélectionnant que les colonnes intéressantes. Voici une description de quelques colonnes :

Attribut Usage
short_message Trace de log
created Date de création de la trace
command Commande lancée dans le conteneur
host Nom d’hôte de la machine docker
image_name Nom de l’image
container_name Nom de l’instance

 
Voyons ce que ça donne avec les colonnes filtrées :
consolider-les-logs-docker-dans-un-elk_5
Nous avons vu comment monter un ELK et indiquer à un conteneur où produire ses traces, les avantages et inconvénients sont les suivants :

Avantages Inconvénients
  • Configuration simple
  • Les applications n’ont qu’à écrire sur la stdout
  • Pas d’impact applicatif
  • Paramétrage à faire à chaque instanciation de conteneur
  • la commande docker logs id_conteneur ne fonctionne plus car les logs sont envoyés ailleurs

 
Pour pallier au dernier point, il est possible de paramétrer le démon docker pour qu’il utilise directement ce driver pour tous les conteneurs et éviter ainsi la configuration à chaque instanciation d’image.
Sous centos 7, éditer le fichier /usr/lib/systemd/system/docker.service et mettre à jour la ligne commencant par ExecStart :

ExecStart=/usr/bin/docker daemon --log-driver=gelf --log-opt gelf-address=udp://ip_machine:12201 -H fd://

Attention toute fois avec cette configuration : il est préférable que le logstash soit une machine différente de ce démon docker afin de pouvoir logger le démarrage de docker en cas de besoin.

Utilisation du conteneur Logspout

Logspout est un conteneur lisant les logs brut produites par l’ensemble des conteneurs s’exécutant sur l’instance de démon docker en écoutant sur la socket /var/run/docker.sock et les envoie sur un appender logstash.
Par défaut, l’image Logspout officielle ne possède pas de driver logstash mais il existe un driver libre et disponible. Nous allons l’ajouter dans notre image.
Pour commencer, il faut récupérer l’image logspout :

docker pull gliderlabs/logspout:master

Se placer dans un nouveau dossier, et créer un fichier modules.go contenant :

package main
import (
_ "github.com/looplab/logspout-logstash"
_ "github.com/gliderlabs/logspout/transports/udp"
)

Ce fichier source écrit en golang sera automatiquement inclus dans l’image que nous produisons.
L’image gliderlabs/logspout définie à l’aide de l’instruction ONBUILD les opérations à réaliser lorsqu’une image fille sera construite. Ici, un script va alors récupérer tout le nécessaire pour recompiler l’outil logspout en intégrant ce nouveau driver (ce n’est pas très “user friendly”, mais c’est exécuté une seule fois).
Puis, ajouter la ligne suivante dans un fichier Dockerfile :

FROM gliderlabs/logspout:master

Désormais, nous avons 2 fichiers dans le dossier courant :
Voici l’arborescence des fichiers :

├── Dockerfile
└── modules.go

Construisons notre image :

docker build -t zenika/logspout .

Note : s’il y a un proxy d’entreprise à passer, penser à ajouter les options --build-arg http_proxy=http://user:motdepasse@monproxy.com:3128 --build-arg https_proxy=https://user:motdepasse@monproxy.com:3128
Il est maintenant nécessaire de reconfigurer logstash pour ajouter un nouveau type d’input. Modifions le fichier gelf.conf de la première partie :

input {
  gelf {
    type => docker
    port => 12201
  }
  udp {
    port => 5000
    codec => json
  }
}
output {
  elasticsearch {
    hosts => elasticsearch
  }
}

Modifions également le fichier docker-compose.yml pour ajouter :

  • la variable d’environnement LOGSPOUT: ignore au conteneur logstash : cela indique que les traces émises par ce conteneur ne doivent pas être transmise. Cela va éviter d’avoir une boucle infinie
  • le mapping du port 5000
...
logstash:
  image: logstash:2.1.1
  environment:
    TZ: Europe/Paris
    LOGSPOUT: ignore
  expose:
    - "12201"
    - "5000"
  ports:
    - "5000:5000/udp"
    - "12201:12201"
    - "12201:12201/udp"
  volumes:
    - ./conf:/conf
  links:
    - elasticsearch:elasticsearch
  command: logstash -f /conf/gelf.conf
...

Relançons ELK

docker-compose up

Nous pouvons enfin instancier notre nouvelle image :

docker run --name="logspout" \
  --volume=/var/run/docker.sock:/tmp/docker.sock \
  zenika/logspout \
  logstash://192.168.10.2:5000

Description des arguments :

  • --volume : mapping du fichier /var/run/docker.sock de la machine hôte vers le fichier /tmp/docker.sock du conteneur. Il s’agit de la socket docker sur laquelle circule tous les événements docker
  • logstash://192.168.10.2:5000 : adresse vers l’instance logstash écoutant

Relançons le conteneur qui produisait des logs :

docker run debian bash -c 'seq 1 10'

Connectons nous à Kibana pour retrouver ces traces. Il est important de noter que le nom des attributs changent entre cette solution et la précédente, il faut indiquer à Kibana de ré-analyser l’index. Aller dans l’onglet settings, puis sur l’icone orange de rechargement.
consolider-les-logs-docker-dans-un-elk_6Puis retournons dans l’onglet Discover et comme pour la première partie, sélectionnons les colonnes les plus intéressantes :

Attribut Usage
message Trace de log
time Heure de création du message
docker.image Nom de l’image
host Ip de l’hôte docker
docker.name Nom de l’instance de conteneur
docker.id Id de l’instance de conteneur

 
Voyons maintenant l’affichage des logs :

Pour cette seconde solution, les avantages et inconvénients sont les suivants :

Avantages Inconvénients
  • Configuration simple
  • Les applications n’ont qu’à écrire sur la stdout
  • Pas de paramétrage d’instanciation des conteneurs
  • docker logs id_conteneur continue de fonctionner
  • Construction de l’image logspout un peu délicate
  • Les logs des conteneurs sont dupliqués entre le storage local de docker et elasticsearch. De plus, il n’existe pas de rotation ni de nettoyage des fichiers de logs dans docker pour le moment

 

Conclusion

Nous avons vu 2 possibilités pour pousser les traces des conteneurs dans un ElasticSearch. Leurs mises en œuvre présentent chacune des avantages et inconvénients qu’il faut prendre en compte sur le choix final. Ma préférence va pour le conteneur logspout qui est le moins intrusif.
Il existe également plusieurs autres solutions avec syslog, fluentd ou filebeat.
Le lecteur attentif aura remarqué que certaines lignes de log ne sont pas ordonnées correctement, cela fera l’objet d’un prochain article pour résoudre ce “petit” détail.
Maintenant, yapluka(c) créer des “dashboard” avec des beaux graphiques & co 😉
 

Auteur/Autrice

4 réflexions sur “Consolider les logs docker dans un ELK

  • Mathieu Buffenoir

    Je recommande de ne pas exposer kibana sur internet, cette image n’est vraiment pas sécurisée.
    Pensez à mettre un reverse proxy authentifiant en face.
    Aussi logspout peut-être aisément remplacé par le log-driver syslog de docker (cependant un peu moins flexible qu’un logspout que l’on peut éteindre/allumer à souhait)

    Répondre
    • Merci pour les détails.
      Le driver syslog est également intéressant, il y a tellement de solution qu’il est difficile de toutes les aborder 🙂

      Répondre
  • Damingo

    Bonjour,
    je veux installer elk ! Entre debian et centos7, lequel est facile à utiliser pour installer, configurer elk ?
    Merci de votre aiguillage.

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.