Tutoriel pour apprendre à consolider les logs docker dans un ELK2

Guillaume Membré

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.

Commentez

- Titre : Tutoriel pour apprendre à consolider les logs docker dans un ELK2

- Auteur : Guillaume Membré

- Parution : 22 avril 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Guillaume Membré. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Installation d'un ELK

- III. Driver gelf de Docker

- IV. Utilisation du conteneur Logspout

- V. Conclusion

I - 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 persistance 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, histogrammes, cartes géographiques, etc.

Remarque : tous ces composants sont libres et gratuits. Des images Docker existent 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 deux aujourd'hui :

- utilisation du driver gelf de Docker ;

- mise en œuvre d'un conteneur spécifique Logspout.

II - Installation d'un ELK

Montons un ELK dans un premier temps. L'ensemble de ses composants sont disponibles sous forme d'images 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 :

1. elasticsearch:

2. image: elasticsearch:2.1.1

3. volumes:

4. - /srv/elasticsearch/data:/usr/share/elasticsearch/data

5. ports:

6. - "9200:9200"

8. logstash:

9. image: logstash:2.1.1

10. environment:

11. TZ: Europe/Paris

12. expose:

13. - "12201"

14. ports:

15. - "12201:12201"

16. - "12201:12201/udp"

17. volumes:

18. - ./conf:/conf

19. links:

20. - elasticsearch:elasticsearch

21. command: logstash -f /conf/gelf.conf

23. kibana:

24. image: kibana:4.3

25. links:

26. - elasticsearch:elasticsearch

27. ports:

28. - "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 :

1. input {

2. gelf {

3. type => docker

4. port => 12201

5. }

6. }

8. output {

9. elasticsearch {

10. hosts => elasticsearch

11. }

12. }

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 quelques 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.

1. 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 ouvrir son navigateur sur « http://ip_machine:5601 » et l'interface suivante s'affiche :

Sur cet écran, Kibana propose de sélectionner 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.

III - Driver gelf de Docker

Docker offre nativement depuis la version 1.8.2 un driver de logs au format GELF (Graylog Extended Log Format). Celui-ci permet de pousser les logs produits 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 :

1. 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 nombres de 1 à 10 s'affiche en console.

Vérifions que ces informations sont bien accessibles 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 leur type.

Puis en allant sur l'onglet discover, nous retrouvons les 10 traces produites précédemment :

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 diagrammes graphiques ;

- 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

- 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 le 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 commençant par ExecStart :

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

Attention toutefois 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.

IV - Utilisation du conteneur Logspout

Logspout est un conteneur lisant les logs bruts produits 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 :

1. package main

3. import (

4. _ "github.com/looplab/logspout-logstash"

5. _ "github.com/gliderlabs/logspout/transports/udp"

6. )

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 ONBUILDlogspout 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 deux 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, pensez à 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 :

1. input {

2. gelf {

3. type => docker

4. port => 12201

5. }

6. udp {

7. port => 5000

8. codec => json

9. }

10. }

11. output {

12. elasticsearch {

13. hosts => elasticsearch

14. }

15. }

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 transmises. 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 circulent 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 change entre cette solution et la précédente, il faut indiquer à Kibana de réanalyser l'index. Allez dans l'onglet settings, puis sur l'icône 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

- 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

V - Conclusion

Nous avons vu deux possibilités pour pousser les traces des conteneurs dans un ElasticSearch. Leur mise en œuvre présente chacune des avantages et inconvénients qu'il faut prendre en compte pour 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.

Tous nos remerciements à Vincent Vialé pour la mise en forme et à Claude Leloup pour la relecture orthographique.