【编者的话】本文主要介绍了如何使用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 两篇文章精神上的继承。十分感谢(译者注:这里原文用的的Huge shout)他们作为巨人的肩膀。本文同时也受到了 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后台程序的日志,而这些程序运行在后台并且日志记录在容器的内部磁盘上的时候,麻烦就来了。这种情况下,问题主要是:
在这片文章中我暂且不谈关于日志轮转的内容,因为那是另一个罐罐里的虫子,但是我会在这里描述该技术栈(译者注:原文是stack,不知道译成技术栈是否正确)的轮廓,主要是为了缓解处理第一个问题的过程。
对于那些把日志记录在容器内部磁盘的进程,如果想要为它们记录标签,有很多种方法可以让它工作。我最喜欢的一种方式——虽然并没有在这里真正列出来,但在我看来非常有用——是将原始容器日志记录的目录作为一个卷( volume ),并且让其他容器使用--volumes-from选项来继承这个卷。然后他们就可以使用tail -f /var/log/foo/access.log或一些其他方法来查看日志了。在我看来这促进了一个相当好的关注点的分离,因为你进行监视日志的容器与写日志的容器的不同的,另外(并不充分)你可以绕开union文件系统来进行操作(就像操作一个数据库一样)。但真的没有必要在镜像中跟踪日志(状态)。
那关于discoverability你要做些什么呢?我们将会在Docker内运行一个 ELK stack ,并且使用 logspout 工具来自动将容器的日志路由到Logstash。我真心觉得在未来这种方法会应用在很多方面 - 如果你将要运行容器、停止它们、删除它们或者其它,那么你可能也会对本地事件感兴趣,并且会让容器的生命周期可以追踪和监视。之后你的基础设施就可以重新激活而不再需要人工干预。同样对于像负载平衡,服务发现等此方法也是可用的,但那将是另一篇文章的内容了。
方法
以下是Evan和ClusterHQ的方法,我们将会运行:
如果它们听起来像是会运行吓人数量的程序,请试着不要那么烦恼 - 我们将会直接使用 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的文章里被直接忽略了,这并没有什么不好,但是当我自己想要实现这些技术的时候,遇到了一些问题:
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手动检查”好很多。需要考虑一些额外的东西,不分先后:
所以,在现实中使用它的时候仍然有很多事情需要去考虑,但我希望我所讲的这些东西会对你有所启发,就像我帮你在你的头脑了安装一些小小齿轮,推动你完成这项工作。我想现在一个非常小的创业公司都是可以运行这些工具的,而且我很兴奋像这样世界级的工具变得越来越多了。不管是现在还是将来,对一个团队来说最终它都会使至关重要的,因为他们需要将使用一个命令来处理多台机器(SRE)(译者注:不知道SRE是什么意思), 正是这类工具让这一目标变得更具可行性。
结语
朋友们,赶紧动手去记录日志吧!!如果你有什么想法或建议,告诉我。我希望看到在关于这类事情的后续文章,可能你就是下一个。
Until next time, stay sassy Internet!
Nathan
原文链接: Automating Docker Logging: ElasticSearch, Logstash, Kibana, and Logspout (翻译:赵军旺)
=======================
译者介绍
赵军旺,没什么可介绍的 :-)