使用Filebeat和Logstash记录Docker应用程序日志

22

我有一组分散在多个服务器上的docker化应用程序,并尝试使用ELK设置生产级别的集中式日志记录。我对ELK本身没有问题,但不太清楚如何将日志转发到我的logstash。

我正在尝试使用Filebeat,因为它具有负载均衡功能。

我还想避免将Filebeat(或任何其他内容)打包到所有docker容器中,并保持其分离,无论是否经过docker化。

我该怎么做?

我一直在尝试以下方法。我的Docker在stdout上记录日志,因此使用非docker化的Filebeat配置从stdin读取时,我执行以下操作:

docker logs -f mycontainer | ./filebeat -e -c filebeat.yml

开始似乎可以工作。前几条日志被转发到我的logstash。我猜是缓存的。但是在某些时候它会卡住并继续发送相同的事件

这只是一个错误还是我朝错误的方向前进?您设置了什么解决方案?


我刚刚尝试了旧的logstash-forwarder,使用以下命令: docker logs -f mycontainer | ./logstash-forwarder_linux_amd64 -config forwarder.conf 它可以正常工作。我怀疑是Filebeat存在一个bug。唯一的问题是连接到logstash是随机的,没有负载均衡。 - Gianluca
你使用的filebeat版本是哪个?这看起来像是一个潜在的 bug。请随意在这里打开一个问题,以便我们能够更深入地研究这个问题。供参考:一些关于docker实现的讨论可以在这里找到:https://github.com/elastic/libbeat/issues/37 - ruflin
6个回答

19

这是将docker logs转发到ELK stack的一种方法(需要Docker版本>=1.8以使用gelf日志驱动程序):

  1. 启动一个Logstash容器,使用gelf输入插件从gelf读取并输出到Elasticsearch主机(ES_HOST:port):

    docker run --rm -p 12201:12201/udp logstash \
        logstash -e 'input { gelf { } } output { elasticsearch { hosts => ["ES_HOST:PORT"] } }'
    
    现在启动一个 Docker 容器,并使用 gelf Docker 日志驱动程序。这是一个简单的示例:
  2. 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'
    
  3. 打开 Kibana 后,之前可能只能通过 docker logs 查看的日志信息现在都可以显示了。根据 gelf 源代码 中的内容(感谢 Christophe Labouisse),一些有用的字段已经为您生成:_container_id_container_name_image_id_image_name_command_tag_created

如果您使用 docker-compose(请确保使用的是 docker-compose >= 1.5),并在启动 logstash 容器后在 docker-compose.yml 中添加相应的设置:

log_driver: "gelf"
log_opt:
  gelf-address: "udp://localhost:12201"

4
我认为 gelf 存在问题的原因是它使用 UDP 协议,可能会悄无声息地丢失日志事件。 - urso
3
不错,@urso。可以使用syslog日志记录器类似的方式通过TCP传递日志这里有一个例子Graylog扩展格式(GELF)文档提到,与UDP默默丢弃日志事件相比,使用TCP可能存在潜在问题。 - Pete
本文解释了使用gelf(包括UDP和TCP)的问题:https://www.claudiokuenzler.com/blog/845/docker-logging-gelf-tcp-good-bad-ugly - Mathieu Rollet

9

Docker允许您指定正在使用的logDriver。本答案不涉及Filebeat或负载均衡。

在一个演示中,我使用syslog将日志转发到监听5000端口的Logstash(ELK)实例。以下命令通过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'

我看了一下logDriver,但是高可用性怎么办?我需要一个TCP负载均衡器来将请求路由到我的logstash集群吗? - Gianluca
我不确定你的系统规模。使用约200个日志生成器(我的答案中的命令),我没有注意到任何问题。但是,我没有考虑高可用性或负载均衡/集群。 - michaelbahr
我并不太关心日志的数量,而是关心logstash的可用性。我需要至少2或3个logstash来确保良好的容错性,并且需要一些机制来在它们之间进行切换。 - Gianluca
嗯...我不能帮你解决这个问题。但是我会留下我的解决方案来帮助其他人。也许你想在你的问题中指出负载均衡。 - michaelbahr
使用Docker Swarm或Kubernetes应该可以解决您的负载均衡问题。在Docker Swarm中,您指定哪个服务应该接收消息,Docker会将其转发到其中一个副本(可能是轮询或其他可能性)。 - herm

8
使用filebeat,您可以像您描述的那样直接将docker logs输出进行管道传输。您所看到的行为肯定听起来像是一个bug,但也可能是部分行读取配置使您受到影响(重新发送部分行直到找到换行符号)。
我在使用管道时遇到的问题是,如果没有可用的logstash,可能会出现反向压力。如果filebeat无法发送任何事件,它将在内部缓冲事件,并最终停止从stdin读取。不知道docker如何保护stdout不变得不响应。使用管道的另一个问题可能是filebeat + docker的重启行为,如果您正在使用docker-compose。默认情况下,docker-compose会重复使用镜像+镜像状态。因此,在重新启动时,您将再次发送所有旧日志(前提是底层日志文件尚未被轮换)。

不必使用管道,您可以尝试读取由docker写入主机系统的日志文件。默认的docker日志驱动程序是 json日志驱动程序。您可以和应该配置json日志驱动程序以进行日志轮换+保留一些旧文件(以在磁盘上缓冲)。请参见max-size和max-file选项。json驱动程序为要记录的每行数据放置一行“json”数据。在docker主机系统上,日志文件被写入到/var/lib/docker/containers/container_id/container_id-json.log中。这些文件将被filebeat转发到logstash。如果logstash或网络不可用或重新启动了filebeat,则它会继续转发日志行,其中它停止(假设文件未因日志轮换而被删除)。不会丢失任何事件。在logstash中,您可以使用json_lines编解码器或过滤器来解析json行,并使用grok过滤器从日志中获取更多信息。

关于使用 libbeat(用于 filebeat 传输日志文件)来为 docker 添加新的日志驱动程序,已经有一些讨论。也许将来可以通过使用 docker 日志 API(我不知道任何利用日志 API 的计划)通过 dockerbeat 收集日志。

使用 syslog 也是一种选择。也许您可以在 docker 主机上获得一些 syslog 中继以负载均衡日志事件。或者让 syslog 写入日志文件并使用 filebeat 将其转发。我认为 rsyslog 至少有一些故障转移模式。您可以使用 logstash syslog 输入插件和 rsyslog 将日志转发到 logstash,并支持故障转移,以防活动的 logstash 实例不可用。


关于 json-file,https://github.com/moby/moby/issues/17763 表明 Docker 的 json 文件被视为内部数据,不应由其他进程使用。 - Jason Martin

7

0

为了帮助需要这样做的其他人,您可以简单地使用Filebeat来传输日志。我会使用@brice-argenson的容器,但我需要SSL支持,所以我选择了本地安装的Filebeat实例。

来自Filebeat的探测器是(对于更多容器,请重复):

- input_type: log
  paths:
    - /var/lib/docker/containers/<guid>/*.log
  document_type: docker_log
  fields:
    dockercontainer: container_name

有点糟糕的是,你需要知道GUIDs,因为它们可能会在更新时更改。

在logstash服务器上,设置通常的filebeat输入源用于logstash,并使用以下过滤器:

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" ]
    }
  }
}

这将解析来自Docker日志的JSON,并将时间戳设置为Docker报告的时间戳。

如果您正在阅读来自nginx Docker镜像的日志,您也可以添加此过滤器:

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" }
    }
  }
}

转换/重命名是可选的,但修复了COMBINEDAPACHELOG表达式中的一个疏忽,即它没有将这些值转换为整数,使它们无法在Kibana中进行聚合。


谢谢您!关于GUID的提示,我同意,但您可能不想手动进行这样的配置,而是使用像Ansible这样的工具。然后只需"docker ps | grep container_name | awk '{print $1}'",然后将结果模板化到配置和重启filebeat文件中即可。 - Thomas Hirsch
根据文档,您应该能够在prospectors.paths中使用类似于以下模式的内容:/var/lib/docker/containers/*/*.log - erewok

0

我在评论中验证了erewok上面写的内容:

根据文档,您应该能够在prospectors.paths中使用以下模式:/var/lib/docker/containers/*/*.log – erewok Apr 18 at 21:03

当filebeat启动时,表示为第一个“*”的docker容器guid被正确解析。 我不知道添加容器时会发生什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接