Docker日志自动化: ElasticSearch、Logstash、Kibana以及Logspout
本文主要介绍了如何使用ElasticSearch、Logstash、Kibana和Logspout技术栈来部署自动化的日志系统。
You, too, could Logstash.
快速念五遍这个题目!不过说实话,我其实并不确定该给这篇文章起个什么样的名字才能确保人们可以找到它。
这篇文章是基于 Evan Hazlett’s article on running the ELK stack in Docker 和 ClusterHQ’s article on doing it with Fig/Docker Compose and Flocker这两篇文章的。本文同时也受到了 Borg paper的影响,它在讨论使用『标准栈』做事情的时候提出了我们感兴趣的的一些工具。
问题
相信不用我说你也明白为什么要对妥善处理日志,日志可以帮助我们排查错误,分析问题。
当然,也有很多日志的解决方案,其中包括Logstash这样非常可爱的技术。而后又出现了更多实用的技术。
我们都知道日志的重要性,所以,随着对Docker兴趣的增加,让人们疯狂的一件事情就是如何在Docker的世界处理日志。
有了Docker,人们突然被迫需要用另一种方式来考虑日志。在部署一个传统的Linux时,应用程序或者架构记录日志的方式通常记录到文件里, 一般(但不一定)会记录到/var/log目录下。的确,我有一些关于检查PHP日志“不错”(译者注:原文是带引号的fond,应该是反语的意思)的回 忆,在那个项目中,日志是放在项目的目录下,当出现错误的时候内置的应用程序日志并不能提供任何有用的东西。我其实并不喜欢把日志拆分成那样(更多可移动 的部分意味着更难调试),可能更好的实践是有一个统一访问日志的方式。的确,有一篇关于这个想法(“统一日志层”)的有趣的 文章,作者是来自 Fluentd的Kiyoto Tamura,这是个类似Logstash的工具。
那对于Docker有什么不同呢?突然间,不同于以往将所有日志放在主机系统的统一位置,如今日志分散在很多不同容器的相互隔离的环境中。啊哦,听起来好像跟我们想要的刚好相反。
Docker以往处理日志的方法是通过docker logs命令 - Docker捕捉每一个容器进程的STDOUT和STDERR,保存在磁盘上,然后用户就能使用docker logs <container>来进行查询。如果用于开发,你只是想往终端屏幕上打印一些输出并快速得到结果,那么这种方式工作的还不错;但是当你考 虑在更复杂的环境下使用Docker,或者想要查看更多传统架构的Unix后台程序的日志,而这些程序运行在后台并且日志记录在容器的内部磁盘上的时候, 麻烦就来了。这种情况下,问题主要是:
- 可发现性(discoverability) - 如果容器只是短暂的存在,那么试图用我想要的日志跟踪它,并且使用grep来解析它,这种方式好像不是那么有趣;
- 日志轮转(log rotation) - 有些服务程序特别健谈(译者注:原文是chatty,意思是这些程序会有很多输出),亦或是会运行很长时间。那么我们就需要一种方式,当系统运行一段时间 后,对日志进行一次清理,来确保磁盘不会被那些我们不再使用的日志所填充。据我所知,现有的Docker还不支持这种特性。
在这片文章中我暂且不谈关于日志轮转的内容,因为那是另一个罐罐里的虫子,但是我会在这里描述该技术栈(译者注:原文是stack,不知道译成技术栈是否正确)的轮廓,主要是为了缓解处理第一个问题的过程。
对于那些把日志记录在容器内部磁盘的进程,如果想要为它们记录标签,有很多种方法可以让它工作。我最喜欢的一种方式——虽然并没有在这里真正列出来,但在我看来非常有用——是将原始容器日志记录的目录作为一个卷( volume), 并且让其他容器使用--volumes-from选项来继承这个卷。然后他们就可以使用tail -f /var/log/foo/access.log或一些其他方法来查看日志了。在我看来这促进了一个相当好的关注点的分离,因为你进行监视日志的容器与写 日志的容器的不同的,另外(并不充分)你可以绕开union文件系统来进行操作(就像操作一个数据库一样)。但真的没有必要在镜像中跟踪日志(状态)。
那关于discoverability你要做些什么呢?我们将会在Docker内运行一个 ELK stack,并且使用 logspout工 具来自动将容器的日志路由到Logstash。我真心觉得在未来这种方法会应用在很多方面 - 如果你将要运行容器、停止它们、删除它们或者其它,那么你可能也会对本地事件感兴趣,并且会让容器的生命周期可以追踪和监视。之后你的基础设施就可以重新 激活而不再需要人工干预。同样对于像负载平衡,服务发现等此方法也是可用的,但那将是另一篇文章的内容了。
方法
以下是Evan和ClusterHQ的方法,我们将会运行:
- 用ElasticSearch来索引收集的日志数据并使它更易于查询
- 使用Logstash作为远程syslog来收集来自容器的日志
- 使用Logspout来向Logstash发送容器的日志
- 使用Kibana作为与收集来的数据进行交互的一个漂亮的前端
- Cadvisor,一个监视容器资源的指标的仪表盘,这个纯属个人喜好(原文:for kicks)
如果它们听起来像是会运行吓人数量的程序,请试着不要那么烦恼 - 我们将会直接使用 Docker Compose来开始这些工作。
所以,如果你想在家跟随我一起来做,你可以运行以下命令来开始 (你需要安装最新版本的Docker和Docker Compose):
$ git clone https://github.com/nathanleclaire/elk $ cd elk $ docker-compose -f docker-compose-quickstart.yml up
这会在从Docker Hub上获得的容器化好的镜像上启动应用程序,而且你的Kibana前端会从80号端口进行获取而不管主机的DOCKER_HOST指向什么地方。
对我而言,我喜欢部署(译者注:原文kick up,不确定是否能译成部署)一个 DigitalOcean droplet(译者注:DigitalOcean提供了IaaS服务,droplets是DigitalOcean公司专有的云服务器术语)或者等价的使用 Docker Machine来做这样的工作,因为拉镜像所需的带宽想必比连接你邻居的WiFi的要求更高一些。如果你也想这么做,那么下面的命令可以创建你自己的服务器(再次强调,确保安装了最新版本的软件):
$ export DIGITALOCEAN_ACCESS_TOKEN=MY_SECRET_API_TOKEN $ docker-machine create -d digitalocean \ --digitalocean-size 4gb \ --digitalocean-image docker \ droplet .... .... .... To point your Docker client at it, run this in your shell: eval "$(docker-machine env droplet)" $ eval "$(docker-machine env droplet)"
我通常建议使用上文提到的体面结实的服务器,因为这些进程可能会消耗大量的内存。它在本地工作的还不错,但是pulls操作会有点慢,除非你使用的是光纤。
如果你不是很喜欢运行不受信任镜像(或者你仅仅是修改并构建自己的镜像),没有问题:发行版中默认的docker-compose.yml是基于构建参数的,所以你可以自己构建镜像:
$ docker-compose build $ docker-compose up
当你把容器启动后,你的终端上会有类似的输出:
你可能会看到关于Logspout未能链接到syslog的错误,不过这没什么问题,很正常。该错误是因为Logstash容器还没有启动。当把它运行之后,上面的错误就会停止了。
现在当你查看启动了一组容器的宿主机的80号端口时,你应该会看到Kibana的欢迎界面:
点击上图中箭头指示的 “Logstash dashboard” 链接,或直接访问<machineIp>/#/dashboard/file/default.json,你会被带到你新建的Docker日 志架构的页面(译者注:真的不想把dashboard译成仪表盘)!
我一直在强调,关于容器最基本的“技术栈”在Evan的文章里被直接忽略了,这并没有什么不好,但是当我自己想要实现这些技术的时候,遇到了一些问题:
- Logspout发送的数据格式跟Evan原文中提到的Logstash的grok过滤器有略微的不同/格式中镜像是需要的,所以:
- 在日志中会有很多grok解析失败的错误(这意味着Logstash试图去匹配一个它知道的日志信息模式却不能正确解析)。对于它自己来说这并不是什么大问题,但是:
- 由 于Logstash是一个被Logspout监视的容器,而Logspout会将Logstash所有的日志转发给Logstash,这会造成一个疯狂的 自旋循环并且几乎会耗尽容器内所有的CPU资源(docker stats,一个非常有用的命令,可以实时报告容器资源的使用情况,我就是用它来捕捉到了上述的问题)。
This can't be good.
那一个黑客会做些什么呢?当然是黑(译者注:这里应该是解决问题的意思)它了!我复制(fork)了Evan的原始的 Dockerfiles/repos并更改了一些东西。第一步,我把所有容器扔进了一个docker-compose.yml文件的服务中,如此以便快速 参考(这样当我想再次启动docker技术栈的时候就不必重复输入所有docker run命令了)。我在 the Logspout Github repo的文档中注意到你可以为容器指明一个环境变量来让它的日志不要转发给Logspout。所以,我在Logstash容器中设置了这个环境变量:LOGSPOUT=ignore。
读书也应该会注意到,gliderlabs/logspout镜像现在想要Docker套接字挂载到/var/run/docker.sock (经典的位置), 而不是以前的/tmp/docker.sock - 在我意识到这点之前,我逐字使用了Evan文章中给出的命令,而这这给我造成了很大的麻烦。所以请注意这个问题!!
现在这个环境(stack)不再出现那个无限循环来鞭挞我的CPU了。不过还有一个挑战:那些在日志中grok解析失败的消息。文章中提供的 Logstash配置例子不能很好地匹配(译者注:原文jive well,不知道以为匹配是否合适)logspout的输出。所以,为了更好地掌握发生了什么,我做了在这种情况下所有人都会做事情:阅读 Logspout源码。
没过多久我偶然发现了这个 block of code related to the syslog adapter:
func NewSyslogAdapter(route *router.Route) (router.LogAdapter, error) { transport, found := router.AdapterTransports.Lookup(route.AdapterTransport("udp")) if !found { return nil, errors.New("bad transport: " + route.Adapter) } conn, err := transport.Dial(route.Address, route.Options) if err != nil { return nil, err } format := getopt("SYSLOG_FORMAT", "rfc5424") priority := getopt("SYSLOG_PRIORITY", "{{.Priority}}") hostname := getopt("SYSLOG_HOSTNAME", "{{.Container.Config.Hostname}}") pid := getopt("SYSLOG_PID", "{{.Container.State.Pid}}") tag := getopt("SYSLOG_TAG", "{{.ContainerName}}"+route.Options["append_tag"]) structuredData := getopt("SYSLOG_STRUCTURED_DATA", "") if route.Options["structured_data"] != "" { structuredData = route.Options["structured_data"] } data := getopt("SYSLOG_DATA", "{{.Data}}") var tmplStr string switch format { case "rfc5424": tmplStr = fmt.Sprintf("<%s>1 {{.Timestamp}} %s %s %s - [%s] %s\n", priority, hostname, tag, pid, structuredData, data) case "rfc3164": tmplStr = fmt.Sprintf("<%s>{{.Timestamp}} %s %s[%s]: %s\n", priority, hostname, tag, pid, data) default: return nil, errors.New("unsupported syslog format: " + format) } tmpl, err := template.New("syslog").Parse(tmplStr) if err != nil { return nil, err } return &SyslogAdapter{ route: route, conn: conn, tmpl: tmpl, }, nil}
原来在我的例子中Logspout按照 syslog RFC5424标准来转发日志的(你可以在上面的代码中查看默认的值)。我花了一些时间在非常好玩的 Logstash grok parse test app上,但之后我很好奇网上是否已经有解决这个问题的现有的资源了。我很快的谷歌了一下,发现了这篇 文章,非常出色的描述了grok parse filter,而这正是我想要的。我仅仅改了部分代码(比如,我把“app”改为了“containername”)就很快上道了 - 把Logspout的日志解析为有用的数据了。
我最终的Logstash配置文件看起来是这样的:
input { tcp { port => 5000 type => syslog } udp { port => 5000 type => syslog } } filter { if [type] == "syslog" { grok { match => { "message" => "%{SYSLOG5424PRI}%{NONNEGINT:ver} +(?:%{TIMESTAMP_ISO8601:ts}|-) +(?:%{HOSTNAME:containerid}|-) +(?:%{NOTSPACE:containername}|-) +(?:%{NOTSPACE:proc}|-) +(?:%{WORD:msgid}|-) +(?:%{SYSLOG5424SD:sd}|-|) +%{GREEDYDATA:msg}" } } syslog_pri { } date { match => [ "syslog_timestamp", "MMM d HH:mm:ss", "MMM dd HH:mm:ss" ] } if !("_grokparsefailure" in [tags]) { mutate { replace => [ "@source_host", "%{syslog_hostname}" ] replace => [ "@message", "%{syslog_message}" ] } } mutate { remove_field => [ "syslog_hostname", "syslog_message", "syslog_timestamp" ] } } } output { elasticsearch { host => "elasticsearch" } stdout { codec => rubydebug }}
也许还有很大的改进空间,但首先我还须更充分地了解Logstash ;P
So what?
现在我得到了有意义的格式的日志了,而且这并没有对CPU产生多大的影响,太棒了!每当我在那台主机上运行一个容器,日志就会自动地在 ElasticSearch中被索引并且可以在Kibana中进行查询!Logstash过滤器负责将原始的系统日志消息解析为更有用的标签信息。这包括 上文提到过的,来自运行该技术栈的容器的日志(除了Logstash - 不确定如何处理它,或我是否应该担心它。可能一个附加的配置参数可以让它仅打印自身的日志到STDOUT而不是所有容器的日志)。想象一下这类自动化容器 日志将会是多么有用,当它处理类似 RancherOS这样的所有系统服务都运行在Docker容器内部的时候。
你可以决定在日志信息中显示哪些字段以便来快速查看你的应用程序产生的日志。这样就可以实时感受到在容器中发生的事情。Kibana有充分能力和可配置性,允许你通过你所拥有的不同字段来进行排序,查询以及过滤。
Logs which are nice and easy to read and query!
尝试在一个配置了ELK技术栈(译者注:包括Elasticsearch,Logstash和Kibana)的主机上运行一个容器,然后来查看自动显示在Kibana上的日志(你可能需要刷新你的浏览器或点击Kibana上“刷新”的刷新按钮)。
$ docker run -d --name number_spitter debian:jessie bash -c 'for i in {0..2000}; do echo $i; done'
你现在可以看到日志中大多消息来自number_spitter容器,它很自然地bash滚动条(译者注:应该是指下图最下边的那一行)中在吐出了一串数字。
Whaaaaat! The Number Spitter container is so chatty!
你可以在这些基本的设置之上做很多不可思议的事情。自然地,有一个可以用作容器活动的可视化表示的时间序列图,可以让你发现热点,并快速知道发生了什么,以及为什么会发生。
What the hell happened here? I don't know, but I can find out.
显示:ElasticSearch容器的真实事件。
有大量的事情可以并且应该在配置了Logstash之后来进行 - 这里讨论的配置文件仅仅是一个开始。有些容器使用它们自己的日志格式,它们需要进一步的进行解析。例如,在上图中显示的Kibana容器的“表格式”就有 它自己的时间戳,以及哪些IP地址访问哪些文件的信息,HTTP响应的状态码,等。
所以,这些附加的信息绝对可以被解析为更有用的结构格式中,而且这些事情应该针对每个应用程序来做(on per-app basis)。同样,你可以想象一下,当你的消息以更高的规格的机构来呈现,它们可以匹配一个模式来告诉你是否有应用程序从panic中启动,或者在代码 执行路径中遇到了空指针异常,亦或是不能链接数据库等,都会以一定的优先级在日志中表现出来,这应该是一件很酷的事情吧。
并且,如果Logspout把Docker的事件(我不清楚是否支持这样的功能,因为我似乎记得看到一些容器的删除事但并没有出现其他别的东西) 和/或Docker的后台日志也转发给了Logstash 将是一件很巧妙的事情。可能有一种可以替代Logstash的更简单的方式。
此外,Docker 1.6 的 log drivers可 能会以稍微不同过的方式来做类似的事情,所以我很好奇当把log driver考虑进来的话这个配置会有什么变化。我不是很清楚Logspout的内部细节,所以也不知道是否可以使用--log-driver=none 参数来禁掉log driver,然后继续使用Logspout来转发日志。那将会很酷因为你只需要跟中ElasticSearch中的数据,而不是既有 ElasticSearch又有--log-driver=json格式的数据。
我也不是很确定Logstash是否支持事件(例如,给一个正在打电话的人发送邮件或短信是否会在有限时间内收到大量的错误),那是另一个潜在的 用框(译者注:北京大学的邵维忠老师告诫我们use case应该译为用框)(如果这样的事情还没有得到很好支持我会感到很吃惊)。想想看,这种事情同时确实强烈地需要松散的集成 - 例如,每次我们关闭一个非接触订阅的时候会通知销售频道,我们知道这些事情,因为它被有记录了下来。不过这些跑题了。
说到跑题,演示程序同时包含了一个 cAdvisor的实例,这是一个非常有用的工具,它可以监视你容器中资源的使用情况。你可以在在你工作的主机的8080号端口访问它:
Pretty graphs for your containers
好了。我应该马上开始使用这个吗?
如果你想不加任何修改并立即在生产中使用这个配置,那这并不是你所要的,尽管它看起来绝对比“docker run, 可能之后还会用docker logs手动检查”好很多。需要考虑一些额外的东西,不分先后:
- ElasticSearch副本:在多个节点保存数据来支持冗余。因为会发生奇怪的事情,节点会挂掉,理想情况下你的基础设施应该可以平 滑的处理这类故障。同样,当你有多个主机,在每个节点配置Logspout实例来向“master”Logstash(我不确定应该如何处理这种潜在的故 障点的冗余)来转发主机的日志是你需要处理的事情。
- 备份(Backing up)和轮转存储在ElasticSearch中的日志数据。要实现这个,我确定ClusterHQ(自称为“container data people”)将会助你一臂之力;)
- 确保访问接口被约束在网络和用户级(演示程序把所有的接口都打开了,所以如果你在公有网络运行它的时候,所有人都是可以看到它并对它做些什么)
- 添加容器重启策略以及监控,来确保服务的健康和正常运行。
- 使用低权限用户来运行容器,这样可以确保更好的安全性
所以,在现实中使用它的时候仍然有很多事情需要去考虑,但我希望我所讲的这些东西会对你有所启发,就像我帮你在你的头脑了安装一些小小齿轮,推动 你完成这项工作。我想现在一个非常小的创业公司都是可以运行这些工具的,而且我很兴奋像这样世界级的工具变得越来越多了。不管是现在还是将来,对一个团队 来说最终它都会使至关重要的,因为他们需要将使用一个命令来处理多台机器(SRE)(译者注:不知道SRE是什么意思), 正是这类工具让这一目标变得更具可行性。
结语
朋友们,赶紧动手去记录日志吧!!如果你有什么想法或建议,告诉我。我希望看到在关于这类事情的后续文章,可能你就是下一个。
Until next time, stay sassy Internet!
Nathan
原文链接:Automating Docker Logging: ElasticSearch, Logstash, Kibana, and Logspout(翻译:赵军旺)
来自:http://dockone.io/article/373