J'ai un ensemble d'applications dockées dispersées sur plusieurs serveurs et essayant de configurer la journalisation centralisée au niveau de la production avec ELK. Je suis d'accord avec la partie ELK elle-même, mais je suis un peu confus quant à la façon de transmettre les journaux à mes journaux de bord. J'essaie d'utiliser Filebeat, en raison de sa fonction d'équilibrage de charge. Je voudrais également éviter de placer Filebeat (ou toute autre chose) dans tous mes dockers, et le garder séparé, docké ou non.
Comment puis-je procéder?
J'ai essayé ce qui suit. Mes Dockers se connectent sur stdout donc avec un Filebeat non dockerized configuré pour lire à partir de stdin, je fais:
docker logs -f mycontainer | ./filebeat -e -c filebeat.yml
Cela semble fonctionner au début. Les premiers journaux sont transmis à mon journal des transactions. Celui en cache, je suppose. Mais à un moment donné, il reste bloqué et continue d'envoyer le même événement
Est-ce juste un bug ou est-ce que je me dirige dans la mauvaise direction? Quelle solution avez-vous configurée?
Docker vous permet de spécifier le logDriver utilisé. Cette réponse ne se soucie pas de Filebeat ou de l'équilibrage de charge.
Dans une présentation, j'ai utilisé syslog pour transmettre les journaux à une instance Logstash (ELK) écoutant sur le port 5000. La commande suivante envoie constamment des messages via syslog à Logstash:
docker run -t -d --log-driver=syslog --log-opt syslog-address=tcp://127.0.0.1:5000 ubuntu /bin/bash -c 'while true; do echo "Hello $(date)"; sleep 1; done'
Voici une façon de transmettre docker logs
à la pile ELK (nécessite docker> = 1.8 pour le pilote de journal gelf):
Démarrez un conteneur Logstash avec le plugin d'entrée gelf pour lire à partir de gelf et sortir vers un hôte Elasticsearch (ES_Host: port):
docker run --rm -p 12201:12201/udp logstash \
logstash -e 'input { gelf { } } output { elasticsearch { hosts => ["ES_Host:PORT"] } }'
Maintenant, démarrez un conteneur Docker et utilisez le pilote de journalisation gelf Docker . Voici un exemple stupide:
docker run --log-driver=gelf --log-opt gelf-address=udp://localhost:12201 busybox \
/bin/sh -c 'while true; do echo "Hello $(date)"; sleep 1; done'
Chargez Kibana et des choses qui auraient atterri dans docker logs
sont désormais visibles. Le code source gelf montre que certains champs pratiques sont générés pour vous (chapeau: Christophe Labouisse ): _container_id
, _container_name
, _image_id
, _image_name
, _command
, _tag
, _created
.
Si vous utilisez docker-compose (assurez-vous d'utiliser docker-compose> = 1.5) et ajoutez les paramètres appropriés dans docker-compose.yml
après le démarrage du conteneur logstash:
log_driver: "gelf"
log_opt:
gelf-address: "udp://localhost:12201"
En utilisant filebeat, vous pouvez simplement diriger docker logs
sortie comme vous l'avez décrit. Le comportement que vous voyez ressemble définitivement à un bug, mais peut également être la configuration de lecture de ligne partielle qui vous frappe (renvoyez les lignes partielles jusqu'à ce que le symbole de nouvelle ligne soit trouvé).
Un problème que je vois avec la tuyauterie est une contre-pression possible au cas où aucun journal de bord n'est disponible. Si le battement de fichier ne peut envoyer aucun événement, il mettra en mémoire tampon les événements en interne et à un moment donné arrêtera la lecture depuis stdin. Aucune idée comment/si docker protège contre la sortie standard de stdout. Un autre problème avec la tuyauterie peut être le comportement de redémarrage de filebeat + docker si vous utilisez docker-compose. docker-compose réutilise par défaut les images + l'état de l'image. Ainsi, lorsque vous redémarrerez, vous enverrez à nouveau tous les anciens journaux (étant donné que le fichier journal sous-jacent n'a pas encore été tourné).
Au lieu de canaliser, vous pouvez essayer de lire les fichiers journaux écrits par docker sur le système hôte. Le pilote de journal docker par défaut est le pilote de journal json . Vous pouvez et devez configurer le pilote de journal json pour effectuer la rotation des journaux + conserver certains anciens fichiers (pour les mettre en mémoire tampon sur le disque). Voir les options max-size et max-file. Le pilote json place une ligne de données "json" pour chaque ligne à enregistrer. Sur le système Docker Host, les fichiers journaux sont écrits dans /var/lib/docker/containers/container_id/container_id-json.log. Ces fichiers seront transmis par filebeat à logstash. Si le journal des connexions ou le réseau devient indisponible ou que le battement de fichier est redémarré, il continue de transmettre les lignes de journal là où il se trouvait (les fichiers donnés n'ont pas été supprimés en raison de la rotation des journaux). Aucun événement ne sera perdu. Dans logstash, vous pouvez utiliser le codec ou le filtre json_lines pour analyser les lignes json et un filtre grok pour obtenir plus d'informations à partir de vos journaux.
Il y a eu quelques discussions sur l'utilisation de libbeat (utilisé par filebeat pour envoyer les fichiers journaux) pour ajouter un nouveau pilote de journal à docker. Peut-être qu'il est possible de collecter des journaux via dockerbeat à l'avenir en utilisant l'api des journaux docker (je ne suis pas au courant de plans concernant l'utilisation de l'api des journaux, cependant).
L'utilisation de syslog est également une option. Vous pouvez peut-être obtenir un relais syslog sur les événements du journal d'équilibrage de charge de votre hôte Docker. Ou demandez à syslog d'écrire des fichiers journaux et utilisez FileBeat pour les transférer. Je pense que rsyslog a au moins un certain mode de basculement. Vous pouvez utiliser le plug-in d'entrée syslog logstash et rsyslog pour transférer les journaux vers logstash avec prise en charge du basculement au cas où l'instance de logstash active deviendrait indisponible.
J'ai créé ma propre image docker en utilisant l'API Docker pour collecter les journaux des conteneurs en cours d'exécution sur la machine et les expédier à Logstash grâce à Filebeat. Pas besoin d'installer ou de configurer quoi que ce soit sur l'hôte.
Vérifiez-le et dites-moi s'il convient à vos besoins: https://hub.docker.com/r/bargenson/filebeat/ .
Le code est disponible ici: https://github.com/bargenson/docker-filebeat
J'ai vérifié ce qu'erewok a écrit ci-dessus dans un commentaire:
Selon les documents, vous devriez pouvoir utiliser un modèle comme celui-ci dans vos prospecteurs.paths: /var/lib/docker/containers/*/*.log - erewok 18 avril à 21:03
Les GUID du conteneur Docker, représentés par le premier "*", sont correctement résolus au démarrage de FileBeat. Je ne sais pas ce qui se passe lorsque des conteneurs sont ajoutés.
Juste pour aider les autres qui ont besoin de le faire, vous pouvez simplement utiliser Filebeat pour expédier les journaux. J'utiliserais le conteneur de @ brice-argenson, mais j'avais besoin de la prise en charge SSL, je suis donc allé avec une instance Filebeat installée localement.
Le prospecteur de filebeat est (répéter pour plus de conteneurs):
- input_type: log
paths:
- /var/lib/docker/containers/<guid>/*.log
document_type: docker_log
fields:
dockercontainer: container_name
Cela craint un peu que vous ayez besoin de connaître les GUID car ils pourraient changer lors des mises à jour.
Sur le serveur logstash, configurez la source d'entrée de battement de fichier habituelle pour logstash et utilisez un filtre comme celui-ci:
filter {
if [type] == "docker_log" {
json {
source => "message"
add_field => [ "received_at", "%{@timestamp}" ]
add_field => [ "received_from", "%{Host}" ]
}
mutate {
rename => { "log" => "message" }
}
date {
match => [ "time", "ISO8601" ]
}
}
}
Cela analysera le JSON à partir des journaux Docker et définira l'horodatage sur celui signalé par Docker.
Si vous lisez des journaux à partir de l'image Docker nginx, vous pouvez également ajouter ce filtre:
filter {
if [fields][dockercontainer] == "nginx" {
grok {
match => { "message" => "(?m)%{IPORHOST:targethost} %{COMBINEDAPACHELOG}" }
}
mutate {
convert => { "[bytes]" => "integer" }
convert => { "[response]" => "integer" }
}
mutate {
rename => { "bytes" => "http_streamlen" }
rename => { "response" => "http_statuscode" }
}
}
}
Les convert/renames sont facultatifs, mais corrige un oubli dans l'expression COMBINEDAPACHELOG
où il ne convertit pas ces valeurs en entiers, les rendant indisponibles pour l'agrégation dans Kibana.