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 :
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 :
- Elasticsearch version 2.1.1
- Logstash version 2.1.1.-1
- Kibana version 4.3.1
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 :
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 :
L’onglet settings présente ici les différents attributs disponibles et leurs types.
Puis en allant sur l’onglet discover, nous retrouvons les 10 traces produites précédement :
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 :
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 |
---|---|
|
|
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.
Puis 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 |
---|---|
|
|
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 😉
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)
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 🙂
Des images Docker existeNT 😉
Bonjour,
je veux installer elk ! Entre debian et centos7, lequel est facile à utiliser pour installer, configurer elk ?
Merci de votre aiguillage.